diff --git a/.claude/launch.json b/.claude/launch.json
new file mode 100644
index 00000000..7bf785a8
--- /dev/null
+++ b/.claude/launch.json
@@ -0,0 +1,29 @@
+{
+ "version": "0.0.1",
+ "configurations": [
+ {
+ "name": "Fooocus WebUI",
+ "runtimeExecutable": "python",
+ "runtimeArgs": ["launch.py"],
+ "port": 7865
+ },
+ {
+ "name": "Fooocus WebUI (ブラウザ自動起動)",
+ "runtimeExecutable": "python",
+ "runtimeArgs": ["launch.py", "--in-browser"],
+ "port": 7865
+ },
+ {
+ "name": "Fooocus WebUI (アップデート付き起動)",
+ "runtimeExecutable": "python",
+ "runtimeArgs": ["entry_with_update.py"],
+ "port": 7865
+ },
+ {
+ "name": "Prompt Forge Bridge (単独テスト)",
+ "runtimeExecutable": "python",
+ "runtimeArgs": ["-c", "import prompt_forge_bridge; import time; prompt_forge_bridge.start_bridge(); print('Bridge running on http://127.0.0.1:8080'); time.sleep(86400)"],
+ "port": 8080
+ }
+ ]
+}
diff --git a/css/style.css b/css/style.css
index ad9de6f2..2dd7c460 100644
--- a/css/style.css
+++ b/css/style.css
@@ -415,4 +415,127 @@ div:has(> #positive_prompt) {
#inpaint_brush_color input[type=color]{
background: none;
+}
+
+/* ── ブラッシュアップ コンパクトボタン ── */
+.brushup_btn_col button {
+ padding: 3px 6px !important;
+ font-size: 11px !important;
+ min-height: 28px !important;
+ height: 28px !important;
+ line-height: 1.2 !important;
+}
+
+#brushup_bar {
+ align-items: flex-start !important;
+ gap: 6px !important;
+}
+
+/* ── OpenCV エディットパネル ── */
+#edit_panel {
+ border-top: 1px solid rgba(255,255,255,0.08) !important;
+ padding-top: 8px !important;
+ margin-top: 4px !important;
+}
+
+/* プリセットボタン */
+#edit_presets button, .preset_btn button {
+ padding: 3px 5px !important;
+ font-size: 10px !important;
+ min-height: 26px !important;
+ height: 26px !important;
+ line-height: 1.1 !important;
+}
+
+/* 回転・反転・B/Aボタン */
+#edit_panel .gr-row button {
+ padding: 3px 6px !important;
+ font-size: 11px !important;
+ min-height: 26px !important;
+}
+
+/* スライダーラベル */
+#edit_panel label {
+ font-size: 11px !important;
+ margin-bottom: 0 !important;
+}
+
+/* スライダー間隔を詰める */
+#edit_panel .gr-form {
+ gap: 2px !important;
+}
+
+/* ══════════════════════════════════════════
+ 統一感テーマ — アクセントカラー: #7c4dff (紫)
+ ══════════════════════════════════════════ */
+
+/* ── アコーディオン共通 ── */
+#pf_accordion > .label-wrap,
+#user_sp_accordion > .label-wrap {
+ border-left: 3px solid #7c4dff !important;
+ padding-left: 8px !important;
+}
+
+/* ── ブラッシュアップバー 背景帯 ── */
+#brushup_bar {
+ background: rgba(124, 77, 255, 0.04) !important;
+ border-radius: 8px !important;
+ padding: 6px 8px !important;
+ border: 1px solid rgba(124, 77, 255, 0.12) !important;
+}
+
+/* ── 微調整・強変更ボタン: プライマリ色をテーマ紫に ── */
+.brushup_btn_col button.secondary {
+ border-color: rgba(124, 77, 255, 0.4) !important;
+ color: rgba(180, 150, 255, 0.9) !important;
+}
+.brushup_btn_col button.secondary:hover {
+ background: rgba(124, 77, 255, 0.15) !important;
+ border-color: #7c4dff !important;
+}
+
+/* ── 変更して生成ボタン ── */
+.brushup_btn_col button.primary {
+ background: linear-gradient(135deg, #7c4dff 0%, #5c35cc 100%) !important;
+ border: none !important;
+}
+
+/* ── エディットパネル ── */
+#edit_panel {
+ background: rgba(124, 77, 255, 0.03) !important;
+ border-radius: 6px !important;
+ border: 1px solid rgba(124, 77, 255, 0.10) !important;
+ border-top: 1px solid rgba(124, 77, 255, 0.15) !important;
+ padding: 8px !important;
+}
+
+/* ── 設定プリセット アコーディオン内 ── */
+#user_sp_accordion {
+ border-radius: 8px !important;
+ overflow: hidden !important;
+}
+
+/* ── 生成ボタンは維持しつつ補助ボタンをトーン統一 ── */
+#generate_button {
+ background: linear-gradient(135deg, #7c4dff 0%, #3a1d96 100%) !important;
+ box-shadow: 0 2px 12px rgba(124, 77, 255, 0.35) !important;
+}
+
+/* ── セクション区切り (Prompt Forge 上部) ── */
+#pf_accordion {
+ border-radius: 8px !important;
+ overflow: hidden !important;
+}
+
+/* ── プリセットボタン ホバー ── */
+#edit_presets button:hover, .preset_btn button:hover {
+ background: rgba(124, 77, 255, 0.2) !important;
+ border-color: #7c4dff !important;
+ color: #d0b8ff !important;
+}
+
+/* ── ステータス HTML テキストカラー ── */
+#brushup_status_html {
+ color: rgba(200, 185, 255, 0.85) !important;
+ font-size: 12px !important;
}
\ No newline at end of file
diff --git a/modules/image_editor.py b/modules/image_editor.py
new file mode 100644
index 00000000..afe4f907
--- /dev/null
+++ b/modules/image_editor.py
@@ -0,0 +1,104 @@
+"""OpenCV-based image adjustment utilities for the gallery editor."""
+import cv2
+import numpy as np
+
+
+# ────────────────────────────────────────────────
+# ユーティリティ
+# ────────────────────────────────────────────────
+
+def resize_for_preview(img_array, max_size=768):
+ """プレビュー用にアスペクト比を保ってリサイズ(高速化)。"""
+ if img_array is None:
+ return None
+ h, w = img_array.shape[:2]
+ if max(h, w) <= max_size:
+ return img_array
+ scale = max_size / max(h, w)
+ nw, nh = int(w * scale), int(h * scale)
+ return cv2.resize(img_array, (nw, nh), interpolation=cv2.INTER_AREA)
+
+
+def rotate_image(img_array, degrees):
+ """画像を 90 / 180 / 270 度回転(RGB 入出力)。"""
+ if img_array is None:
+ return None
+ degrees = int(degrees) % 360
+ if degrees == 90:
+ return cv2.rotate(img_array, cv2.ROTATE_90_CLOCKWISE)
+ elif degrees == 180:
+ return cv2.rotate(img_array, cv2.ROTATE_180)
+ elif degrees == 270:
+ return cv2.rotate(img_array, cv2.ROTATE_90_COUNTERCLOCKWISE)
+ return img_array
+
+
+def flip_image(img_array, direction='h'):
+ """水平('h') または垂直('v') 反転(RGB 入出力)。"""
+ if img_array is None:
+ return None
+ code = 1 if direction == 'h' else 0
+ return cv2.flip(img_array, code)
+
+
+# ────────────────────────────────────────────────
+# メイン調整関数
+# ────────────────────────────────────────────────
+
+def apply_adjustments(img_array,
+ brightness=0, contrast=1.0,
+ saturation=1.0, hue_shift=0,
+ sharpness=1.0, temperature=0):
+ """OpenCV で画像調整を適用する。
+
+ Parameters
+ ----------
+ img_array : numpy ndarray (H, W, 3) RGB
+ brightness : int -100 … 100 加算
+ contrast : float 0.1 … 3.0 乗算
+ saturation : float 0.0 … 3.0 HSV S 倍率
+ hue_shift : int -90 … 90 HSV H 加算(度)
+ sharpness : float 0.0 … 3.0 1.0=変化なし / >1=鮮明 / <1=ぼかし
+ temperature : int -100 … 100 負=クール(青) / 正=ウォーム(赤)
+
+ Returns
+ -------
+ numpy ndarray (H, W, 3) RGB
+ """
+ if img_array is None:
+ return None
+
+ img = np.clip(img_array, 0, 255).astype(np.uint8)
+ bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
+
+ # ── 明るさ / コントラスト ──────────────────────
+ if brightness != 0 or contrast != 1.0:
+ bgr = cv2.convertScaleAbs(bgr, alpha=float(contrast), beta=float(brightness))
+
+ # ── 彩度 / 色相 ────────────────────────────────
+ if saturation != 1.0 or hue_shift != 0:
+ hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV).astype(np.float32)
+ hsv[:, :, 0] = (hsv[:, :, 0] + float(hue_shift)) % 180
+ hsv[:, :, 1] = np.clip(hsv[:, :, 1] * float(saturation), 0, 255)
+ bgr = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
+
+ # ── 色温度 ─────────────────────────────────────
+ if temperature != 0:
+ b, g, r = cv2.split(bgr.astype(np.float32))
+ t = float(temperature)
+ if t > 0: # ウォーム (赤+, 青-)
+ r = np.clip(r + t * 0.8, 0, 255)
+ b = np.clip(b - t * 0.4, 0, 255)
+ else: # クール (青+, 赤-)
+ r = np.clip(r + t * 0.4, 0, 255)
+ b = np.clip(b - t * 0.8, 0, 255)
+ bgr = cv2.merge([b.astype(np.uint8), g.astype(np.uint8), r.astype(np.uint8)])
+
+ # ── シャープネス(アンシャープマスク)─────────
+ if sharpness != 1.0:
+ blur = cv2.GaussianBlur(bgr, (0, 0), 3.0)
+ strength = float(sharpness) - 1.0
+ bgr = cv2.addWeighted(bgr, 1.0 + strength, blur, -strength, 0)
+ bgr = np.clip(bgr, 0, 255).astype(np.uint8)
+
+ return cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
diff --git a/prompt_forge_bridge.py b/prompt_forge_bridge.py
new file mode 100644
index 00000000..5be71b1b
--- /dev/null
+++ b/prompt_forge_bridge.py
@@ -0,0 +1,323 @@
+"""
+Prompt Forge Bridge Server
+--------------------------
+Fooocusプロセス内でバックグラウンドスレッドとして動作するHTTPサーバー。
+- ポート8080でPrompt ForgeのHTMLを配信
+- /v1/generation/text-to-image REST APIを提供
+- Fooocusのworkerキューに直接AsyncTaskを投入して画像生成
+"""
+
+import json
+import os
+import base64
+import threading
+import time
+import io
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from urllib.parse import urlparse
+
+BRIDGE_PORT = 8080
+_html_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'prompt_forge_v3_11.html')
+
+
+# ──────────────────────────────────────────────────────────────────
+# AsyncTask args ビルダー
+# ──────────────────────────────────────────────────────────────────
+def _build_args(req: dict) -> list:
+ """リクエストJSONからAsyncTask argsリストを構築する。"""
+ import modules.config as config
+ import modules.flags as flags
+ import args_manager
+
+ # ── ユーザー指定パラメータ ──
+ prompt = req.get('prompt', '')
+ negative_prompt = req.get('negative_prompt', '')
+ styles = req.get('style_selections', list(config.default_styles))
+ if isinstance(styles, str):
+ styles = [styles]
+ performance = req.get('performance_selection', 'Speed')
+ image_number = int(req.get('image_number', 1))
+ output_format = req.get('output_format', 'png')
+ image_seed = int(req.get('image_seed', -1))
+ sharpness = float(req.get('sharpness', config.default_sample_sharpness))
+ guidance_scale = float(req.get('guidance_scale', config.default_cfg_scale))
+
+ # アスペクト比を Fooocus 形式に変換 ("1024*1024" → "1024×1024 | 1:1")
+ aspect_raw = str(req.get('aspect_ratios_selection', '1024*1024'))
+ aspect_raw = aspect_raw.replace('×', '*').replace('x', '*').replace('X', '*')
+ if '*' not in aspect_raw:
+ aspect_raw = '1024*1024'
+ aspect_sel = config.add_ratio(aspect_raw)
+
+ args = [
+ False, # generate_image_grid
+ prompt,
+ negative_prompt,
+ styles,
+ performance,
+ aspect_sel,
+ image_number,
+ output_format,
+ image_seed,
+ False, # read_wildcards_in_order
+ sharpness,
+ guidance_scale,
+ config.default_base_model_name,
+ config.default_refiner_model_name,
+ config.default_refiner_switch,
+ ]
+
+ # LoRA (default_max_lora_number x 3: enabled, name, weight)
+ # default_loras は必ず default_max_lora_number 個にパディングされている
+ loras_to_add = config.default_loras[:config.default_max_lora_number]
+ # 不足分を 'None' で補填
+ while len(loras_to_add) < config.default_max_lora_number:
+ loras_to_add.append([True, 'None', 1.0])
+ for lora in loras_to_add:
+ args.extend([bool(lora[0]), str(lora[1]), float(lora[2])])
+
+ args += [
+ False, # input_image_checkbox
+ 'uov', # current_tab
+ flags.disabled, # uov_method ('Disabled')
+ None, # uov_input_image
+ [], # outpaint_selections
+ {'image': None, 'mask': None}, # inpaint_input_image
+ '', # inpaint_additional_prompt
+ None, # inpaint_mask_image_upload
+ False, # disable_preview
+ False, # disable_intermediate_results
+ False, # disable_seed_increment
+ config.default_black_out_nsfw,
+ 1.5, # adm_scaler_positive
+ 0.8, # adm_scaler_negative
+ 0.3, # adm_scaler_end
+ config.default_cfg_tsnr, # adaptive_cfg
+ config.default_clip_skip, # clip_skip
+ config.default_sampler, # sampler_name
+ config.default_scheduler, # scheduler_name
+ flags.default_vae, # vae_name
+ -1, # overwrite_step
+ -1, # overwrite_switch
+ -1, # overwrite_width
+ -1, # overwrite_height
+ -1, # overwrite_vary_strength
+ -1, # overwrite_upscale_strength
+ False, # mixing_image_prompt_and_vary_upscale
+ False, # mixing_image_prompt_and_inpaint
+ False, # debugging_cn_preprocessor
+ False, # skipping_cn_preprocessor
+ 100, # canny_low_threshold
+ 200, # canny_high_threshold
+ flags.refiner_swap_method, # 'joint'
+ 0.25, # controlnet_softness
+ False, # freeu_enabled
+ 1.01, # freeu_b1
+ 1.02, # freeu_b2
+ 0.99, # freeu_s1
+ 0.95, # freeu_s2
+ # ── inpaint_ctrls ──
+ False, # debugging_inpaint_preprocessor
+ False, # inpaint_disable_initial_latent
+ config.default_inpaint_engine_version, # inpaint_engine
+ 1.0, # inpaint_strength
+ 0.618, # inpaint_respective_field
+ False, # inpaint_advanced_masking_checkbox
+ False, # invert_mask_checkbox
+ 0, # inpaint_erode_or_dilate
+ ]
+
+ if not args_manager.args.disable_image_log:
+ args.append(config.default_save_only_final_enhanced_image)
+ if not args_manager.args.disable_metadata:
+ args.append(config.default_save_metadata_to_images)
+ args.append('fooocus') # metadata_scheme
+
+ # ControlNet IP (default_controlnet_image_count x 4)
+ for _ in range(config.default_controlnet_image_count):
+ args += [None, 0.5, 0.6, flags.default_ip] # img, stop, weight, type
+
+ # enhance 制御
+ args += [
+ False, # debugging_dino
+ 0, # dino_erode_or_dilate
+ False, # debugging_enhance_masks_checkbox
+ None, # enhance_input_image
+ False, # enhance_checkbox
+ config.default_enhance_uov_method,
+ config.default_enhance_uov_processing_order,
+ config.default_enhance_uov_prompt_type,
+ ]
+
+ # enhance タブ (default_enhance_tabs x 16)
+ for _ in range(config.default_enhance_tabs):
+ args += [
+ False, # enhance_enabled
+ '', # enhance_mask_dino_prompt_text
+ '', # enhance_prompt
+ '', # enhance_negative_prompt
+ config.default_enhance_inpaint_mask_model, # 'sam'
+ config.default_inpaint_mask_cloth_category, # 'full'
+ config.default_inpaint_mask_sam_model, # 'vit_b'
+ 0.25, # enhance_mask_text_threshold
+ 0.3, # enhance_mask_box_threshold
+ config.default_sam_max_detections,
+ False, # enhance_inpaint_disable_initial_latent
+ config.default_inpaint_engine_version,
+ 0.5, # enhance_inpaint_strength
+ 0.618, # enhance_inpaint_respective_field
+ 0, # enhance_inpaint_erode_or_dilate
+ False, # enhance_mask_invert
+ ]
+
+ print(f'[PromptForge] args構築完了: {len(args)}個 '
+ f'(max_lora={config.default_max_lora_number}, '
+ f'cn_count={config.default_controlnet_image_count}, '
+ f'enhance_tabs={config.default_enhance_tabs})')
+ return args
+
+
+# ──────────────────────────────────────────────────────────────────
+# 画像生成メイン
+# ──────────────────────────────────────────────────────────────────
+def _generate(req: dict) -> list:
+ """
+ Fooocusのworkerキューに生成タスクを投入し、完成した画像をbase64リストで返す。
+ """
+ import modules.async_worker as worker
+ from modules.async_worker import AsyncTask
+
+ args = _build_args(req)
+ task = AsyncTask(args=args)
+ worker.async_tasks.append(task)
+
+ print(f'[PromptForge] 生成開始: "{task.prompt[:60]}"')
+
+ timeout = 600 # 10分
+ start = time.time()
+ results_b64 = []
+
+ while time.time() - start < timeout:
+ time.sleep(0.3)
+
+ if task.last_stop:
+ raise RuntimeError('生成がキャンセルされました')
+
+ # yields をすべて処理
+ while task.yields:
+ flag, product = task.yields.pop(0)
+ if flag == 'finish':
+ for img in product:
+ try:
+ if isinstance(img, str) and os.path.exists(img):
+ with open(img, 'rb') as f:
+ b64 = base64.b64encode(f.read()).decode('utf-8')
+ elif hasattr(img, 'save'):
+ buf = io.BytesIO()
+ img.save(buf, format='PNG')
+ b64 = base64.b64encode(buf.getvalue()).decode('utf-8')
+ else:
+ continue
+ results_b64.append({
+ 'base64': b64,
+ 'url': '',
+ 'seed': task.seed if hasattr(task, 'seed') else req.get('image_seed', -1),
+ 'finish_reason': 'SUCCESS',
+ 'meta': {}
+ })
+ except Exception as e:
+ print(f'[PromptForge] 画像読込エラー: {e}')
+ if results_b64:
+ print(f'[PromptForge] 完了: {len(results_b64)}枚')
+ return results_b64
+ raise RuntimeError('画像データを取得できませんでした')
+
+ raise TimeoutError('生成タイムアウト (600秒)')
+
+
+# ──────────────────────────────────────────────────────────────────
+# HTTP ハンドラ
+# ──────────────────────────────────────────────────────────────────
+class _Handler(BaseHTTPRequestHandler):
+
+ def log_message(self, fmt, *args):
+ pass # コンソールへの詳細ログを抑制
+
+ def _cors(self):
+ self.send_header('Access-Control-Allow-Origin', '*')
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type, Accept')
+
+ def do_OPTIONS(self):
+ self.send_response(200)
+ self._cors()
+ self.end_headers()
+
+ def do_GET(self):
+ path = urlparse(self.path).path.rstrip('/') or '/'
+ if path in ('/', '/index.html'):
+ if os.path.exists(_html_path):
+ with open(_html_path, 'rb') as f:
+ data = f.read()
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html; charset=utf-8')
+ self._cors()
+ self.end_headers()
+ self.wfile.write(data)
+ else:
+ self.send_response(404)
+ self.end_headers()
+ self.wfile.write(b'prompt_forge_v3_11.html not found')
+ elif path == '/ping':
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self._cors()
+ self.end_headers()
+ self.wfile.write(b'pong')
+ else:
+ self.send_response(404)
+ self.end_headers()
+
+ def do_POST(self):
+ path = urlparse(self.path).path
+ if path == '/v1/generation/text-to-image':
+ try:
+ length = int(self.headers.get('Content-Length', 0))
+ body = self.rfile.read(length)
+ req = json.loads(body.decode('utf-8')) if body else {}
+ images = _generate(req)
+ resp = json.dumps(images).encode('utf-8')
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/json')
+ self._cors()
+ self.end_headers()
+ self.wfile.write(resp)
+ except Exception as e:
+ import traceback
+ msg = traceback.format_exc()
+ print(f'[PromptForge] エラー:\n{msg}')
+ resp = json.dumps({'error': str(e), 'traceback': msg}).encode('utf-8')
+ self.send_response(500)
+ self.send_header('Content-Type', 'application/json')
+ self._cors()
+ self.end_headers()
+ self.wfile.write(resp)
+ else:
+ self.send_response(404)
+ self.end_headers()
+
+
+# ──────────────────────────────────────────────────────────────────
+# 起動関数(webui.py から呼ばれる)
+# ──────────────────────────────────────────────────────────────────
+def start_bridge(port: int = BRIDGE_PORT):
+ """ブリッジサーバーをデーモンスレッドで起動する。"""
+ server = HTTPServer(('127.0.0.1', port), _Handler)
+ t = threading.Thread(target=server.serve_forever, daemon=True)
+ t.start()
+ print(f'\n{"="*55}')
+ print(f' 🎨 Prompt Forge が起動しました!')
+ print(f' ブラウザで以下を開いてください:')
+ print(f' http://127.0.0.1:{port}')
+ print(f'{"="*55}\n')
+ return server, t
diff --git a/prompt_forge_v3_11.html b/prompt_forge_v3_11.html
new file mode 100644
index 00000000..e16eac1f
--- /dev/null
+++ b/prompt_forge_v3_11.html
@@ -0,0 +1,7710 @@
+
+
+
+
+
+PROMPT FORGE
+
+
+
+PROMPT FORGE
+Fooocus 日本語プロンプト → 英語変換 v3
+
+
+
+
+
POSITIVE
+
ここにポジティブプロンプトが表示されます
+
NEGATIVE
+
ここにネガティブプロンプトが表示されます
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ 翻訳結果
+
← 上で翻訳ボタンを押してください
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ 翻訳結果(ネガティブ)
+
← ネガティブを翻訳してください
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📦 キャラクタープリセット
+
+
+
▶
+
+
+
+
+
+ ◾ マイプリセット
+
+
まだ保存なし
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ モデル別タグ 0 selected
+
▶
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 📚 作品 ▸
+
+
+
+
+
+ 🔍 キャラ検索 ▸
+
+
+
+
+
+
+
+ 外見 ▸
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 衣装 ▸
+
+
+
+
+
+
+
+
+
+
+ ANI ▸
+
+
+
+
+
+
+
+ PONY ▸
+
+
+
+
+
+
+ NSFW ▸
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 📷 写真 ▸
+
+
+
+
+
+
+
+
+
+
+
▸ 選択中タグ(ドラッグで並び替え)
+
タグをクリックして追加
+
▸ ネガティブ選択タグ
+
ネガティブタグをクリックして追加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ Wildcard 置き場 TXTファイルと対応
+
+
+
+
+
▶
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ Wildcard TXT 出力(現在のタグ一覧から自動作成)
+
+
+
+
+
+
+
+
+
※ 内容は常に現在のタグ一覧から生成されます。HTML内のPRESETSを編集したら、再度エクスポートすると反映されます。
+
+
+ ▸ TXTは Fooocus の wildcards/ フォルダに配置
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ Anthropic API Key
+
+
+ 未設定
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 未保存
+
+
+
+
+
+
+
+
▸ 保存一覧(検索)
+
+
+
+
+
+
+
+
+
+
+
▸ 選択中(コピー・画像・メタデータ)
+
+
+
+
+
左の一覧から選択してください
+
+
画像メタデータはここに表示
+
+
+
+
+
+
+
+
+
+
+
+
▸ Fooocus API 接続設定
+
+
+
+
+
+ オフライン
+
+
+
+ API Path:
+
+
+
+ ※ run_with_forge.bat でFooocusを起動すると自動的に接続できます。デフォルト: http://127.0.0.1:8080
+
+
+
+
+
+
▸ 生成パラメータ
+
+
+
+ 📐 サイズ
+
+ 🖼 枚数
+
+ 🎲 シード
+
+
+
+
+
+
+ ▸ 詳細設定(クリックで展開)
+
+
+
+
+
+
▸ POSITIVE(最終プロンプトから自動取得・編集可)
+
+
▸ NEGATIVE(最終プロンプトから自動取得・編集可)
+
+
+
+
+
+
+
+
+
+
+
+
⚡ GENERATING...
+
+
経過時間: 0s
+
+
+
+
+
+
+
+
+
+
▸ 生成済み画像ギャラリー
+
+
+
+
▶
+
+
+
+
+
+
+
+
+
✕
+
![Generated image]()
+
+
+※ APIキーはこのページ内のみで使用。外部送信なし(Anthropic APIへの翻訳リクエストを除く)
+
+
+
+
+
+
+
+
+
+
+
🎨 色調補正
+
+
![補正プレビュー]()
+
+
+ PRESET:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 💡 スライダーをダブルクリックすると個別リセット
+
+
+
+
+
+
+
+
+
+
𝕏 X (Twitter) に投稿
+
![投稿画像プレビュー]()
+
+ 📌 投稿キャプション(280文字以内)
+
+
+
0 / 280
+
+ 🏷 ハッシュタグ(クリックで追加/解除)
+
+
+
+
+
+
+
+
+
+
+
+
+
📋 投稿手順
+
+ ① 画像を自動ダウンロード
+ ② キャプション+ハッシュタグをクリップボードにコピー
+ ③ X.com が新しいタブで開きます
+ ④ 画像を添付 → キャプションを貼り付けて投稿
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/run_forge.bat b/run_forge.bat
new file mode 100644
index 00000000..ac667bac
--- /dev/null
+++ b/run_forge.bat
@@ -0,0 +1,33 @@
+@echo off
+chcp 65001 > nul
+title FOOOCUS FORGE LAUNCHER
+
+cd /d "%~dp0"
+
+REM ── Python を探す (embedded > PATH) ──────────────────────────────
+if exist "python_embedded\python.exe" (
+ set "PYTHON=python_embedded\python.exe"
+ echo [INFO] Embedded Python を使用します
+) else (
+ set "PYTHON=python"
+)
+
+REM ── tkinter が使えるか確認 ───────────────────────────────────────
+%PYTHON% -c "import tkinter" 2>nul
+if errorlevel 1 (
+ echo.
+ echo [ERROR] tkinter が見つかりません。
+ echo Python を公式サイト (python.org) からインストールしてください。
+ echo インストール時に "tcl/tk and IDLE" にチェックを入れてください。
+ pause
+ exit /b 1
+)
+
+REM ── ランチャー起動 ───────────────────────────────────────────────
+%PYTHON% run_forge_launcher.py
+
+if errorlevel 1 (
+ echo.
+ echo [ERROR] 起動中にエラーが発生しました。
+ pause
+)
diff --git a/run_forge_launcher.py b/run_forge_launcher.py
new file mode 100644
index 00000000..7cd18ca6
--- /dev/null
+++ b/run_forge_launcher.py
@@ -0,0 +1,433 @@
+"""
+FOOOCUS FORGE LAUNCHER
+tkinter dark-themed GUI launcher for Fooocus + Prompt Forge Bridge
+"""
+import sys
+import os
+import threading
+import subprocess
+import socket
+import time
+import webbrowser
+import queue
+import tkinter as tk
+from tkinter import ttk, scrolledtext, messagebox
+
+# ── カラーテーマ ──────────────────────────────────────────────────
+BG = "#0a0a0f"
+BG2 = "#12121a"
+BG3 = "#1a1a28"
+BORDER = "#2a2a3d"
+FG = "#e8e8f0"
+FG2 = "#8888aa"
+ACCENT = "#7c4dff"
+ACCENT2 = "#b388ff"
+ACCENT3 = "#00e5ff"
+GREEN = "#00e676"
+RED = "#ff1744"
+YELLOW = "#ffea00"
+ORANGE = "#ff6d00"
+X_BLUE = "#1da1f2"
+
+# ── ポート定義 ─────────────────────────────────────────────────────
+PORT_FOOOCUS = 7865
+PORT_BRIDGE = 8080
+
+# ── 起動コマンド ───────────────────────────────────────────────────
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+
+def _py():
+ emb = os.path.join(BASE_DIR, "python_embedded", "python.exe")
+ return emb if os.path.exists(emb) else sys.executable
+
+def build_fooocus_cmd(preset="", in_browser=False, listen=False):
+ cmd = [_py(), "launch.py"]
+ if preset:
+ cmd += ["--preset", preset]
+ if in_browser:
+ cmd.append("--in-browser")
+ if listen:
+ cmd.append("--listen")
+ return cmd
+
+def is_port_open(port: int) -> bool:
+ try:
+ with socket.create_connection(("127.0.0.1", port), timeout=0.5):
+ return True
+ except OSError:
+ return False
+
+
+# ══════════════════════════════════════════════════════════════════
+class LauncherApp(tk.Tk):
+ def __init__(self):
+ super().__init__()
+
+ self.title("FOOOCUS FORGE LAUNCHER")
+ self.configure(bg=BG)
+ self.resizable(True, True)
+ self.minsize(680, 520)
+
+ # プロセス管理
+ self._proc_fooocus: subprocess.Popen | None = None
+ self._log_queue: queue.Queue = queue.Queue()
+
+ # ── ウィジェット構築 ──
+ self._build_header()
+ self._build_status_bar()
+ self._build_options()
+ self._build_controls()
+ self._build_log()
+ self._build_links()
+
+ # ── イベント ──
+ self.bind("", lambda _: self._on_close())
+ self.protocol("WM_DELETE_WINDOW", self._on_close)
+
+ # ── ポーリング開始 ──
+ self._poll_status()
+ self._drain_log_queue()
+
+ # ── ヘッダー ──────────────────────────────────────────────────
+ def _build_header(self):
+ f = tk.Frame(self, bg=BG, pady=12)
+ f.pack(fill="x", padx=20)
+ tk.Label(f, text="⚡ FOOOCUS FORGE LAUNCHER",
+ bg=BG, fg=ACCENT2,
+ font=("Consolas", 16, "bold")).pack(side="left")
+ tk.Label(f, text="v1.0",
+ bg=BG, fg=FG2,
+ font=("Consolas", 10)).pack(side="left", padx=(8, 0), pady=4)
+
+ # ── ステータスバー ────────────────────────────────────────────
+ def _build_status_bar(self):
+ outer = tk.Frame(self, bg=BG3, bd=0, highlightbackground=BORDER,
+ highlightthickness=1)
+ outer.pack(fill="x", padx=20, pady=(0, 8))
+
+ inner = tk.Frame(outer, bg=BG3, pady=8)
+ inner.pack(fill="x", padx=12)
+
+ # Fooocus
+ fc = tk.Frame(inner, bg=BG3)
+ fc.pack(side="left", padx=(0, 24))
+ self._dot_fooocus = tk.Label(fc, text="●", fg=RED, bg=BG3,
+ font=("Consolas", 14))
+ self._dot_fooocus.pack(side="left")
+ tk.Label(fc, text=f" Fooocus :{PORT_FOOOCUS}",
+ bg=BG3, fg=FG, font=("Consolas", 11)).pack(side="left")
+ self._lbl_fooocus = tk.Label(fc, text="停止中",
+ bg=BG3, fg=RED,
+ font=("Consolas", 10))
+ self._lbl_fooocus.pack(side="left", padx=(6, 0))
+
+ sep = tk.Frame(inner, bg=BORDER, width=1, height=24)
+ sep.pack(side="left", padx=12)
+
+ # Bridge
+ bc = tk.Frame(inner, bg=BG3)
+ bc.pack(side="left")
+ self._dot_bridge = tk.Label(bc, text="●", fg=RED, bg=BG3,
+ font=("Consolas", 14))
+ self._dot_bridge.pack(side="left")
+ tk.Label(bc, text=f" Bridge :{PORT_BRIDGE}",
+ bg=BG3, fg=FG, font=("Consolas", 11)).pack(side="left")
+ self._lbl_bridge = tk.Label(bc, text="停止中",
+ bg=BG3, fg=RED,
+ font=("Consolas", 10))
+ self._lbl_bridge.pack(side="left", padx=(6, 0))
+
+ # ── 起動オプション ────────────────────────────────────────────
+ def _build_options(self):
+ outer = tk.Frame(self, bg=BG2, bd=0, highlightbackground=BORDER,
+ highlightthickness=1)
+ outer.pack(fill="x", padx=20, pady=(0, 8))
+
+ inner = tk.Frame(outer, bg=BG2, pady=8, padx=12)
+ inner.pack(fill="x")
+
+ tk.Label(inner, text="起動オプション", bg=BG2, fg=FG2,
+ font=("Consolas", 9, "bold")).grid(
+ row=0, column=0, columnspan=6, sticky="w", pady=(0, 6))
+
+ # プリセット
+ tk.Label(inner, text="プリセット:", bg=BG2, fg=FG,
+ font=("Consolas", 10)).grid(row=1, column=0, sticky="w", padx=(0, 6))
+ self._preset_var = tk.StringVar(value="なし")
+ preset_cb = ttk.Combobox(inner, textvariable=self._preset_var,
+ values=["なし", "anime", "realistic", "lcm"],
+ state="readonly", width=12,
+ font=("Consolas", 10))
+ preset_cb.grid(row=1, column=1, sticky="w", padx=(0, 20))
+ self._style_combobox(preset_cb)
+
+ # ブラウザ自動起動
+ self._browser_var = tk.BooleanVar(value=False)
+ chk_browser = tk.Checkbutton(inner, text="ブラウザ自動起動",
+ variable=self._browser_var,
+ bg=BG2, fg=FG, selectcolor=BG3,
+ activebackground=BG2, activeforeground=ACCENT2,
+ font=("Consolas", 10))
+ chk_browser.grid(row=1, column=2, sticky="w", padx=(0, 20))
+
+ # --listen
+ self._listen_var = tk.BooleanVar(value=False)
+ chk_listen = tk.Checkbutton(inner, text="--listen (LAN公開)",
+ variable=self._listen_var,
+ bg=BG2, fg=FG, selectcolor=BG3,
+ activebackground=BG2, activeforeground=ACCENT2,
+ font=("Consolas", 10))
+ chk_listen.grid(row=1, column=3, sticky="w")
+
+ # ── コントロールボタン ────────────────────────────────────────
+ def _build_controls(self):
+ f = tk.Frame(self, bg=BG, pady=4)
+ f.pack(fill="x", padx=20)
+
+ btn_cfg = dict(font=("Consolas", 11, "bold"), cursor="hand2",
+ relief="flat", bd=0, padx=18, pady=7)
+
+ self._btn_start = tk.Button(
+ f, text="▶ 起動", bg=ACCENT, fg="white",
+ activebackground=ACCENT2, activeforeground="white",
+ command=self._start_all, **btn_cfg)
+ self._btn_start.pack(side="left", padx=(0, 8))
+
+ self._btn_stop = tk.Button(
+ f, text="■ 停止", bg=BG3, fg=RED,
+ activebackground=BORDER, activeforeground=RED,
+ command=self._stop_all, state="disabled", **btn_cfg)
+ self._btn_stop.pack(side="left", padx=(0, 8))
+
+ self._btn_restart = tk.Button(
+ f, text="↺ 再起動", bg=BG3, fg=YELLOW,
+ activebackground=BORDER, activeforeground=YELLOW,
+ command=self._restart_all, state="disabled", **btn_cfg)
+ self._btn_restart.pack(side="left", padx=(0, 8))
+
+ tk.Button(f, text="ログクリア", bg=BG3, fg=FG2,
+ activebackground=BORDER, activeforeground=FG,
+ command=self._clear_log, **btn_cfg).pack(side="right")
+
+ # ── ログ ──────────────────────────────────────────────────────
+ def _build_log(self):
+ f = tk.Frame(self, bg=BG)
+ f.pack(fill="both", expand=True, padx=20, pady=(8, 4))
+
+ tk.Label(f, text="ログ", bg=BG, fg=FG2,
+ font=("Consolas", 9, "bold")).pack(anchor="w", pady=(0, 3))
+
+ self._log = scrolledtext.ScrolledText(
+ f, bg=BG2, fg=FG, insertbackground=FG,
+ font=("Consolas", 9), relief="flat",
+ bd=0, highlightbackground=BORDER, highlightthickness=1,
+ state="disabled", wrap="word")
+ self._log.pack(fill="both", expand=True)
+
+ # タグ色
+ self._log.tag_config("info", foreground=FG2)
+ self._log.tag_config("ok", foreground=GREEN)
+ self._log.tag_config("warn", foreground=YELLOW)
+ self._log.tag_config("err", foreground=RED)
+ self._log.tag_config("accent", foreground=ACCENT3)
+
+ # ── クイックリンク ────────────────────────────────────────────
+ def _build_links(self):
+ f = tk.Frame(self, bg=BG, pady=8)
+ f.pack(fill="x", padx=20)
+
+ link_cfg = dict(font=("Consolas", 10), cursor="hand2",
+ relief="flat", bd=0, padx=12, pady=5)
+
+ tk.Button(f, text="🌐 Prompt Forge http://127.0.0.1:8080",
+ bg=BG3, fg=ACCENT3,
+ activebackground=BORDER, activeforeground=ACCENT3,
+ command=lambda: webbrowser.open("http://127.0.0.1:8080"),
+ **link_cfg).pack(side="left", padx=(0, 8))
+
+ tk.Button(f, text="🌐 Fooocus UI http://127.0.0.1:7865",
+ bg=BG3, fg=ACCENT2,
+ activebackground=BORDER, activeforeground=ACCENT2,
+ command=lambda: webbrowser.open("http://127.0.0.1:7865"),
+ **link_cfg).pack(side="left")
+
+ # ── Combobox スタイル ─────────────────────────────────────────
+ def _style_combobox(self, cb):
+ style = ttk.Style(self)
+ style.theme_use("default")
+ style.configure("TCombobox",
+ fieldbackground=BG3,
+ background=BG3,
+ foreground=FG,
+ selectbackground=ACCENT,
+ selectforeground="white",
+ arrowcolor=FG2)
+ style.map("TCombobox",
+ fieldbackground=[("readonly", BG3)],
+ foreground=[("readonly", FG)],
+ background=[("readonly", BG3)])
+ self.option_add("*TCombobox*Listbox.background", BG3)
+ self.option_add("*TCombobox*Listbox.foreground", FG)
+ self.option_add("*TCombobox*Listbox.selectBackground", ACCENT)
+ self.option_add("*TCombobox*Listbox.font", ("Consolas", 10))
+
+ # ── ログ書き込み ──────────────────────────────────────────────
+ def _log_write(self, text: str, tag: str = "info"):
+ self._log.configure(state="normal")
+ self._log.insert("end", text + "\n", tag)
+ self._log.see("end")
+ self._log.configure(state="disabled")
+
+ def _clear_log(self):
+ self._log.configure(state="normal")
+ self._log.delete("1.0", "end")
+ self._log.configure(state="disabled")
+
+ # ── ログキューをUIスレッドで消費 ──────────────────────────────
+ def _drain_log_queue(self):
+ try:
+ while True:
+ text, tag = self._log_queue.get_nowait()
+ self._log_write(text, tag)
+ except queue.Empty:
+ pass
+ self.after(100, self._drain_log_queue)
+
+ def _queue_log(self, text: str, tag: str = "info"):
+ self._log_queue.put((text, tag))
+
+ # ── ステータスポーリング ──────────────────────────────────────
+ def _poll_status(self):
+ fooocus_up = is_port_open(PORT_FOOOCUS)
+ bridge_up = is_port_open(PORT_BRIDGE)
+
+ # Fooocus
+ if fooocus_up:
+ self._dot_fooocus.config(fg=GREEN)
+ self._lbl_fooocus.config(text="起動中", fg=GREEN)
+ else:
+ if self._proc_fooocus and self._proc_fooocus.poll() is None:
+ self._dot_fooocus.config(fg=YELLOW)
+ self._lbl_fooocus.config(text="起動中...", fg=YELLOW)
+ else:
+ self._dot_fooocus.config(fg=RED)
+ self._lbl_fooocus.config(text="停止中", fg=RED)
+
+ # Bridge (Fooocusの中で起動されるためプロセスを別管理しない)
+ if bridge_up:
+ self._dot_bridge.config(fg=GREEN)
+ self._lbl_bridge.config(text="起動中", fg=GREEN)
+ else:
+ self._dot_bridge.config(fg=RED)
+ self._lbl_bridge.config(text="停止中", fg=RED)
+
+ # ボタン状態
+ running = (self._proc_fooocus is not None and
+ self._proc_fooocus.poll() is None)
+ self._btn_start.config(state="disabled" if running else "normal")
+ self._btn_stop.config(state="normal" if running else "disabled")
+ self._btn_restart.config(state="normal" if running else "disabled")
+
+ self.after(2000, self._poll_status)
+
+ # ── 起動 ──────────────────────────────────────────────────────
+ def _start_all(self):
+ preset = self._preset_var.get()
+ browser = self._browser_var.get()
+ listen = self._listen_var.get()
+
+ cmd = build_fooocus_cmd(
+ preset = "" if preset == "なし" else preset,
+ in_browser = browser,
+ listen = listen
+ )
+
+ self._log_write(f"▶ 起動コマンド: {' '.join(cmd)}", "accent")
+
+ try:
+ self._proc_fooocus = subprocess.Popen(
+ cmd,
+ cwd=BASE_DIR,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ encoding="utf-8",
+ errors="replace",
+ bufsize=1
+ )
+ except FileNotFoundError as e:
+ self._log_write(f"[ERROR] Python が見つかりません: {e}", "err")
+ return
+
+ self._log_write(f"PID: {self._proc_fooocus.pid}", "info")
+ threading.Thread(target=self._stream_output,
+ args=(self._proc_fooocus,), daemon=True).start()
+
+ def _stream_output(self, proc: subprocess.Popen):
+ try:
+ for line in proc.stdout:
+ line = line.rstrip("\n")
+ if not line:
+ continue
+ low = line.lower()
+ if any(k in low for k in ("error", "exception", "traceback", "failed")):
+ tag = "err"
+ elif any(k in low for k in ("warning", "warn")):
+ tag = "warn"
+ elif any(k in low for k in ("running on", "http://", "startup", "ready", "started")):
+ tag = "ok"
+ else:
+ tag = "info"
+ self._queue_log(line, tag)
+ except Exception:
+ pass
+ self._queue_log("── プロセス終了 ──", "warn")
+
+ # ── 停止 ──────────────────────────────────────────────────────
+ def _stop_all(self):
+ if self._proc_fooocus and self._proc_fooocus.poll() is None:
+ self._log_write("■ 停止中...", "warn")
+ try:
+ self._proc_fooocus.terminate()
+ try:
+ self._proc_fooocus.wait(timeout=8)
+ except subprocess.TimeoutExpired:
+ self._proc_fooocus.kill()
+ self._log_write("停止しました。", "ok")
+ except Exception as e:
+ self._log_write(f"[ERROR] 停止失敗: {e}", "err")
+ self._proc_fooocus = None
+
+ # ── 再起動 ────────────────────────────────────────────────────
+ def _restart_all(self):
+ self._log_write("↺ 再起動中...", "accent")
+ self._stop_all()
+ time.sleep(1)
+ self._start_all()
+
+ # ── 終了 ──────────────────────────────────────────────────────
+ def _on_close(self):
+ if (self._proc_fooocus and self._proc_fooocus.poll() is None):
+ if messagebox.askyesno(
+ "確認",
+ "Fooocus が起動中です。終了しますか?\n(プロセスも停止されます)",
+ parent=self
+ ):
+ self._stop_all()
+ else:
+ return
+ self.destroy()
+
+
+# ── エントリーポイント ─────────────────────────────────────────────
+if __name__ == "__main__":
+ app = LauncherApp()
+ # ウィンドウを画面中央に
+ app.update_idletasks()
+ w, h = 700, 580
+ sw = app.winfo_screenwidth()
+ sh = app.winfo_screenheight()
+ x = (sw - w) // 2
+ y = (sh - h) // 2
+ app.geometry(f"{w}x{h}+{x}+{y}")
+ app.mainloop()
diff --git a/ui_settings.json b/ui_settings.json
new file mode 100644
index 00000000..197d693b
--- /dev/null
+++ b/ui_settings.json
@@ -0,0 +1,3 @@
+{
+ "path_outputs": "E:\\マイドライブ\\AI画像入れ"
+}
\ No newline at end of file
diff --git a/user_setting_presets.json b/user_setting_presets.json
new file mode 100644
index 00000000..e8b46a15
--- /dev/null
+++ b/user_setting_presets.json
@@ -0,0 +1,30 @@
+{
+ "アルビノ": {
+ "prompt": "white skin,clause up,__pf_angle__,masterpiece, high score, great score, absurdres, very aesthetic, smooth hair, neat hair, tidy hair, clean lineart, crisp detail, official art, highres, well-groomed hair, sleek hair, perfect hair, styled hair, hair in place, pure white background, simple background, white background, 1girl, looking at viewer, sensitive,red eyes, distinct pupils, detailed eyes, beautiful eyes, half-closed eyes, white skin, albino, porcelain skin, enigmatic, mysterious, distant, gloomy, white hair, __pf_hairstyle__,detailed white eyelashes,__pf_fetish__,__pf_costume_casual__",
+ "negative_prompt": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digits, fewer digits, cropped, worst quality, low quality, low score, bad score, average score, signature, watermark, username, blurry, sketch, doodle, rough draft, messy lines, rough lines, traditional media, watercolor, pencil, background, scenery, building, nature, detailed background, head out of frame, no sclera, colored sclera, solid eyes, frizzy hair, messy hair, flyaway hair, bad hair day, ahoge, stray hair, unruly hair, wild hair,sunmissing_limb,lace fabric,background,bad eyes, deformed eyes, thick eyelashes, clumped eyelashes, fused eyelashes, solid eyelashes, heavy eyelashes, messy eyelashes, overly dramatic eyelashes, clumpy mascara",
+ "styles": "[]",
+ "performance": "Quality",
+ "resolution": "(1024, 1024)",
+ "image_number": 1,
+ "guidance_scale": 6.0,
+ "sharpness": 2.0,
+ "base_model": "animagineXL40_v4Opt.safetensors",
+ "refiner_model": "None",
+ "refiner_switch": 0.5,
+ "sampler": "dpmpp_2m_sde_gpu",
+ "scheduler": "karras",
+ "vae": "Default (model)",
+ "adm_guidance": "(1.5, 0.8, 0.3)",
+ "refiner_swap_method": "joint",
+ "adaptive_cfg": 7.0,
+ "clip_skip": 2,
+ "input_image_enabled": false,
+ "enhance_enabled": true,
+ "uov_method": "Disabled",
+ "lora_combined_1": "True : add-detail-xl.safetensors : 0.58",
+ "lora_combined_2": "True : SDXL_FILM_PHOTOGRAPHY_STYLE_V1.safetensors : 0.38",
+ "lora_combined_3": "True : sexy_details_v4.safetensors : 0.22",
+ "lora_combined_4": "True : None : 1",
+ "lora_combined_5": "True : None : 1"
+ }
+}
\ No newline at end of file
diff --git a/webui.py b/webui.py
index b8159d85..073070f2 100644
--- a/webui.py
+++ b/webui.py
@@ -24,6 +24,66 @@ from modules.ui_gradio_extensions import reload_javascript
from modules.auth import auth_enabled, check_auth
from modules.util import is_json
+# ──────────────────────────────────────────────────────────────────
+# UI Settings (保存先など永続設定)
+# ──────────────────────────────────────────────────────────────────
+_UI_SETTINGS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ui_settings.json')
+
+
+def _ui_cfg_load() -> dict:
+ if os.path.exists(_UI_SETTINGS_PATH):
+ try:
+ with open(_UI_SETTINGS_PATH, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ except Exception:
+ pass
+ return {}
+
+
+def _ui_cfg_save(d: dict):
+ with open(_UI_SETTINGS_PATH, 'w', encoding='utf-8') as f:
+ json.dump(d, f, ensure_ascii=False, indent=2)
+
+
+# 起動時に保存先を復元
+_ui_cfg = _ui_cfg_load()
+if 'path_outputs' in _ui_cfg:
+ import modules.config as _cfg_mod
+ _cfg_mod.path_outputs = _ui_cfg['path_outputs']
+ os.makedirs(_ui_cfg['path_outputs'], exist_ok=True)
+
+
+# ──────────────────────────────────────────────────────────────────
+# User Setting Presets helpers
+# ──────────────────────────────────────────────────────────────────
+_USER_SP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user_setting_presets.json')
+
+
+def _sp_load() -> dict:
+ if os.path.exists(_USER_SP_PATH):
+ try:
+ with open(_USER_SP_PATH, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ except Exception:
+ pass
+ return {}
+
+
+def _sp_save(presets: dict):
+ with open(_USER_SP_PATH, 'w', encoding='utf-8') as f:
+ json.dump(presets, f, ensure_ascii=False, indent=2)
+
+
+def _sp_aspect_to_resolution(aspect_label: str) -> str:
+ """'1024×1024 | 1:1' → '(1024, 1024)'"""
+ try:
+ part = aspect_label.split('|')[0].strip()
+ w, h = part.split('×')
+ return f'({int(w.strip())}, {int(h.strip())})'
+ except Exception:
+ return '(1024, 1024)'
+
+
def get_task(*args):
args = list(args)
args.pop(0)
@@ -35,7 +95,7 @@ def generate_clicked(task: worker.AsyncTask):
with model_management.interrupt_processing_mutex:
model_management.interrupt_processing = False
- # outputs=[progress_html, progress_window, progress_gallery, gallery]
+ # outputs=[progress_html, progress_window, progress_gallery, gallery, gallery_paths]
if len(task.args) == 0:
return
@@ -46,7 +106,8 @@ def generate_clicked(task: worker.AsyncTask):
yield gr.update(visible=True, value=modules.html.make_progress_html(1, 'Waiting for task to start ...')), \
gr.update(visible=True, value=None), \
gr.update(visible=False, value=None), \
- gr.update(visible=False)
+ gr.update(visible=False), \
+ gr.update()
worker.async_tasks.append(task)
@@ -66,12 +127,14 @@ def generate_clicked(task: worker.AsyncTask):
yield gr.update(visible=True, value=modules.html.make_progress_html(percentage, title)), \
gr.update(visible=True, value=image) if image is not None else gr.update(), \
gr.update(), \
- gr.update(visible=False)
+ gr.update(visible=False), \
+ gr.update()
if flag == 'results':
yield gr.update(visible=True), \
gr.update(visible=True), \
gr.update(visible=True, value=product), \
- gr.update(visible=False)
+ gr.update(visible=False), \
+ gr.update()
if flag == 'finish':
if not args_manager.args.disable_enhance_output_sorting:
product = sort_enhance_images(product, task)
@@ -79,7 +142,8 @@ def generate_clicked(task: worker.AsyncTask):
yield gr.update(visible=False), \
gr.update(visible=False), \
gr.update(visible=False), \
- gr.update(visible=True, value=product)
+ gr.update(visible=True, value=product), \
+ product
finished = True
# delete Fooocus temp images, only keep gradio temp images
@@ -158,18 +222,64 @@ with shared.gradio_root:
with gr.Row():
with gr.Column(scale=2):
with gr.Row():
- progress_window = grh.Image(label='Preview', show_label=True, visible=False, height=768,
+ progress_window = grh.Image(label='プレビュー', show_label=True, visible=False, height=768,
elem_classes=['main_view'])
- progress_gallery = gr.Gallery(label='Finished Images', show_label=True, object_fit='contain',
+ progress_gallery = gr.Gallery(label='生成完了', show_label=True, object_fit='contain',
height=768, visible=False, elem_classes=['main_view', 'image_gallery'])
progress_html = gr.HTML(value=modules.html.make_progress_html(32, 'Progress 32%'), visible=False,
elem_id='progress-bar', elem_classes='progress-bar')
- gallery = gr.Gallery(label='Gallery', show_label=False, object_fit='contain', visible=True, height=768,
+ gallery = gr.Gallery(label='ギャラリー', show_label=False, object_fit='contain', visible=True, height=768,
elem_classes=['resizable_area', 'main_view', 'final_gallery', 'image_gallery'],
elem_id='final_gallery')
+
+ # ── ブラッシュアップ & OpenCV エディットパネル ──
+ brushup_selected = gr.State(None)
+ brushup_original = gr.State(None)
+ gallery_paths = gr.State([])
+ with gr.Row(elem_id='brushup_bar'):
+ with gr.Column(scale=0, min_width=114, elem_classes='brushup_btn_col', visible=False) as brushup_btn_col:
+ brushup_subtle_btn = gr.Button('🎨 微調整', variant='secondary', min_width=0)
+ brushup_strong_btn = gr.Button('🎭 強変更', variant='secondary', min_width=0)
+ brushup_upscale_btn = gr.Button('🔍 ×2拡大', variant='secondary', min_width=0)
+ brushup_gen_btn = gr.Button('⚡ 変更して生成', variant='primary', min_width=0)
+ with gr.Column(scale=1):
+ brushup_status_html = gr.HTML(
+ value=''
+ '💡 ギャラリーの画像をクリックして選択 → ブラッシュアップ / 編集',
+ elem_id='brushup_status_html')
+ with gr.Row(visible=False, elem_id='edit_panel') as edit_panel:
+ with gr.Column(scale=1):
+ # ── プリセット ──
+ with gr.Row(elem_id='edit_presets'):
+ edit_preset_vivid = gr.Button('🔆 ビビッド', min_width=0, elem_classes='preset_btn')
+ edit_preset_cinema = gr.Button('🎬 シネマ', min_width=0, elem_classes='preset_btn')
+ edit_preset_warm = gr.Button('🌅 ウォーム', min_width=0, elem_classes='preset_btn')
+ edit_preset_cool = gr.Button('❄️ クール', min_width=0, elem_classes='preset_btn')
+ edit_preset_soft = gr.Button('🌫️ ソフト', min_width=0, elem_classes='preset_btn')
+ # ── スライダー ──
+ edit_brightness = gr.Slider(label='明るさ', minimum=-100, maximum=100, value=0, step=1)
+ edit_contrast = gr.Slider(label='コントラスト', minimum=0.1, maximum=3.0, value=1.0, step=0.05)
+ edit_saturation = gr.Slider(label='彩度', minimum=0.0, maximum=3.0, value=1.0, step=0.05)
+ edit_hue = gr.Slider(label='色相シフト', minimum=-90, maximum=90, value=0, step=1)
+ edit_temperature = gr.Slider(label='色温度 ❄→🌅', minimum=-100, maximum=100, value=0, step=1)
+ edit_sharpness = gr.Slider(label='シャープネス', minimum=0.0, maximum=3.0, value=1.0, step=0.1)
+ # ── 回転・反転 ──
+ with gr.Row():
+ edit_rot_l = gr.Button('↺ 左90°', min_width=0)
+ edit_rot_r = gr.Button('↻ 右90°', min_width=0)
+ edit_flip_h = gr.Button('↔ 水平反転', min_width=0)
+ edit_flip_v = gr.Button('↕ 垂直反転', min_width=0)
+ # ── アクション ──
+ with gr.Row():
+ edit_reset_btn = gr.Button('🔄 リセット', min_width=0)
+ edit_apply_btn = gr.Button('✓ 適用 (選択更新)', variant='primary', min_width=0)
+ with gr.Column(scale=1):
+ edit_preview = gr.Image(label='編集プレビュー', type='numpy', interactive=False, height=340)
+ edit_ba_btn = gr.Button('🔍 元画像を確認 (B/A)', min_width=0)
+
with gr.Row():
with gr.Column(scale=17):
- prompt = gr.Textbox(show_label=False, placeholder="Type prompt here or paste parameters.", elem_id='positive_prompt',
+ prompt = gr.Textbox(show_label=False, placeholder="プロンプトを入力、またはパラメータを貼り付け", elem_id='positive_prompt',
autofocus=True, lines=3)
default_prompt = modules.config.default_prompt
@@ -177,11 +287,11 @@ with shared.gradio_root:
shared.gradio_root.load(lambda: default_prompt, outputs=prompt)
with gr.Column(scale=3, min_width=0):
- generate_button = gr.Button(label="Generate", value="Generate", elem_classes='type_row', elem_id='generate_button', visible=True)
- reset_button = gr.Button(label="Reconnect", value="Reconnect", elem_classes='type_row', elem_id='reset_button', visible=False)
- load_parameter_button = gr.Button(label="Load Parameters", value="Load Parameters", elem_classes='type_row', elem_id='load_parameter_button', visible=False)
- skip_button = gr.Button(label="Skip", value="Skip", elem_classes='type_row_half', elem_id='skip_button', visible=False)
- stop_button = gr.Button(label="Stop", value="Stop", elem_classes='type_row_half', elem_id='stop_button', visible=False)
+ generate_button = gr.Button(label="生成", value="生成", elem_classes='type_row', elem_id='generate_button', visible=True)
+ reset_button = gr.Button(label="再接続", value="再接続", elem_classes='type_row', elem_id='reset_button', visible=False)
+ load_parameter_button = gr.Button(label="パラメータ読込", value="パラメータ読込", elem_classes='type_row', elem_id='load_parameter_button', visible=False)
+ skip_button = gr.Button(label="スキップ", value="スキップ", elem_classes='type_row_half', elem_id='skip_button', visible=False)
+ stop_button = gr.Button(label="停止", value="停止", elem_classes='type_row_half', elem_id='stop_button', visible=False)
def stop_clicked(currentTask):
import ldm_patched.modules.model_management as model_management
@@ -199,20 +309,47 @@ with shared.gradio_root:
stop_button.click(stop_clicked, inputs=currentTask, outputs=currentTask, queue=False, show_progress=False, _js='cancelGenerateForever')
skip_button.click(skip_clicked, inputs=currentTask, outputs=currentTask, queue=False, show_progress=False)
+ with gr.Accordion('🔮 Prompt Forge — タグ選択&翻訳', open=False, elem_id='pf_accordion'):
+ gr.HTML(value=(
+ ''
+ '
'
+ '
'
+ '
Prompt Forge を読み込み中...'
+ '
'
+ ' '
+ '
'
+ '
'
+ ''
+ ))
with gr.Row(elem_classes='advanced_check_row'):
- input_image_checkbox = gr.Checkbox(label='Input Image', value=modules.config.default_image_prompt_checkbox, container=False, elem_classes='min_check')
- enhance_checkbox = gr.Checkbox(label='Enhance', value=modules.config.default_enhance_checkbox, container=False, elem_classes='min_check')
- advanced_checkbox = gr.Checkbox(label='Advanced', value=modules.config.default_advanced_checkbox, container=False, elem_classes='min_check')
+ input_image_checkbox = gr.Checkbox(label='入力画像', value=modules.config.default_image_prompt_checkbox, container=False, elem_classes='min_check')
+ enhance_checkbox = gr.Checkbox(label='強化', value=modules.config.default_enhance_checkbox, container=False, elem_classes='min_check')
+ advanced_checkbox = gr.Checkbox(label='詳細設定', value=modules.config.default_advanced_checkbox, container=False, elem_classes='min_check')
with gr.Row(visible=modules.config.default_image_prompt_checkbox) as image_input_panel:
- with gr.Tabs(selected=modules.config.default_selected_image_input_tab_id):
- with gr.Tab(label='Upscale or Variation', id='uov_tab') as uov_tab:
+ with gr.Tabs(selected=modules.config.default_selected_image_input_tab_id) as image_input_tabs:
+ with gr.Tab(label='拡大・バリエーション', id='uov_tab') as uov_tab:
with gr.Row():
with gr.Column():
- uov_input_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False)
+ uov_input_image = grh.Image(label='画像', source='upload', type='numpy', show_label=False)
with gr.Column():
- uov_method = gr.Radio(label='Upscale or Variation:', choices=flags.uov_list, value=modules.config.default_uov_method)
- gr.HTML('\U0001F4D4 Documentation')
- with gr.Tab(label='Image Prompt', id='ip_tab') as ip_tab:
+ uov_method = gr.Radio(label='拡大またはバリエーション:', choices=flags.uov_list, value=modules.config.default_uov_method)
+ gr.HTML('📖 ドキュメント')
+ with gr.Tab(label='画像プロンプト', id='ip_tab') as ip_tab:
with gr.Row():
ip_images = []
ip_types = []
@@ -223,27 +360,27 @@ with shared.gradio_root:
for image_count in range(modules.config.default_controlnet_image_count):
image_count += 1
with gr.Column():
- ip_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False, height=300, value=modules.config.default_ip_images[image_count])
+ ip_image = grh.Image(label='画像', source='upload', type='numpy', show_label=False, height=300, value=modules.config.default_ip_images[image_count])
ip_images.append(ip_image)
ip_ctrls.append(ip_image)
with gr.Column(visible=modules.config.default_image_prompt_advanced_checkbox) as ad_col:
with gr.Row():
- ip_stop = gr.Slider(label='Stop At', minimum=0.0, maximum=1.0, step=0.001, value=modules.config.default_ip_stop_ats[image_count])
+ ip_stop = gr.Slider(label='停止位置', minimum=0.0, maximum=1.0, step=0.001, value=modules.config.default_ip_stop_ats[image_count])
ip_stops.append(ip_stop)
ip_ctrls.append(ip_stop)
- ip_weight = gr.Slider(label='Weight', minimum=0.0, maximum=2.0, step=0.001, value=modules.config.default_ip_weights[image_count])
+ ip_weight = gr.Slider(label='ウェイト', minimum=0.0, maximum=2.0, step=0.001, value=modules.config.default_ip_weights[image_count])
ip_weights.append(ip_weight)
ip_ctrls.append(ip_weight)
- ip_type = gr.Radio(label='Type', choices=flags.ip_list, value=modules.config.default_ip_types[image_count], container=False)
+ ip_type = gr.Radio(label='タイプ', choices=flags.ip_list, value=modules.config.default_ip_types[image_count], container=False)
ip_types.append(ip_type)
ip_ctrls.append(ip_type)
ip_type.change(lambda x: flags.default_parameters[x], inputs=[ip_type], outputs=[ip_stop, ip_weight], queue=False, show_progress=False)
ip_ad_cols.append(ad_col)
- ip_advanced = gr.Checkbox(label='Advanced', value=modules.config.default_image_prompt_advanced_checkbox, container=False)
- gr.HTML('* \"Image Prompt\" is powered by Fooocus Image Mixture Engine (v1.0.1). \U0001F4D4 Documentation')
+ ip_advanced = gr.Checkbox(label='詳細設定', value=modules.config.default_image_prompt_advanced_checkbox, container=False)
+ gr.HTML('* 「画像プロンプト」は Fooocus Image Mixture Engine (v1.0.1) で動作しています。📖 ドキュメント')
def ip_advance_checked(x):
return [gr.update(visible=x)] * len(ip_ad_cols) + \
@@ -255,35 +392,35 @@ with shared.gradio_root:
outputs=ip_ad_cols + ip_types + ip_stops + ip_weights,
queue=False, show_progress=False)
- with gr.Tab(label='Inpaint or Outpaint', id='inpaint_tab') as inpaint_tab:
+ with gr.Tab(label='インペイント・アウトペイント', id='inpaint_tab') as inpaint_tab:
with gr.Row():
with gr.Column():
- inpaint_input_image = grh.Image(label='Image', source='upload', type='numpy', tool='sketch', height=500, brush_color="#FFFFFF", elem_id='inpaint_canvas', show_label=False)
- inpaint_advanced_masking_checkbox = gr.Checkbox(label='Enable Advanced Masking Features', value=modules.config.default_inpaint_advanced_masking_checkbox)
- inpaint_mode = gr.Dropdown(choices=modules.flags.inpaint_options, value=modules.config.default_inpaint_method, label='Method')
- inpaint_additional_prompt = gr.Textbox(placeholder="Describe what you want to inpaint.", elem_id='inpaint_additional_prompt', label='Inpaint Additional Prompt', visible=False)
- outpaint_selections = gr.CheckboxGroup(choices=['Left', 'Right', 'Top', 'Bottom'], value=[], label='Outpaint Direction')
+ inpaint_input_image = grh.Image(label='画像', source='upload', type='numpy', tool='sketch', height=500, brush_color="#FFFFFF", elem_id='inpaint_canvas', show_label=False)
+ inpaint_advanced_masking_checkbox = gr.Checkbox(label='高度マスキング機能を有効化', value=modules.config.default_inpaint_advanced_masking_checkbox)
+ inpaint_mode = gr.Dropdown(choices=modules.flags.inpaint_options, value=modules.config.default_inpaint_method, label='方式')
+ inpaint_additional_prompt = gr.Textbox(placeholder="インペイントしたい内容を入力", elem_id='inpaint_additional_prompt', label='インペイント追加プロンプト', visible=False)
+ outpaint_selections = gr.CheckboxGroup(choices=['Left', 'Right', 'Top', 'Bottom'], value=[], label='アウトペイント方向')
example_inpaint_prompts = gr.Dataset(samples=modules.config.example_inpaint_prompts,
- label='Additional Prompt Quick List',
+ label='追加プロンプト クイックリスト',
components=[inpaint_additional_prompt],
visible=False)
- gr.HTML('* Powered by Fooocus Inpaint Engine \U0001F4D4 Documentation')
+ gr.HTML('* Fooocus Inpaint Engine で動作しています。📖 ドキュメント')
example_inpaint_prompts.click(lambda x: x[0], inputs=example_inpaint_prompts, outputs=inpaint_additional_prompt, show_progress=False, queue=False)
with gr.Column(visible=modules.config.default_inpaint_advanced_masking_checkbox) as inpaint_mask_generation_col:
- inpaint_mask_image = grh.Image(label='Mask Upload', source='upload', type='numpy', tool='sketch', height=500, brush_color="#FFFFFF", mask_opacity=1, elem_id='inpaint_mask_canvas')
- invert_mask_checkbox = gr.Checkbox(label='Invert Mask When Generating', value=modules.config.default_invert_mask_checkbox)
- inpaint_mask_model = gr.Dropdown(label='Mask generation model',
+ inpaint_mask_image = grh.Image(label='マスクアップロード', source='upload', type='numpy', tool='sketch', height=500, brush_color="#FFFFFF", mask_opacity=1, elem_id='inpaint_mask_canvas')
+ invert_mask_checkbox = gr.Checkbox(label='生成時にマスクを反転', value=modules.config.default_invert_mask_checkbox)
+ inpaint_mask_model = gr.Dropdown(label='マスク生成モデル',
choices=flags.inpaint_mask_models,
value=modules.config.default_inpaint_mask_model)
- inpaint_mask_cloth_category = gr.Dropdown(label='Cloth category',
+ inpaint_mask_cloth_category = gr.Dropdown(label='衣類カテゴリー',
choices=flags.inpaint_mask_cloth_category,
value=modules.config.default_inpaint_mask_cloth_category,
visible=False)
- inpaint_mask_dino_prompt_text = gr.Textbox(label='Detection prompt', value='', visible=False, info='Use singular whenever possible', placeholder='Describe what you want to detect.')
+ inpaint_mask_dino_prompt_text = gr.Textbox(label='検出プロンプト', value='', visible=False, info='できるだけ単数形で入力してください', placeholder='検出したい対象を入力')
example_inpaint_mask_dino_prompt_text = gr.Dataset(
samples=modules.config.example_enhance_detection_prompts,
- label='Detection Prompt Quick List',
+ label='検出プロンプト クイックリスト',
components=[inpaint_mask_dino_prompt_text],
visible=modules.config.default_inpaint_mask_model == 'sam')
example_inpaint_mask_dino_prompt_text.click(lambda x: x[0],
@@ -291,12 +428,12 @@ with shared.gradio_root:
outputs=inpaint_mask_dino_prompt_text,
show_progress=False, queue=False)
- with gr.Accordion("Advanced options", visible=False, open=False) as inpaint_mask_advanced_options:
- inpaint_mask_sam_model = gr.Dropdown(label='SAM model', choices=flags.inpaint_mask_sam_model, value=modules.config.default_inpaint_mask_sam_model)
- inpaint_mask_box_threshold = gr.Slider(label="Box Threshold", minimum=0.0, maximum=1.0, value=0.3, step=0.05)
- inpaint_mask_text_threshold = gr.Slider(label="Text Threshold", minimum=0.0, maximum=1.0, value=0.25, step=0.05)
- inpaint_mask_sam_max_detections = gr.Slider(label="Maximum number of detections", info="Set to 0 to detect all", minimum=0, maximum=10, value=modules.config.default_sam_max_detections, step=1, interactive=True)
- generate_mask_button = gr.Button(value='Generate mask from image')
+ with gr.Accordion("詳細オプション", visible=False, open=False) as inpaint_mask_advanced_options:
+ inpaint_mask_sam_model = gr.Dropdown(label='SAM モデル', choices=flags.inpaint_mask_sam_model, value=modules.config.default_inpaint_mask_sam_model)
+ inpaint_mask_box_threshold = gr.Slider(label="ボックス閾値", minimum=0.0, maximum=1.0, value=0.3, step=0.05)
+ inpaint_mask_text_threshold = gr.Slider(label="テキスト閾値", minimum=0.0, maximum=1.0, value=0.25, step=0.05)
+ inpaint_mask_sam_max_detections = gr.Slider(label="最大検出数", info="0 に設定するとすべて検出", minimum=0, maximum=10, value=modules.config.default_sam_max_detections, step=1, interactive=True)
+ generate_mask_button = gr.Button(value='画像からマスクを生成')
def generate_mask(image, mask_model, cloth_category, dino_prompt_text, sam_model, box_threshold, text_threshold, sam_max_detections, dino_erode_or_dilate, dino_debug):
from extras.inpaint_mask import generate_mask_from_image
@@ -332,19 +469,19 @@ with shared.gradio_root:
example_inpaint_mask_dino_prompt_text],
queue=False, show_progress=False)
- with gr.Tab(label='Describe', id='describe_tab') as describe_tab:
+ with gr.Tab(label='画像解析', id='describe_tab') as describe_tab:
with gr.Row():
with gr.Column():
- describe_input_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False)
+ describe_input_image = grh.Image(label='画像', source='upload', type='numpy', show_label=False)
with gr.Column():
describe_methods = gr.CheckboxGroup(
- label='Content Type',
+ label='コンテンツタイプ',
choices=flags.describe_types,
value=modules.config.default_describe_content_type)
- describe_apply_styles = gr.Checkbox(label='Apply Styles', value=modules.config.default_describe_apply_prompts_checkbox)
- describe_btn = gr.Button(value='Describe this Image into Prompt')
- describe_image_size = gr.Textbox(label='Image Size and Recommended Size', elem_id='describe_image_size', visible=False)
- gr.HTML('\U0001F4D4 Documentation')
+ describe_apply_styles = gr.Checkbox(label='スタイルを適用', value=modules.config.default_describe_apply_prompts_checkbox)
+ describe_btn = gr.Button(value='画像をプロンプトに変換')
+ describe_image_size = gr.Textbox(label='画像サイズと推奨サイズ', elem_id='describe_image_size', visible=False)
+ gr.HTML('📖 ドキュメント')
def trigger_show_image_properties(image):
value = modules.util.get_image_size_info(image, modules.flags.sdxl_aspect_ratios)
@@ -353,17 +490,17 @@ with shared.gradio_root:
describe_input_image.upload(trigger_show_image_properties, inputs=describe_input_image,
outputs=describe_image_size, show_progress=False, queue=False)
- with gr.Tab(label='Enhance', id='enhance_tab') as enhance_tab:
+ with gr.Tab(label='強化', id='enhance_tab') as enhance_tab:
with gr.Row():
with gr.Column():
- enhance_input_image = grh.Image(label='Use with Enhance, skips image generation', source='upload', type='numpy')
- gr.HTML('\U0001F4D4 Documentation')
+ enhance_input_image = grh.Image(label='強化モード用(画像生成をスキップ)', source='upload', type='numpy')
+ gr.HTML('📖 ドキュメント')
- with gr.Tab(label='Metadata', id='metadata_tab') as metadata_tab:
+ with gr.Tab(label='メタデータ', id='metadata_tab') as metadata_tab:
with gr.Column():
- metadata_input_image = grh.Image(label='For images created by Fooocus', source='upload', type='pil')
- metadata_json = gr.JSON(label='Metadata')
- metadata_import_button = gr.Button(value='Apply Metadata')
+ metadata_input_image = grh.Image(label='Fooocus で作成した画像を読み込む', source='upload', type='pil')
+ metadata_json = gr.JSON(label='メタデータ')
+ metadata_import_button = gr.Button(value='メタデータを適用')
def trigger_metadata_preview(file):
parameters, metadata_scheme = modules.meta_parser.read_info_from_image(file)
@@ -382,17 +519,17 @@ with shared.gradio_root:
with gr.Row(visible=modules.config.default_enhance_checkbox) as enhance_input_panel:
with gr.Tabs():
- with gr.Tab(label='Upscale or Variation'):
+ with gr.Tab(label='拡大・バリエーション'):
with gr.Row():
with gr.Column():
- enhance_uov_method = gr.Radio(label='Upscale or Variation:', choices=flags.uov_list,
+ enhance_uov_method = gr.Radio(label='拡大またはバリエーション:', choices=flags.uov_list,
value=modules.config.default_enhance_uov_method)
- enhance_uov_processing_order = gr.Radio(label='Order of Processing',
- info='Use before to enhance small details and after to enhance large areas.',
+ enhance_uov_processing_order = gr.Radio(label='処理順序',
+ info='「前」は細部の強化、「後」は広い領域の強化に使用します。',
choices=flags.enhancement_uov_processing_order,
value=modules.config.default_enhance_uov_processing_order)
- enhance_uov_prompt_type = gr.Radio(label='Prompt',
- info='Choose which prompt to use for Upscale or Variation.',
+ enhance_uov_prompt_type = gr.Radio(label='プロンプト',
+ info='拡大またはバリエーションに使用するプロンプトを選択します。',
choices=flags.enhancement_uov_prompt_types,
value=modules.config.default_enhance_uov_prompt_type,
visible=modules.config.default_enhance_uov_processing_order == flags.enhancement_uov_after)
@@ -401,24 +538,24 @@ with shared.gradio_root:
inputs=enhance_uov_processing_order,
outputs=enhance_uov_prompt_type,
queue=False, show_progress=False)
- gr.HTML('\U0001F4D4 Documentation')
+ gr.HTML('📖 ドキュメント')
enhance_ctrls = []
enhance_inpaint_mode_ctrls = []
enhance_inpaint_engine_ctrls = []
enhance_inpaint_update_ctrls = []
for index in range(modules.config.default_enhance_tabs):
with gr.Tab(label=f'#{index + 1}') as enhance_tab_item:
- enhance_enabled = gr.Checkbox(label='Enable', value=False, elem_classes='min_check',
+ enhance_enabled = gr.Checkbox(label='有効化', value=False, elem_classes='min_check',
container=False)
- enhance_mask_dino_prompt_text = gr.Textbox(label='Detection prompt',
- info='Use singular whenever possible',
- placeholder='Describe what you want to detect.',
+ enhance_mask_dino_prompt_text = gr.Textbox(label='検出プロンプト',
+ info='できるだけ単数形で入力してください',
+ placeholder='検出したい対象を入力',
interactive=True,
visible=modules.config.default_enhance_inpaint_mask_model == 'sam')
example_enhance_mask_dino_prompt_text = gr.Dataset(
samples=modules.config.example_enhance_detection_prompts,
- label='Detection Prompt Quick List',
+ label='検出プロンプト クイックリスト',
components=[enhance_mask_dino_prompt_text],
visible=modules.config.default_enhance_inpaint_mask_model == 'sam')
example_enhance_mask_dino_prompt_text.click(lambda x: x[0],
@@ -426,74 +563,73 @@ with shared.gradio_root:
outputs=enhance_mask_dino_prompt_text,
show_progress=False, queue=False)
- enhance_prompt = gr.Textbox(label="Enhancement positive prompt",
- placeholder="Uses original prompt instead if empty.",
+ enhance_prompt = gr.Textbox(label="強化用ポジティブプロンプト",
+ placeholder="空欄の場合は元のプロンプトを使用します。",
elem_id='enhance_prompt')
- enhance_negative_prompt = gr.Textbox(label="Enhancement negative prompt",
- placeholder="Uses original negative prompt instead if empty.",
+ enhance_negative_prompt = gr.Textbox(label="強化用ネガティブプロンプト",
+ placeholder="空欄の場合は元のネガティブプロンプトを使用します。",
elem_id='enhance_negative_prompt')
- with gr.Accordion("Detection", open=False):
- enhance_mask_model = gr.Dropdown(label='Mask generation model',
+ with gr.Accordion("検出", open=False):
+ enhance_mask_model = gr.Dropdown(label='マスク生成モデル',
choices=flags.inpaint_mask_models,
value=modules.config.default_enhance_inpaint_mask_model)
- enhance_mask_cloth_category = gr.Dropdown(label='Cloth category',
+ enhance_mask_cloth_category = gr.Dropdown(label='衣類カテゴリー',
choices=flags.inpaint_mask_cloth_category,
value=modules.config.default_inpaint_mask_cloth_category,
visible=modules.config.default_enhance_inpaint_mask_model == 'u2net_cloth_seg',
interactive=True)
- with gr.Accordion("SAM Options",
+ with gr.Accordion("SAM オプション",
visible=modules.config.default_enhance_inpaint_mask_model == 'sam',
open=False) as sam_options:
- enhance_mask_sam_model = gr.Dropdown(label='SAM model',
+ enhance_mask_sam_model = gr.Dropdown(label='SAM モデル',
choices=flags.inpaint_mask_sam_model,
value=modules.config.default_inpaint_mask_sam_model,
interactive=True)
- enhance_mask_box_threshold = gr.Slider(label="Box Threshold", minimum=0.0,
+ enhance_mask_box_threshold = gr.Slider(label="ボックス閾値", minimum=0.0,
maximum=1.0, value=0.3, step=0.05,
interactive=True)
- enhance_mask_text_threshold = gr.Slider(label="Text Threshold", minimum=0.0,
+ enhance_mask_text_threshold = gr.Slider(label="テキスト閾値", minimum=0.0,
maximum=1.0, value=0.25, step=0.05,
interactive=True)
- enhance_mask_sam_max_detections = gr.Slider(label="Maximum number of detections",
- info="Set to 0 to detect all",
+ enhance_mask_sam_max_detections = gr.Slider(label="最大検出数",
+ info="0 に設定するとすべて検出",
minimum=0, maximum=10,
value=modules.config.default_sam_max_detections,
step=1, interactive=True)
- with gr.Accordion("Inpaint", visible=True, open=False):
+ with gr.Accordion("インペイント", visible=True, open=False):
enhance_inpaint_mode = gr.Dropdown(choices=modules.flags.inpaint_options,
value=modules.config.default_inpaint_method,
- label='Method', interactive=True)
+ label='方式', interactive=True)
enhance_inpaint_disable_initial_latent = gr.Checkbox(
- label='Disable initial latent in inpaint', value=False)
- enhance_inpaint_engine = gr.Dropdown(label='Inpaint Engine',
+ label='インペイントの初期潜在を無効化', value=False)
+ enhance_inpaint_engine = gr.Dropdown(label='インペイントエンジン',
value=modules.config.default_inpaint_engine_version,
choices=flags.inpaint_engine_versions,
- info='Version of Fooocus inpaint model. If set, use performance Quality or Speed (no performance LoRAs) for best results.')
- enhance_inpaint_strength = gr.Slider(label='Inpaint Denoising Strength',
+ info='Fooocus インペイントモデルのバージョン。Quality または Speed を推奨。')
+ enhance_inpaint_strength = gr.Slider(label='インペイント デノイズ強度',
minimum=0.0, maximum=1.0, step=0.001,
value=1.0,
- info='Same as the denoising strength in A1111 inpaint. '
- 'Only used in inpaint, not used in outpaint. '
- '(Outpaint always use 1.0)')
- enhance_inpaint_respective_field = gr.Slider(label='Inpaint Respective Field',
+ info='A1111 インペイントのデノイズ強度と同じです。'
+ 'インペイントのみに使用(アウトペイントには無効)。'
+ '(アウトペイントは常に 1.0)')
+ enhance_inpaint_respective_field = gr.Slider(label='インペイント対象範囲',
minimum=0.0, maximum=1.0, step=0.001,
value=0.618,
- info='The area to inpaint. '
- 'Value 0 is same as "Only Masked" in A1111. '
- 'Value 1 is same as "Whole Image" in A1111. '
- 'Only used in inpaint, not used in outpaint. '
- '(Outpaint always use 1.0)')
- enhance_inpaint_erode_or_dilate = gr.Slider(label='Mask Erode or Dilate',
+ info='インペイントする領域。'
+ '0 は A1111 の「マスクのみ」、'
+ '1 は「画像全体」と同じです。'
+ 'インペイントのみ有効(アウトペイントは常に 1.0)')
+ enhance_inpaint_erode_or_dilate = gr.Slider(label='マスク 侵食/膨張',
minimum=-64, maximum=64, step=1, value=0,
- info='Positive value will make white area in the mask larger, '
- 'negative value will make white area smaller. '
- '(default is 0, always processed before any mask invert)')
- enhance_mask_invert = gr.Checkbox(label='Invert Mask', value=False)
+ info='正の値はマスクの白い領域を拡大、'
+ '負の値は縮小します。'
+ '(デフォルト 0、マスク反転前に処理)')
+ enhance_mask_invert = gr.Checkbox(label='マスクを反転', value=False)
- gr.HTML('\U0001F4D4 Documentation')
+ gr.HTML('📖 ドキュメント')
enhance_ctrls += [
enhance_enabled,
@@ -556,40 +692,66 @@ with shared.gradio_root:
outputs=enhance_input_panel, queue=False, show_progress=False, _js=switch_js)
with gr.Column(scale=1, visible=modules.config.default_advanced_checkbox) as advanced_column:
- with gr.Tab(label='Settings'):
+ with gr.Tab(label='設定'):
if not args_manager.args.disable_preset_selection:
- preset_selection = gr.Dropdown(label='Preset',
+ preset_selection = gr.Dropdown(label='プリセット',
choices=modules.config.available_presets,
value=args_manager.args.preset if args_manager.args.preset else "initial",
interactive=True)
- performance_selection = gr.Radio(label='Performance',
+ # ── User Setting Presets UI ──
+ with gr.Accordion('⭐ 設定プリセット', open=False, elem_id='user_sp_accordion'):
+ # ── 保存先フォルダ ──
+ with gr.Row():
+ output_path_box = gr.Textbox(
+ label='📁 保存先フォルダ',
+ value=modules.config.path_outputs,
+ scale=5, interactive=True,
+ placeholder='例: C:/Users/xxx/Pictures/Fooocus')
+ output_path_btn = gr.Button('✓ 変更', variant='secondary', scale=1, min_width=0)
+ output_path_status = gr.Markdown(value='')
+ gr.HTML('
')
+ # ── プリセット操作 ──
+ with gr.Row():
+ user_sp_dropdown = gr.Dropdown(
+ label='保存済みプリセット',
+ choices=list(_sp_load().keys()),
+ value=None, scale=4, interactive=True)
+ user_sp_load_btn = gr.Button('📂 読込', variant='primary', scale=1, min_width=0)
+ user_sp_delete_btn = gr.Button('🗑️ 削除', variant='stop', scale=1, min_width=0)
+ with gr.Row():
+ user_sp_name = gr.Textbox(
+ label='保存名', placeholder='プリセット名を入力して保存...', scale=4)
+ user_sp_save_btn = gr.Button('💾 現在の設定を保存', variant='secondary', scale=2, min_width=0)
+ user_sp_status = gr.Markdown(value='')
+
+ performance_selection = gr.Radio(label='パフォーマンス',
choices=flags.Performance.values(),
value=modules.config.default_performance,
elem_classes=['performance_selection'])
- with gr.Accordion(label='Aspect Ratios', open=False, elem_id='aspect_ratios_accordion') as aspect_ratios_accordion:
- aspect_ratios_selection = gr.Radio(label='Aspect Ratios', show_label=False,
+ with gr.Accordion(label='アスペクト比', open=False, elem_id='aspect_ratios_accordion') as aspect_ratios_accordion:
+ aspect_ratios_selection = gr.Radio(label='アスペクト比', show_label=False,
choices=modules.config.available_aspect_ratios_labels,
value=modules.config.default_aspect_ratio,
- info='width × height',
+ info='幅 × 高さ',
elem_classes='aspect_ratios')
aspect_ratios_selection.change(lambda x: None, inputs=aspect_ratios_selection, queue=False, show_progress=False, _js='(x)=>{refresh_aspect_ratios_label(x);}')
shared.gradio_root.load(lambda x: None, inputs=aspect_ratios_selection, queue=False, show_progress=False, _js='(x)=>{refresh_aspect_ratios_label(x);}')
- image_number = gr.Slider(label='Image Number', minimum=1, maximum=modules.config.default_max_image_number, step=1, value=modules.config.default_image_number)
+ image_number = gr.Slider(label='生成枚数', minimum=1, maximum=modules.config.default_max_image_number, step=1, value=modules.config.default_image_number)
- output_format = gr.Radio(label='Output Format',
+ output_format = gr.Radio(label='出力形式',
choices=flags.OutputFormat.list(),
value=modules.config.default_output_format)
- negative_prompt = gr.Textbox(label='Negative Prompt', show_label=True, placeholder="Type prompt here.",
- info='Describing what you do not want to see.', lines=2,
+ negative_prompt = gr.Textbox(label='ネガティブプロンプト', show_label=True, placeholder="ここにプロンプトを入力",
+ info='生成したくない要素を入力してください。', lines=2,
elem_id='negative_prompt',
value=modules.config.default_prompt_negative)
- seed_random = gr.Checkbox(label='Random', value=True)
- image_seed = gr.Textbox(label='Seed', value=0, max_lines=1, visible=False) # workaround for https://github.com/gradio-app/gradio/issues/5354
+ seed_random = gr.Checkbox(label='ランダム', value=True)
+ image_seed = gr.Textbox(label='シード', value=0, max_lines=1, visible=False) # workaround for https://github.com/gradio-app/gradio/issues/5354
def random_checked(r):
return gr.update(visible=not r)
@@ -613,24 +775,24 @@ with shared.gradio_root:
if args_manager.args.disable_image_log:
return gr.update(value='')
- return gr.update(value=f'\U0001F4DA History Log')
+ return gr.update(value=f'📚 生成履歴')
history_link = gr.HTML()
shared.gradio_root.load(update_history_link, outputs=history_link, queue=False, show_progress=False)
- with gr.Tab(label='Styles', elem_classes=['style_selections_tab']):
+ with gr.Tab(label='スタイル', elem_classes=['style_selections_tab']):
style_sorter.try_load_sorted_styles(
style_names=legal_style_names,
default_selected=modules.config.default_styles)
style_search_bar = gr.Textbox(show_label=False, container=False,
- placeholder="\U0001F50E Type here to search styles ...",
+ placeholder="🔍 スタイルを検索...",
value="",
- label='Search Styles')
+ label='スタイル検索')
style_selections = gr.CheckboxGroup(show_label=False, container=False,
choices=copy.deepcopy(style_sorter.all_styles),
value=copy.deepcopy(modules.config.default_styles),
- label='Selected Styles',
+ label='選択中のスタイル',
elem_classes=['style_selections'])
gradio_receiver_style_selections = gr.Textbox(elem_id='gradio_receiver_style_selections', visible=False)
@@ -651,17 +813,14 @@ with shared.gradio_root:
show_progress=False).then(
lambda: None, _js='()=>{refresh_style_localization();}')
- with gr.Tab(label='Models'):
+ with gr.Tab(label='モデル'):
with gr.Group():
with gr.Row():
- base_model = gr.Dropdown(label='Base Model (SDXL only)', choices=modules.config.model_filenames, value=modules.config.default_base_model_name, show_label=True)
- refiner_model = gr.Dropdown(label='Refiner (SDXL or SD 1.5)', choices=['None'] + modules.config.model_filenames, value=modules.config.default_refiner_model_name, show_label=True)
+ base_model = gr.Dropdown(label='ベースモデル(SDXL のみ)', choices=modules.config.model_filenames, value=modules.config.default_base_model_name, show_label=True)
+ refiner_model = gr.Dropdown(label='リファイナー(SDXL または SD 1.5)', choices=['None'] + modules.config.model_filenames, value=modules.config.default_refiner_model_name, show_label=True)
- refiner_switch = gr.Slider(label='Refiner Switch At', minimum=0.1, maximum=1.0, step=0.0001,
- info='Use 0.4 for SD1.5 realistic models; '
- 'or 0.667 for SD1.5 anime models; '
- 'or 0.8 for XL-refiners; '
- 'or any value for switching two SDXL models.',
+ refiner_switch = gr.Slider(label='リファイナー切替タイミング', minimum=0.1, maximum=1.0, step=0.0001,
+ info='SD1.5 リアル系: 0.4 / アニメ系: 0.667 / XL リファイナー: 0.8 / 2モデル切替: 任意の値',
value=modules.config.default_refiner_switch,
visible=modules.config.default_refiner_model_name != 'None')
@@ -673,173 +832,168 @@ with shared.gradio_root:
for i, (enabled, filename, weight) in enumerate(modules.config.default_loras):
with gr.Row():
- lora_enabled = gr.Checkbox(label='Enable', value=enabled,
+ lora_enabled = gr.Checkbox(label='有効', value=enabled,
elem_classes=['lora_enable', 'min_check'], scale=1)
lora_model = gr.Dropdown(label=f'LoRA {i + 1}',
choices=['None'] + modules.config.lora_filenames, value=filename,
elem_classes='lora_model', scale=5)
- lora_weight = gr.Slider(label='Weight', minimum=modules.config.default_loras_min_weight,
+ lora_weight = gr.Slider(label='ウェイト', minimum=modules.config.default_loras_min_weight,
maximum=modules.config.default_loras_max_weight, step=0.01, value=weight,
elem_classes='lora_weight', scale=5)
lora_ctrls += [lora_enabled, lora_model, lora_weight]
with gr.Row():
- refresh_files = gr.Button(label='Refresh', value='\U0001f504 Refresh All Files', variant='secondary', elem_classes='refresh_button')
- with gr.Tab(label='Advanced'):
- guidance_scale = gr.Slider(label='Guidance Scale', minimum=1.0, maximum=30.0, step=0.01,
+ refresh_files = gr.Button(label='更新', value='🔄 全ファイルを更新', variant='secondary', elem_classes='refresh_button')
+ with gr.Tab(label='詳細設定'):
+ guidance_scale = gr.Slider(label='ガイダンススケール', minimum=1.0, maximum=30.0, step=0.01,
value=modules.config.default_cfg_scale,
- info='Higher value means style is cleaner, vivider, and more artistic.')
- sharpness = gr.Slider(label='Image Sharpness', minimum=0.0, maximum=30.0, step=0.001,
+ info='値が高いほどスタイルが鮮明・鮮やか・芸術的になります。')
+ sharpness = gr.Slider(label='画像シャープネス', minimum=0.0, maximum=30.0, step=0.001,
value=modules.config.default_sample_sharpness,
- info='Higher value means image and texture are sharper.')
- gr.HTML('\U0001F4D4 Documentation')
- dev_mode = gr.Checkbox(label='Developer Debug Mode', value=modules.config.default_developer_debug_mode_checkbox, container=False)
+ info='値が高いほど画像とテクスチャがシャープになります。')
+ gr.HTML('📖 ドキュメント')
+ dev_mode = gr.Checkbox(label='開発者デバッグモード', value=modules.config.default_developer_debug_mode_checkbox, container=False)
with gr.Column(visible=modules.config.default_developer_debug_mode_checkbox) as dev_tools:
- with gr.Tab(label='Debug Tools'):
- adm_scaler_positive = gr.Slider(label='Positive ADM Guidance Scaler', minimum=0.1, maximum=3.0,
- step=0.001, value=1.5, info='The scaler multiplied to positive ADM (use 1.0 to disable). ')
- adm_scaler_negative = gr.Slider(label='Negative ADM Guidance Scaler', minimum=0.1, maximum=3.0,
- step=0.001, value=0.8, info='The scaler multiplied to negative ADM (use 1.0 to disable). ')
- adm_scaler_end = gr.Slider(label='ADM Guidance End At Step', minimum=0.0, maximum=1.0,
+ with gr.Tab(label='デバッグツール'):
+ adm_scaler_positive = gr.Slider(label='ポジティブ ADM ガイダンス スケーラー', minimum=0.1, maximum=3.0,
+ step=0.001, value=1.5, info='ポジティブ ADM に乗算するスケーラー(無効にするには 1.0)。')
+ adm_scaler_negative = gr.Slider(label='ネガティブ ADM ガイダンス スケーラー', minimum=0.1, maximum=3.0,
+ step=0.001, value=0.8, info='ネガティブ ADM に乗算するスケーラー(無効にするには 1.0)。')
+ adm_scaler_end = gr.Slider(label='ADM ガイダンス終了ステップ', minimum=0.0, maximum=1.0,
step=0.001, value=0.3,
- info='When to end the guidance from positive/negative ADM. ')
+ info='ポジティブ/ネガティブ ADM ガイダンスを終了するタイミング。')
- refiner_swap_method = gr.Dropdown(label='Refiner swap method', value=flags.refiner_swap_method,
+ refiner_swap_method = gr.Dropdown(label='リファイナー スワップ方式', value=flags.refiner_swap_method,
choices=['joint', 'separate', 'vae'])
- adaptive_cfg = gr.Slider(label='CFG Mimicking from TSNR', minimum=1.0, maximum=30.0, step=0.01,
+ adaptive_cfg = gr.Slider(label='CFG TSNR ミミッキング', minimum=1.0, maximum=30.0, step=0.01,
value=modules.config.default_cfg_tsnr,
- info='Enabling Fooocus\'s implementation of CFG mimicking for TSNR '
- '(effective when real CFG > mimicked CFG).')
- clip_skip = gr.Slider(label='CLIP Skip', minimum=1, maximum=flags.clip_skip_max, step=1,
+ info='TSNR 向け CFG ミミッキングを有効化します(実 CFG > ミミッキング CFG のとき有効)。')
+ clip_skip = gr.Slider(label='CLIP スキップ', minimum=1, maximum=flags.clip_skip_max, step=1,
value=modules.config.default_clip_skip,
- info='Bypass CLIP layers to avoid overfitting (use 1 to not skip any layers, 2 is recommended).')
- sampler_name = gr.Dropdown(label='Sampler', choices=flags.sampler_list,
+ info='過学習を避けるため CLIP レイヤーをスキップします(1: スキップなし、推奨: 2)。')
+ sampler_name = gr.Dropdown(label='サンプラー', choices=flags.sampler_list,
value=modules.config.default_sampler)
- scheduler_name = gr.Dropdown(label='Scheduler', choices=flags.scheduler_list,
+ scheduler_name = gr.Dropdown(label='スケジューラー', choices=flags.scheduler_list,
value=modules.config.default_scheduler)
vae_name = gr.Dropdown(label='VAE', choices=[modules.flags.default_vae] + modules.config.vae_filenames,
value=modules.config.default_vae, show_label=True)
- generate_image_grid = gr.Checkbox(label='Generate Image Grid for Each Batch',
- info='(Experimental) This may cause performance problems on some computers and certain internet conditions.',
+ generate_image_grid = gr.Checkbox(label='バッチごとに画像グリッドを生成',
+ info='(実験的)環境によってはパフォーマンスに影響する場合があります。',
value=False)
- overwrite_step = gr.Slider(label='Forced Overwrite of Sampling Step',
+ overwrite_step = gr.Slider(label='サンプリングステップ 強制上書き',
minimum=-1, maximum=200, step=1,
value=modules.config.default_overwrite_step,
- info='Set as -1 to disable. For developer debugging.')
- overwrite_switch = gr.Slider(label='Forced Overwrite of Refiner Switch Step',
+ info='-1 で無効。開発者向けデバッグ用。')
+ overwrite_switch = gr.Slider(label='リファイナー切替ステップ 強制上書き',
minimum=-1, maximum=200, step=1,
value=modules.config.default_overwrite_switch,
- info='Set as -1 to disable. For developer debugging.')
- overwrite_width = gr.Slider(label='Forced Overwrite of Generating Width',
+ info='-1 で無効。開発者向けデバッグ用。')
+ overwrite_width = gr.Slider(label='生成幅 強制上書き',
minimum=-1, maximum=2048, step=1, value=-1,
- info='Set as -1 to disable. For developer debugging. '
- 'Results will be worse for non-standard numbers that SDXL is not trained on.')
- overwrite_height = gr.Slider(label='Forced Overwrite of Generating Height',
+ info='-1 で無効。SDXL の学習外の値は品質が低下します。')
+ overwrite_height = gr.Slider(label='生成高さ 強制上書き',
minimum=-1, maximum=2048, step=1, value=-1,
- info='Set as -1 to disable. For developer debugging. '
- 'Results will be worse for non-standard numbers that SDXL is not trained on.')
- overwrite_vary_strength = gr.Slider(label='Forced Overwrite of Denoising Strength of "Vary"',
+ info='-1 で無効。SDXL の学習外の値は品質が低下します。')
+ overwrite_vary_strength = gr.Slider(label='「バリエーション」デノイズ強度 強制上書き',
minimum=-1, maximum=1.0, step=0.001, value=-1,
- info='Set as negative number to disable. For developer debugging.')
- overwrite_upscale_strength = gr.Slider(label='Forced Overwrite of Denoising Strength of "Upscale"',
+ info='負の値で無効。開発者向けデバッグ用。')
+ overwrite_upscale_strength = gr.Slider(label='「アップスケール」デノイズ強度 強制上書き',
minimum=-1, maximum=1.0, step=0.001,
value=modules.config.default_overwrite_upscale,
- info='Set as negative number to disable. For developer debugging.')
+ info='負の値で無効。開発者向けデバッグ用。')
- disable_preview = gr.Checkbox(label='Disable Preview', value=modules.config.default_black_out_nsfw,
+ disable_preview = gr.Checkbox(label='プレビューを無効化', value=modules.config.default_black_out_nsfw,
interactive=not modules.config.default_black_out_nsfw,
- info='Disable preview during generation.')
- disable_intermediate_results = gr.Checkbox(label='Disable Intermediate Results',
+ info='生成中のプレビュー表示を無効にします。')
+ disable_intermediate_results = gr.Checkbox(label='中間結果を無効化',
value=flags.Performance.has_restricted_features(modules.config.default_performance),
- info='Disable intermediate results during generation, only show final gallery.')
+ info='生成中の中間結果を非表示にし、最終ギャラリーのみ表示します。')
- disable_seed_increment = gr.Checkbox(label='Disable seed increment',
- info='Disable automatic seed increment when image number is > 1.',
+ disable_seed_increment = gr.Checkbox(label='シード自動増加を無効化',
+ info='生成枚数が 1 より多い場合の自動シード増加を無効にします。',
value=False)
- read_wildcards_in_order = gr.Checkbox(label="Read wildcards in order", value=False)
+ read_wildcards_in_order = gr.Checkbox(label="ワイルドカードを順番に読む", value=False)
- black_out_nsfw = gr.Checkbox(label='Black Out NSFW', value=modules.config.default_black_out_nsfw,
+ black_out_nsfw = gr.Checkbox(label='NSFW を黒塗り', value=modules.config.default_black_out_nsfw,
interactive=not modules.config.default_black_out_nsfw,
- info='Use black image if NSFW is detected.')
+ info='NSFW が検出された場合に黒い画像を表示します。')
black_out_nsfw.change(lambda x: gr.update(value=x, interactive=not x),
inputs=black_out_nsfw, outputs=disable_preview, queue=False,
show_progress=False)
if not args_manager.args.disable_image_log:
- save_final_enhanced_image_only = gr.Checkbox(label='Save only final enhanced image',
+ save_final_enhanced_image_only = gr.Checkbox(label='最終強化画像のみ保存',
value=modules.config.default_save_only_final_enhanced_image)
if not args_manager.args.disable_metadata:
- save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images,
- info='Adds parameters to generated images allowing manual regeneration.')
- metadata_scheme = gr.Radio(label='Metadata Scheme', choices=flags.metadata_scheme, value=modules.config.default_metadata_scheme,
- info='Image Prompt parameters are not included. Use png and a1111 for compatibility with Civitai.',
+ save_metadata_to_images = gr.Checkbox(label='メタデータを画像に保存', value=modules.config.default_save_metadata_to_images,
+ info='生成パラメータを画像に埋め込み、後から再生成できるようにします。')
+ metadata_scheme = gr.Radio(label='メタデータスキーム', choices=flags.metadata_scheme, value=modules.config.default_metadata_scheme,
+ info='画像プロンプトのパラメータは含まれません。Civitai との互換性には png と a1111 を使用してください。',
visible=modules.config.default_save_metadata_to_images)
save_metadata_to_images.change(lambda x: gr.update(visible=x), inputs=[save_metadata_to_images], outputs=[metadata_scheme],
queue=False, show_progress=False)
- with gr.Tab(label='Control'):
- debugging_cn_preprocessor = gr.Checkbox(label='Debug Preprocessors', value=False,
- info='See the results from preprocessors.')
- skipping_cn_preprocessor = gr.Checkbox(label='Skip Preprocessors', value=False,
- info='Do not preprocess images. (Inputs are already canny/depth/cropped-face/etc.)')
+ with gr.Tab(label='コントロール'):
+ debugging_cn_preprocessor = gr.Checkbox(label='プリプロセッサーをデバッグ', value=False,
+ info='プリプロセッサーの処理結果を確認します。')
+ skipping_cn_preprocessor = gr.Checkbox(label='プリプロセッサーをスキップ', value=False,
+ info='画像を前処理しません(入力が既に Canny/Depth 等の場合に使用)。')
- mixing_image_prompt_and_vary_upscale = gr.Checkbox(label='Mixing Image Prompt and Vary/Upscale',
+ mixing_image_prompt_and_vary_upscale = gr.Checkbox(label='画像プロンプトとバリエーション/拡大を混合',
value=False)
- mixing_image_prompt_and_inpaint = gr.Checkbox(label='Mixing Image Prompt and Inpaint',
+ mixing_image_prompt_and_inpaint = gr.Checkbox(label='画像プロンプトとインペイントを混合',
value=False)
- controlnet_softness = gr.Slider(label='Softness of ControlNet', minimum=0.0, maximum=1.0,
+ controlnet_softness = gr.Slider(label='ControlNet ソフトネス', minimum=0.0, maximum=1.0,
step=0.001, value=0.25,
- info='Similar to the Control Mode in A1111 (use 0.0 to disable). ')
+ info='A1111 のコントロールモードに相当します(0.0 で無効)。')
with gr.Tab(label='Canny'):
- canny_low_threshold = gr.Slider(label='Canny Low Threshold', minimum=1, maximum=255,
+ canny_low_threshold = gr.Slider(label='Canny 低閾値', minimum=1, maximum=255,
step=1, value=64)
- canny_high_threshold = gr.Slider(label='Canny High Threshold', minimum=1, maximum=255,
+ canny_high_threshold = gr.Slider(label='Canny 高閾値', minimum=1, maximum=255,
step=1, value=128)
- with gr.Tab(label='Inpaint'):
- debugging_inpaint_preprocessor = gr.Checkbox(label='Debug Inpaint Preprocessing', value=False)
- debugging_enhance_masks_checkbox = gr.Checkbox(label='Debug Enhance Masks', value=False,
- info='Show enhance masks in preview and final results')
- debugging_dino = gr.Checkbox(label='Debug GroundingDINO', value=False,
- info='Use GroundingDINO boxes instead of more detailed SAM masks')
- inpaint_disable_initial_latent = gr.Checkbox(label='Disable initial latent in inpaint', value=False)
- inpaint_engine = gr.Dropdown(label='Inpaint Engine',
+ with gr.Tab(label='インペイント'):
+ debugging_inpaint_preprocessor = gr.Checkbox(label='インペイント前処理をデバッグ', value=False)
+ debugging_enhance_masks_checkbox = gr.Checkbox(label='強化マスクをデバッグ', value=False,
+ info='プレビューと最終結果に強化マスクを表示します。')
+ debugging_dino = gr.Checkbox(label='GroundingDINO をデバッグ', value=False,
+ info='SAM マスクの代わりに GroundingDINO のボックスを使用します。')
+ inpaint_disable_initial_latent = gr.Checkbox(label='インペイントの初期潜在を無効化', value=False)
+ inpaint_engine = gr.Dropdown(label='インペイントエンジン',
value=modules.config.default_inpaint_engine_version,
choices=flags.inpaint_engine_versions,
- info='Version of Fooocus inpaint model. If set, use performance Quality or Speed (no performance LoRAs) for best results.')
- inpaint_strength = gr.Slider(label='Inpaint Denoising Strength',
+ info='Fooocus インペイントモデルのバージョン。Quality または Speed を推奨。')
+ inpaint_strength = gr.Slider(label='インペイント デノイズ強度',
minimum=0.0, maximum=1.0, step=0.001, value=1.0,
- info='Same as the denoising strength in A1111 inpaint. '
- 'Only used in inpaint, not used in outpaint. '
- '(Outpaint always use 1.0)')
- inpaint_respective_field = gr.Slider(label='Inpaint Respective Field',
+ info='A1111 インペイントのデノイズ強度と同じです。'
+ 'インペイントのみ有効(アウトペイントは常に 1.0)。')
+ inpaint_respective_field = gr.Slider(label='インペイント対象範囲',
minimum=0.0, maximum=1.0, step=0.001, value=0.618,
- info='The area to inpaint. '
- 'Value 0 is same as "Only Masked" in A1111. '
- 'Value 1 is same as "Whole Image" in A1111. '
- 'Only used in inpaint, not used in outpaint. '
- '(Outpaint always use 1.0)')
- inpaint_erode_or_dilate = gr.Slider(label='Mask Erode or Dilate',
+ info='インペイントする領域。'
+ '0 = A1111 の「マスクのみ」、'
+ '1 = 「画像全体」。'
+ 'インペイントのみ有効(アウトペイントは常に 1.0)。')
+ inpaint_erode_or_dilate = gr.Slider(label='マスク 侵食/膨張',
minimum=-64, maximum=64, step=1, value=0,
- info='Positive value will make white area in the mask larger, '
- 'negative value will make white area smaller. '
- '(default is 0, always processed before any mask invert)')
- dino_erode_or_dilate = gr.Slider(label='GroundingDINO Box Erode or Dilate',
+ info='正の値はマスクの白い領域を拡大、'
+ '負の値は縮小します。'
+ '(デフォルト 0、マスク反転前に処理)')
+ dino_erode_or_dilate = gr.Slider(label='GroundingDINO ボックス 侵食/膨張',
minimum=-64, maximum=64, step=1, value=0,
- info='Positive value will make white area in the mask larger, '
- 'negative value will make white area smaller. '
- '(default is 0, processed before SAM)')
+ info='正の値はマスクの白い領域を拡大、'
+ '負の値は縮小します。'
+ '(デフォルト 0、SAM 処理前に実行)')
- inpaint_mask_color = gr.ColorPicker(label='Inpaint brush color', value='#FFFFFF', elem_id='inpaint_brush_color')
+ inpaint_mask_color = gr.ColorPicker(label='インペイントブラシの色', value='#FFFFFF', elem_id='inpaint_brush_color')
inpaint_ctrls = [debugging_inpaint_preprocessor, inpaint_disable_initial_latent, inpaint_engine,
inpaint_strength, inpaint_respective_field,
@@ -855,7 +1009,7 @@ with shared.gradio_root:
queue=False, show_progress=False)
with gr.Tab(label='FreeU'):
- freeu_enabled = gr.Checkbox(label='Enabled', value=False)
+ freeu_enabled = gr.Checkbox(label='有効化', value=False)
freeu_b1 = gr.Slider(label='B1', minimum=0, maximum=2, step=0.01, value=1.01)
freeu_b2 = gr.Slider(label='B2', minimum=0, maximum=2, step=0.01, value=1.02)
freeu_s1 = gr.Slider(label='S1', minimum=0, maximum=4, step=0.01, value=0.99)
@@ -937,6 +1091,345 @@ with shared.gradio_root:
.then(lambda: None, _js='()=>{refresh_style_localization();}') \
.then(inpaint_engine_state_change, inputs=[inpaint_engine_state] + enhance_inpaint_mode_ctrls, outputs=enhance_inpaint_engine_ctrls, queue=False, show_progress=False)
+ # ── 保存先変更 イベント ──
+ def _do_change_output_path(new_path):
+ try:
+ p = str(new_path).strip()
+ if not p:
+ return gr.update(), '❌ パスを入力してください'
+ os.makedirs(p, exist_ok=True)
+ modules.config.path_outputs = p
+ cfg = _ui_cfg_load()
+ cfg['path_outputs'] = p
+ _ui_cfg_save(cfg)
+ return gr.update(value=p), f'✅ 保存先を変更しました: `{p}`'
+ except Exception as e:
+ return gr.update(), f'❌ エラー: {e}'
+
+ output_path_btn.click(
+ fn=_do_change_output_path, inputs=[output_path_box],
+ outputs=[output_path_box, output_path_status], queue=False, show_progress=False)
+
+ # ── User Setting Presets イベント ──
+ _sp_save_inputs = [
+ user_sp_name, prompt, negative_prompt, style_selections,
+ performance_selection, aspect_ratios_selection, image_number,
+ guidance_scale, sharpness, base_model, refiner_model, refiner_switch,
+ sampler_name, scheduler_name, vae_name,
+ seed_random, image_seed,
+ adm_scaler_positive, adm_scaler_negative, adm_scaler_end,
+ refiner_swap_method, adaptive_cfg, clip_skip,
+ input_image_checkbox, enhance_checkbox, uov_method,
+ ] + freeu_ctrls + lora_ctrls
+
+ def sp_do_save(name, prompt_v, neg_v, styles_v, perf_v, aspect_v, img_num,
+ guidance_v, sharp_v, base_v, ref_v, ref_sw_v,
+ sampler_v, sched_v, vae_v,
+ seed_rnd, seed_v,
+ adm_p, adm_n, adm_e,
+ ref_swap_v, adap_v, clip_v,
+ input_img_cb, enhance_cb, uov_m,
+ *rest):
+ try:
+ if not name or not str(name).strip():
+ return gr.update(), '❌ プリセット名を入力してください'
+ n = str(name).strip()
+ # rest = [freeu_en, freeu_b1, freeu_b2, freeu_s1, freeu_s2, lora_en1, lora_m1, lora_w1, ...]
+ freeu_en = rest[0] if len(rest) > 0 else False
+ freeu_b1_v = rest[1] if len(rest) > 1 else 1.01
+ freeu_b2_v = rest[2] if len(rest) > 2 else 1.02
+ freeu_s1_v = rest[3] if len(rest) > 3 else 0.99
+ freeu_s2_v = rest[4] if len(rest) > 4 else 0.95
+ lora_vals = rest[5:]
+
+ meta = {
+ 'prompt': str(prompt_v) if prompt_v else '',
+ 'negative_prompt': str(neg_v) if neg_v else '',
+ 'styles': str(styles_v) if styles_v else '[]',
+ 'performance': str(perf_v) if perf_v else 'Speed',
+ 'resolution': _sp_aspect_to_resolution(str(aspect_v) if aspect_v else ''),
+ 'image_number': int(float(img_num)) if img_num is not None else 1,
+ 'guidance_scale': float(guidance_v) if guidance_v is not None else 7.0,
+ 'sharpness': float(sharp_v) if sharp_v is not None else 2.0,
+ 'base_model': str(base_v) if base_v else '',
+ 'refiner_model': str(ref_v) if ref_v else 'None',
+ 'refiner_switch': float(ref_sw_v) if ref_sw_v is not None else 0.5,
+ 'sampler': str(sampler_v) if sampler_v else 'dpmpp_2m_sde_gpu',
+ 'scheduler': str(sched_v) if sched_v else 'karras',
+ 'vae': str(vae_v) if vae_v else 'Default (model)',
+ 'adm_guidance': f'({adm_p}, {adm_n}, {adm_e})',
+ 'refiner_swap_method': str(ref_swap_v) if ref_swap_v else 'joint',
+ 'adaptive_cfg': float(adap_v) if adap_v is not None else 7.0,
+ 'clip_skip': int(float(clip_v)) if clip_v is not None else 2,
+ 'input_image_enabled': bool(input_img_cb),
+ 'enhance_enabled': bool(enhance_cb),
+ 'uov_method': str(uov_m) if uov_m else flags.disabled,
+ }
+ if not seed_rnd:
+ try:
+ meta['seed'] = int(float(seed_v))
+ except Exception:
+ pass
+ if freeu_en:
+ meta['freeu'] = f'({freeu_b1_v}, {freeu_b2_v}, {freeu_s1_v}, {freeu_s2_v})'
+ for i in range(0, len(lora_vals), 3):
+ en = lora_vals[i]
+ nm = lora_vals[i + 1]
+ wt = lora_vals[i + 2]
+ meta[f'lora_combined_{i // 3 + 1}'] = f'{"True" if en else "False"} : {nm} : {wt}'
+
+ presets = _sp_load()
+ presets[n] = meta
+ _sp_save(presets)
+ choices = list(presets.keys())
+ print(f'[UserPreset] 保存完了: {n} / 合計{len(choices)}件 → {_USER_SP_PATH}')
+ return gr.update(choices=choices, value=n), f'✅ 保存しました:{n}'
+ except Exception as e:
+ import traceback
+ msg = traceback.format_exc()
+ print(f'[UserPreset] 保存エラー:\n{msg}')
+ return gr.update(), f'❌ 保存エラー: {e}'
+
+ def sp_do_load(name, is_gen, inpaint_m):
+ _no_change = [gr.update()] * 5 # input_img_cb, enhance_cb, img_panel, enhance_panel, uov_m
+ try:
+ if not name:
+ return [gr.update()] * len(load_data_outputs) + ['❌ プリセットを選択してください'] + _no_change
+ presets = _sp_load()
+ if name not in presets:
+ return [gr.update()] * len(load_data_outputs) + [f'❌ 「{name}」が見つかりません'] + _no_change
+ meta = presets[name]
+ result = modules.meta_parser.load_parameter_button_click(meta, is_gen, inpaint_m)
+ print(f'[UserPreset] 読込完了: {name}')
+ # 追加フィールド: 入力画像 / 強化 / UoV メソッド
+ img_en = bool(meta.get('input_image_enabled', False))
+ enh_en = bool(meta.get('enhance_enabled', False))
+ uov_val = meta.get('uov_method', flags.disabled)
+ return (result
+ + [f'✅ 読み込みました:{name}']
+ + [gr.update(value=img_en),
+ gr.update(value=enh_en),
+ gr.update(visible=img_en),
+ gr.update(visible=enh_en),
+ gr.update(value=uov_val)])
+ except Exception as e:
+ import traceback
+ msg = traceback.format_exc()
+ print(f'[UserPreset] 読込エラー:\n{msg}')
+ return [gr.update()] * len(load_data_outputs) + [f'❌ 読込エラー: {e}'] + _no_change
+
+ def sp_do_delete(name):
+ try:
+ if not name:
+ return gr.update(), '❌ プリセットを選択してください'
+ presets = _sp_load()
+ if name not in presets:
+ return gr.update(), f'❌ 「{name}」が見つかりません'
+ del presets[name]
+ _sp_save(presets)
+ choices = list(presets.keys())
+ return gr.update(choices=choices, value=choices[0] if choices else None), f'🗑️ 削除しました:{name}'
+ except Exception as e:
+ return gr.update(), f'❌ 削除エラー: {e}'
+
+ def sp_refresh_dropdown():
+ choices = list(_sp_load().keys())
+ return gr.update(choices=choices, value=choices[0] if choices else None)
+
+ shared.gradio_root.load(fn=sp_refresh_dropdown, inputs=[], outputs=[user_sp_dropdown],
+ queue=False, show_progress=False)
+
+ user_sp_save_btn.click(
+ fn=sp_do_save,
+ inputs=_sp_save_inputs,
+ outputs=[user_sp_dropdown, user_sp_status],
+ queue=False, show_progress=False)
+
+ user_sp_load_btn.click(
+ fn=sp_do_load,
+ inputs=[user_sp_dropdown, state_is_generating, inpaint_mode],
+ outputs=load_data_outputs + [user_sp_status,
+ input_image_checkbox, enhance_checkbox,
+ image_input_panel, enhance_input_panel, uov_method],
+ queue=False, show_progress=True) \
+ .then(fn=style_sorter.sort_styles, inputs=style_selections, outputs=style_selections, queue=False, show_progress=False) \
+ .then(lambda: None, _js='()=>{refresh_style_localization();}')
+
+ user_sp_delete_btn.click(
+ fn=sp_do_delete,
+ inputs=[user_sp_dropdown],
+ outputs=[user_sp_dropdown, user_sp_status],
+ queue=False, show_progress=False)
+
+ # ── ブラッシュアップ & OpenCV エディット イベント ──
+ _brushup_outputs = [
+ uov_input_image, uov_method, input_image_checkbox, image_input_panel,
+ image_input_tabs, current_tab,
+ ]
+ _brushup_select_outputs = [
+ brushup_selected, brushup_original, brushup_status_html,
+ brushup_btn_col, edit_panel, edit_preview,
+ ]
+
+ def on_brushup_select(evt: gr.SelectData, paths_state):
+ # Gradio 3.41.2: evt.value = caption (usually None), evt.index = int index
+ _hide = gr.update(visible=False)
+ _fail = (None, None,
+ ''
+ '💡 ギャラリーの画像をクリックして選択',
+ _hide, _hide, None)
+ try:
+ idx = evt.index
+ if not (paths_state and isinstance(idx, int) and 0 <= idx < len(paths_state)):
+ return _fail
+ path = paths_state[idx]
+ if not (isinstance(path, str) and os.path.exists(path)):
+ return _fail
+ from PIL import Image as PILImage
+ import numpy as np
+ from modules.image_editor import resize_for_preview as _rfp
+ arr = np.array(PILImage.open(path).convert('RGB'))
+ h, w = arr.shape[:2]
+ fname = os.path.basename(path)
+ html = (f''
+ f'✓ {fname} ({w}×{h}px) — 方法を選択 または ↓ で編集')
+ preview = _rfp(arr, 768)
+ return arr, arr, html, gr.update(visible=True), gr.update(visible=True), preview
+ except Exception as e:
+ return (None, None,
+ f'❌ {e}',
+ _hide, _hide, None)
+
+ gallery.select(fn=on_brushup_select, inputs=[gallery_paths], outputs=_brushup_select_outputs, queue=False, show_progress=False)
+ progress_gallery.select(fn=on_brushup_select, inputs=[gallery_paths], outputs=_brushup_select_outputs, queue=False, show_progress=False)
+
+ def do_brushup(img, method):
+ if img is None:
+ return (gr.update(), gr.update(), gr.update(), gr.update(visible=False),
+ gr.update(), gr.update())
+ return (img, method, True, gr.update(visible=True),
+ gr.update(selected='uov_tab'), 'uov')
+
+ _scroll_js = '() => { setTimeout(function(){ var el=document.getElementById("image_input_panel"); if(!el) el=document.querySelector(".image_input_panel"); if(el) el.scrollIntoView({behavior:"smooth",block:"start"}); }, 200); return []; }'
+
+ brushup_subtle_btn.click(
+ fn=lambda img: do_brushup(img, flags.subtle_variation),
+ inputs=[brushup_selected], outputs=_brushup_outputs, queue=False, show_progress=False
+ ).then(fn=lambda: None, _js=_scroll_js)
+
+ brushup_strong_btn.click(
+ fn=lambda img: do_brushup(img, flags.strong_variation),
+ inputs=[brushup_selected], outputs=_brushup_outputs, queue=False, show_progress=False
+ ).then(fn=lambda: None, _js=_scroll_js)
+
+ brushup_upscale_btn.click(
+ fn=lambda img: do_brushup(img, flags.upscale_2),
+ inputs=[brushup_selected], outputs=_brushup_outputs, queue=False, show_progress=False
+ ).then(fn=lambda: None, _js=_scroll_js)
+
+ brushup_gen_btn.click(
+ fn=lambda img: do_brushup(img, flags.subtle_variation),
+ inputs=[brushup_selected], outputs=_brushup_outputs, queue=False, show_progress=False
+ ).then(fn=lambda: None, _js='() => { setTimeout(function(){ var b=document.getElementById("generate_button"); if(b&&!b.disabled)b.click(); },600); return []; }')
+
+ # ── OpenCV エディター イベント ──
+ from modules.image_editor import apply_adjustments, resize_for_preview, rotate_image, flip_image
+
+ # プリセット定義: (brightness, contrast, saturation, hue, temperature, sharpness)
+ _PRESETS = {
+ 'vivid': (10, 1.25, 1.6, 0, 0, 1.3),
+ 'cinema': (-8, 1.35, 0.7, 0, 15, 1.0),
+ 'warm': (12, 1.05, 1.2, 0, 40, 1.0),
+ 'cool': (0, 1.1, 0.85, 0, -40, 1.0),
+ 'soft': (8, 0.88, 0.88, 0, 0, 0.4),
+ }
+
+ # スライダーの入力リスト (順番をそろえる)
+ _edit_inputs = [brushup_original,
+ edit_brightness, edit_contrast, edit_saturation,
+ edit_hue, edit_temperature, edit_sharpness]
+ _slider_outputs = [edit_brightness, edit_contrast, edit_saturation,
+ edit_hue, edit_temperature, edit_sharpness]
+
+ def _update_preview(orig, brightness, contrast, saturation, hue_shift, temperature, sharpness):
+ small = resize_for_preview(orig, 768)
+ return apply_adjustments(small, brightness=brightness, contrast=contrast,
+ saturation=saturation, hue_shift=hue_shift,
+ sharpness=sharpness, temperature=temperature)
+
+ # スライダー変更 → リアルタイムプレビュー
+ for _sl in [edit_brightness, edit_contrast, edit_saturation, edit_hue, edit_temperature, edit_sharpness]:
+ _sl.change(fn=_update_preview, inputs=_edit_inputs, outputs=edit_preview,
+ queue=False, show_progress=False)
+
+ # リセット
+ def _reset_edit(orig):
+ preview = resize_for_preview(orig, 768)
+ return 0, 1.0, 1.0, 0, 0, 1.0, preview
+
+ edit_reset_btn.click(
+ fn=_reset_edit, inputs=[brushup_original],
+ outputs=_slider_outputs + [edit_preview],
+ queue=False, show_progress=False
+ )
+
+ # 適用 → brushup_selected を編集版に更新
+ def _apply_edit(orig, brightness, contrast, saturation, hue_shift, temperature, sharpness):
+ edited = apply_adjustments(orig, brightness=brightness, contrast=contrast,
+ saturation=saturation, hue_shift=hue_shift,
+ sharpness=sharpness, temperature=temperature)
+ if edited is None:
+ return gr.update(), gr.update()
+ preview = resize_for_preview(edited, 768)
+ return edited, preview
+
+ edit_apply_btn.click(
+ fn=_apply_edit, inputs=_edit_inputs,
+ outputs=[brushup_selected, edit_preview],
+ queue=False, show_progress=False
+ )
+
+ # ── プリセット ──────────────────────────────
+ def _apply_preset(key, orig):
+ br, ct, sat, hue, temp, sharp = _PRESETS[key]
+ preview = _update_preview(orig, br, ct, sat, hue, temp, sharp)
+ return br, ct, sat, hue, temp, sharp, preview
+
+ _preset_outs = _slider_outputs + [edit_preview]
+ edit_preset_vivid.click( fn=lambda o: _apply_preset('vivid', o), inputs=[brushup_original], outputs=_preset_outs, queue=False, show_progress=False)
+ edit_preset_cinema.click(fn=lambda o: _apply_preset('cinema', o), inputs=[brushup_original], outputs=_preset_outs, queue=False, show_progress=False)
+ edit_preset_warm.click( fn=lambda o: _apply_preset('warm', o), inputs=[brushup_original], outputs=_preset_outs, queue=False, show_progress=False)
+ edit_preset_cool.click( fn=lambda o: _apply_preset('cool', o), inputs=[brushup_original], outputs=_preset_outs, queue=False, show_progress=False)
+ edit_preset_soft.click( fn=lambda o: _apply_preset('soft', o), inputs=[brushup_original], outputs=_preset_outs, queue=False, show_progress=False)
+
+ # ── 回転・反転 ──────────────────────────────
+ _transform_inputs = [brushup_original] + _slider_outputs
+ _transform_outputs = [brushup_original, edit_preview]
+
+ def _do_rotate(orig, brightness, contrast, saturation, hue_shift, temperature, sharpness, deg):
+ rotated = rotate_image(orig, deg)
+ if rotated is None:
+ return gr.update(), gr.update()
+ return rotated, _update_preview(rotated, brightness, contrast, saturation, hue_shift, temperature, sharpness)
+
+ def _do_flip(orig, brightness, contrast, saturation, hue_shift, temperature, sharpness, direction):
+ flipped = flip_image(orig, direction)
+ if flipped is None:
+ return gr.update(), gr.update()
+ return flipped, _update_preview(flipped, brightness, contrast, saturation, hue_shift, temperature, sharpness)
+
+ edit_rot_l.click( fn=lambda *a: _do_rotate(*a, 270), inputs=_transform_inputs, outputs=_transform_outputs, queue=False, show_progress=False)
+ edit_rot_r.click( fn=lambda *a: _do_rotate(*a, 90), inputs=_transform_inputs, outputs=_transform_outputs, queue=False, show_progress=False)
+ edit_flip_h.click(fn=lambda *a: _do_flip(*a, 'h'), inputs=_transform_inputs, outputs=_transform_outputs, queue=False, show_progress=False)
+ edit_flip_v.click(fn=lambda *a: _do_flip(*a, 'v'), inputs=_transform_inputs, outputs=_transform_outputs, queue=False, show_progress=False)
+
+ # ── Before / After ─────────────────────────
+ edit_ba_btn.click(
+ fn=lambda orig: resize_for_preview(orig, 768),
+ inputs=[brushup_original], outputs=edit_preview,
+ queue=False, show_progress=False
+ )
+
performance_selection.change(lambda x: [gr.update(interactive=not flags.Performance.has_restricted_features(x))] * 11 +
[gr.update(visible=not flags.Performance.has_restricted_features(x))] * 1 +
[gr.update(value=flags.Performance.has_restricted_features(x))] * 1,
@@ -1027,7 +1520,7 @@ with shared.gradio_root:
def trigger_metadata_import(file, state_is_generating):
parameters, metadata_scheme = modules.meta_parser.read_info_from_image(file)
if parameters is None:
- print('Could not find metadata in the image!')
+ print('画像にメタデータが見つかりませんでした。')
parsed_parameters = {}
else:
metadata_parser = modules.meta_parser.get_metadata_parser(metadata_scheme)
@@ -1042,7 +1535,7 @@ with shared.gradio_root:
outputs=[stop_button, skip_button, generate_button, gallery, state_is_generating]) \
.then(fn=refresh_seed, inputs=[seed_random, image_seed], outputs=image_seed) \
.then(fn=get_task, inputs=ctrls, outputs=currentTask) \
- .then(fn=generate_clicked, inputs=currentTask, outputs=[progress_html, progress_window, progress_gallery, gallery]) \
+ .then(fn=generate_clicked, inputs=currentTask, outputs=[progress_html, progress_window, progress_gallery, gallery, gallery_paths]) \
.then(lambda: (gr.update(visible=True, interactive=True), gr.update(visible=False, interactive=False), gr.update(visible=False, interactive=False), False),
outputs=[generate_button, stop_button, skip_button, state_is_generating]) \
.then(fn=update_history_link, outputs=history_link) \
@@ -1092,6 +1585,84 @@ with shared.gradio_root:
.then(fn=style_sorter.sort_styles, inputs=style_selections, outputs=style_selections, queue=False, show_progress=False) \
.then(lambda: None, _js='()=>{refresh_style_localization();}')
+ # ── Prompt Forge 統合:postMessage リスナー ──
+ shared.gradio_root.load(
+ fn=lambda: None, inputs=[], outputs=[],
+ queue=False, show_progress=False,
+ _js="""
+function() {
+ if (window._pfListenerAdded) return [];
+ window._pfListenerAdded = true;
+
+ // React管理のtextareaに値をセットしてイベントを発火する
+ function pfSetTextarea(selector, value) {
+ var el = document.querySelector(selector);
+ if (!el) return false;
+ try {
+ var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
+ nativeSetter.call(el, value);
+ } catch(e) {
+ el.value = value;
+ }
+ el.dispatchEvent(new Event('input', {bubbles: true}));
+ el.dispatchEvent(new Event('change', {bubbles: true}));
+ return true;
+ }
+
+ // 転送後、テキストエリアをハイライト(緑フラッシュ)
+ function pfFlash(selector) {
+ var el = document.querySelector(selector);
+ if (!el) return;
+ el.style.transition = 'box-shadow 0.15s ease';
+ el.style.boxShadow = '0 0 0 3px rgba(100,255,150,0.7)';
+ setTimeout(function() { el.style.boxShadow = ''; }, 1000);
+ }
+
+ // アコーディオンを閉じる(複数のセレクタを試みる)
+ function pfCloseAccordion() {
+ var btn = document.querySelector('#pf_accordion button[aria-expanded="true"]');
+ if (!btn) btn = document.querySelector('#pf_accordion .toggle-icon[aria-expanded="true"]');
+ if (!btn) btn = document.querySelector('#pf_accordion > button:first-child');
+ if (btn) {
+ try { btn.click(); } catch(e) {}
+ }
+ }
+
+ window.addEventListener('message', function(ev) {
+ if (!ev.data || ev.data.type !== 'pf_send') return;
+
+ // アコーディオンを閉じる
+ pfCloseAccordion();
+
+ // positive_prompt を更新
+ var posOk = pfSetTextarea('#positive_prompt textarea', ev.data.pos || '');
+
+ // negative_prompt を更新(Settingsタブが閉じていてもGradioはDOMに保持する)
+ pfSetTextarea('#negative_prompt textarea', ev.data.neg || '');
+
+ // フォーカス&フラッシュ
+ setTimeout(function() {
+ var posEl = document.querySelector('#positive_prompt textarea');
+ if (posEl) {
+ posEl.focus();
+ posEl.scrollIntoView({behavior: 'smooth', block: 'center'});
+ }
+ pfFlash('#positive_prompt textarea');
+ }, 150);
+
+ // 生成ボタン自動クリック
+ if (ev.data.generate) {
+ setTimeout(function() {
+ var btn = document.getElementById('generate_button');
+ if (btn) btn.click();
+ }, 600);
+ }
+ });
+ return [];
+}
+"""
+ )
+
if args_manager.args.enable_auto_describe_image:
def trigger_auto_describe(mode, img, prompt, apply_styles):
# keep prompt if not empty
@@ -1117,6 +1688,13 @@ def dump_default_english_config():
# dump_default_english_config()
+# ── Prompt Forge ブリッジサーバーを起動 ──
+try:
+ import prompt_forge_bridge
+ prompt_forge_bridge.start_bridge()
+except Exception as _pf_err:
+ print(f'[PromptForge] ブリッジ起動失敗: {_pf_err}')
+
shared.gradio_root.launch(
inbrowser=args_manager.args.in_browser,
server_name=args_manager.args.listen,
diff --git a/wildcards/prompt_forge_v3.html b/wildcards/prompt_forge_v3.html
new file mode 100644
index 00000000..bc1eb789
--- /dev/null
+++ b/wildcards/prompt_forge_v3.html
@@ -0,0 +1,3110 @@
+
+
+
+
+
+PROMPT FORGE
+
+
+
+PROMPT FORGE
+Fooocus 日本語プロンプト → 英語変換 v3
+
+
+
+
▸ Anthropic API Key
+
+
+ 未設定
+
+
+
+
+
+
+
+
+
+
+
▸ 翻訳結果
+
← 上で翻訳ボタンを押してください
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ 翻訳結果(ネガティブ)
+
← ネガティブを翻訳してください
+
+
+
+
+
+
+
+
+
+
▸ モデル別タグ 0 selected
+
▶
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 📚 作品 ▸
+
+
+
+
+
+ 🔍 キャラ検索 ▸
+
+
+
+
+
+
+
+ 外見 ▸
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ANI ▸
+
+
+
+
+
+
+ PONY ▸
+
+
+
+
+
+
+ NSFW ▸
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ 選択中タグ(ドラッグで並び替え)
+
タグをクリックして追加
+
▸ ネガティブ選択タグ
+
ネガティブタグをクリックして追加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
▸ Wildcard 置き場 TXTファイルと対応
+
+
+
+
+
▶
+
+
+
+
+
+
+
+
+
+
+
+
+ ▸ TXTは Fooocus の wildcards/ フォルダに配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
POSITIVE
+
ここにポジティブプロンプトが表示されます
+
NEGATIVE
+
ここにネガティブプロンプトが表示されます
+
+
+
+
+
+
+
+
+※ APIキーはこのページ内のみで使用。外部送信なし(Anthropic APIへの翻訳リクエストを除く)
+
+
+
+
+