Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6016ac1fcf | ||
|
|
b82e93a431 |
@@ -2,6 +2,8 @@
|
||||
|
||||
Condensed version-by-version index of DEVLOG.md (each line: version + one-line summary of what changed and why). Full rationale, code details, and "How to apply" lessons live in the excluded DEVLOG.md itself; this index exists so graphify can extract a queryable semantic node per release without feeding the full log to the local Ollama model. Regenerate with `python tools/generate_doc_index.py devlog` whenever DEVLOG.md changes.
|
||||
|
||||
- **v0.9.9(2026-11-15)** — プロンプト翻訳(自国語 → 英語、PLaMo 3 Translate)
|
||||
- **v0.9.8(2026-11-14)** — セキュリティ: クライアントから受け取るパスを解決前に検証
|
||||
- **v0.9.6(2026-11-12)** — マニュアル v0.9.6版(ja/en)
|
||||
- **v0.9.5(2026-11-11)** — AI TOOL Chat: スキル v2(metadata・同梱スキルの版管理・プロンプトボタン)
|
||||
- **v0.9.4(2026-11-07)** — Gallery: 3Dモデル対応(GLB / glTF / VRM / PLY / SPZ)
|
||||
|
||||
@@ -2,6 +2,44 @@
|
||||
|
||||
---
|
||||
|
||||
## v0.9.9(2026-11-15)
|
||||
|
||||
### プロンプト翻訳(自国語 → 英語、PLaMo 3 Translate)
|
||||
|
||||
- 設定タブに「プロンプト翻訳」セクションを新設(既定オフ)。A1111 の prompt translator 拡張と同じく、英数字以外の文字を含む部分だけを翻訳し、カンマ・`(語句:1.2)`・`[ ]`・`{a|b}`・`<lora:...>`・`__wildcard__`・`embedding:`・`BREAK`/`AND` は保護する(`static/js/prompt-translate.js` の `splitPrompt`)。
|
||||
- 🌐ボタン(+「原文」ボタン、Ctrl+Z も可)を GenerateUI の Positive/Negative、Lab プロンプトモーダル、Video Plan のブロックプロンプトに追加。
|
||||
- モデルは Ollama 経由。PLaMo は `/api/generate` に `raw:true` で PFML3 プロンプトを直接送る(モデルカード推奨、stop `<|plamo:tag|>`/`<|plamo:bos|>`)。PLaMo 以外の Ollama モデルは `/api/chat` +翻訳指示。PLaMo の4サイズはプリセットとして表示し、未取得なら「ダウンロード」(`/api/wfm/translate/pull`、プリセットのみ許可)。
|
||||
- 実行場所: GPU(1語句約0.1秒、翻訳後アンロード可)/CPU常駐(`num_gpu:0`+`keep_alive:-1`、1語句約0.3秒、VRAM 0、GenerateUI表示時に `/warmup` で裏読み込み、デバイス・モデル切替や無効化時は `/unload`)。実測でコールド約4秒の大半は Ollama ランナー起動(CPUでも4.4秒)だったため、常駐でほぼ解消。
|
||||
- 「実行時に自動で翻訳する」: GenerateUI Generate(欄の文を置き換え)/Lab Run(各回のAPIワークフローのコピーを翻訳)/Video Plan Run(送信するプロンプトだけ翻訳)。Lab/Video はプランを原文のまま残す。失敗時は生成せず中断し、理由を進行状況欄に残す(終了トーストが上書きするため)。
|
||||
- サーバー側に翻訳結果の上限付きキャッシュ(1024件)。設定は `settings.json` の `prompt_translate_*`。
|
||||
- 実機確認: Z-Image で Generate 時自動翻訳と Lab Run(2枚)を実生成、Video Plan Run は `/prompt` 送信をブラウザで横取りして英訳の送信を確認。
|
||||
|
||||
### 修正: PLaMo を選んだときの各翻訳ボタン
|
||||
|
||||
- Prompt タブ AI アシスタント・AI TOOL タブ・サイドパネル A タブの翻訳は、指示文+本文(または指示の system メッセージ)で送っていたため、PLaMo では指示文まで翻訳される/日本語⇔英語の自動切替になり ZH→EN・EN→ZH・自由入力が誤った言語で出ていた。PLaMo 系モデルのときは先頭 system に `[plamo-translate]` と `target_lang`/`source_lang` を書き本文だけを送る(モデルカードの Ollama/LM Studio 向け方式)。言語名(French・フランス語・français 等)を PLaMo の言語コードに変換する `static/js/plamo-chat.js` を追加(サイドパネルは import できないためインラインコピー)。
|
||||
- Prompt タブ AI アシスタントに応答待ちの表示を追加(初回はモデル読み込みで十数秒かかることがある)。
|
||||
|
||||
### 修正: Video Plan のプロンプト等がテンプレートの値のまま送られていた
|
||||
|
||||
- 新しいフロントエンドで保存したワークフローは、サブグラフのインスタンスノードに名前付きの値のコピー `widgets_values_named` を持ち、`convertUiToApi()` はそちらを優先する。`applyBlockToWorkflow` は `widgets_values` だけを書いていたため、Plan のプロンプト・Duration・Turbo 等が無視されていた(手元の動画テンプレート8本中7本)。書き込み後に `widgets_values_named` へ反映する `_syncNamedWidgetValues` を追加。修正後は12本中11本で反映(残る `minimaax_h3_fast.json` はプロンプトを LiveChatStream から配線で受けるため対象外)。
|
||||
|
||||
### ヘルプ
|
||||
|
||||
- 設定タブ(helpSettings26)と GenerateUI(helpGen33)にプロンプト翻訳を追加(en/ja/zh)。
|
||||
|
||||
### マニュアル v0.9.9版(ja/en)
|
||||
|
||||
- 新節 5-10「日本語のままプロンプトを書く(プロンプト翻訳)」(準備・モデルの大きさ・GPU/CPU常駐・🌐と原文・実行時の自動翻訳・Lab/Video Plan)。7-5・10-2 に PLaMo の注記、11-4 に Video Plan の🌐と v0.9.9 修正の注意、11-5 の表・12-3 のトラブル表・付録の用語集と参考リンクを追記。対象バージョンを v0.9.9 に更新し、全章MD・ZIPを `manual/rebuild_repo_edition.py` で再生成(v0.9.6版は削除)。
|
||||
- EPUB(画像付き、75枚=新規3枚: 設定のプロンプト翻訳・生成UIで翻訳後・Labモーダル)を Google ドライブで公開。
|
||||
|
||||
## v0.9.8(2026-11-14)
|
||||
|
||||
### セキュリティ: クライアントから受け取るパスを解決前に検証
|
||||
|
||||
- `py/config.py` に `is_safe_rel` / `resolve_untrusted` を追加。文字列だけで正規化・配下チェックし、UNC・ドライブ相対・NUL 混入を最初の `resolve()`/`exists()` より前に拒否(Windows では UNC を解決した時点でアカウント資格情報付きで相手ホストへ接続するため)。UNC は許可ルート自体が UNC の場合のみ許可。G'MIC 結果・Gallery のパス受け取りに適用(ea44cf4)。
|
||||
- 追加の強化: 受け付ける形をドライブレター絶対パス(Windows)または許可された UNC に限定し、NT デバイス形式(`\??\...`、`\.\...`)等を拒否。Eagle の SVG パスで配下チェック前に `os.path.isfile` していた箇所を修正(f39227a)。
|
||||
- ※v0.9.8 はリリース時に README バッジと DEVLOG の更新が漏れていたため、v0.9.9 で追記。
|
||||
|
||||
## v0.9.6(2026-11-12)
|
||||
|
||||
### マニュアル v0.9.6版(ja/en)
|
||||
|
||||
+7
-7
@@ -27,7 +27,7 @@ A comprehensive workflow, asset management, and generation UI plugin for [ComfyU
|
||||
- Built-in AI tools (translation and more)
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Features (tab -> feature names)
|
||||
|
||||
@@ -50,7 +50,7 @@ Setting / Results / Plan JSON sub-panels, Per-column keyframes, Model column —
|
||||
3-column layout, AI chat assistant, Image attachment, Translation, Prompt presets, Preset Manager, Group management, Clipboard copy, GenUI Set, Wildcard/Style tab bar, Wildcard input toolbar, Wildcard file manager, Style manager, Form / Table toggle, Table view — Presets / Presets Group / Wildcards / Style
|
||||
|
||||
### Settings Tab
|
||||
2-column layout, Collapsible sections, Theme selection, Theme customization, Workflows directory, Gallery output directory, Eagle connection, CivitAI Host, CivitAI API Key, Default workflow, Default Checkpoint, VRAM Management (Ollama), Video Playback Volume, Data Management, Text Size, RAW JSON Colors, GenerateUI Model Tab Highlight, Wildcard Integration, G'MIC-Qt Integration, Language
|
||||
2-column layout, Collapsible sections, Theme selection, Theme customization, Workflows directory, Gallery output directory, Eagle connection, CivitAI Host, CivitAI API Key, Default workflow, Default Checkpoint, VRAM Management (Ollama), Video Playback Volume, Data Management, Text Size, RAW JSON Colors, GenerateUI Model Tab Highlight, Wildcard Integration, G'MIC-Qt Integration, Prompt Translation (native language → English), Language
|
||||
|
||||
### Gallery Tab (v0.3.44)
|
||||
Output / ImagePrompt / Style_Catalog / Metadata sub-tabs, Image browser, Folder tree root label, Thumbnail / Table views, Folder management, File operations, Download, MP4 video support, MP4 embedded metadata, WebM support, Embedded metadata reading fixes, Audio support, File conversion, Move To folder filter, Semantic Search, Audio waveform colors, Info tab: dimensions & duration, Multi-select, Image Compare, Prompt search, Server-side filtering, Group management, Thumbnail F button, Favorites, Detail panel, Prompt tab, Nanobanana tab, Gemini / Nano Banana nodes, MOSS-Image in GenerateUI, Faster GenerateUI Check / conversion, Partner API nodes in GenerateUI, Workflow viewer, GenUI button, Image Edit button, Send GenUI Image button, Send to LI node button, Send to LV node button, Send CC Layout / Send CC Image buttons, Search clear (✕), Clear all filters (✕ Clear), Workflow auto-save, Output folder configurable, SVG file support, PSD file support, 3D model support, Performance
|
||||
@@ -128,12 +128,12 @@ Samples for the Feeder tab: `ws_sdxl_image_feeder_i2i` / `ws_dwpose_image_feeder
|
||||
|
||||
## Manual
|
||||
|
||||
A beginner-oriented manual (covers v0.9.6, text-only edition without screenshots) is available in [`docs/manual/`](docs/manual/):
|
||||
A beginner-oriented manual (covers v0.9.9, text-only edition without screenshots) is available in [`docs/manual/`](docs/manual/):
|
||||
|
||||
- English: [`docs/manual/en/`](docs/manual/en/) — ZIP: `Workflow_Studio_Manual_v0.9.6_en.zip`
|
||||
- 日本語: [`docs/manual/`](docs/manual/) — ZIP: `Workflow_Studio_Manual_v0.9.6.zip`
|
||||
- English: [`docs/manual/en/`](docs/manual/en/) — ZIP: `Workflow_Studio_Manual_v0.9.9_en.zip`
|
||||
- 日本語: [`docs/manual/`](docs/manual/) — ZIP: `Workflow_Studio_Manual_v0.9.9.zip`
|
||||
|
||||
An EPUB edition with screenshots (Japanese and English, v0.9.6) is available on [Google Drive](https://drive.google.com/drive/folders/14mOHcnLYrFgNrOo6fy7ELyFjjWrAoU-_?usp=sharing).
|
||||
An EPUB edition with screenshots (Japanese and English, v0.9.9) is available on [Google Drive](https://drive.google.com/drive/folders/14mOHcnLYrFgNrOo6fy7ELyFjjWrAoU-_?usp=sharing).
|
||||
|
||||
---
|
||||
|
||||
@@ -171,7 +171,7 @@ Click the **camera icon** (next to the W button) in ComfyUI's top bar to capture
|
||||
|
||||
### Optional
|
||||
|
||||
- **[Ollama](https://ollama.com/)** — for AI chat assistant, translation, and VLM features
|
||||
- **[Ollama](https://ollama.com/)** — for AI chat assistant, translation, and VLM features; also runs PLaMo 3 Translate for Settings → Prompt Translation (`ollama pull hf.co/pfnet/plamo-3-translate-2b-GGUF:Q4_K_M`, or the Download button there)
|
||||
- **[LM Studio](https://lmstudio.ai/)** — alternative backend for translation and VLM (OpenAI-compatible API)
|
||||
- **[Lemonade Server](https://lemonade-server.ai/)** — alternative backend for translation, chat, and VLM (OpenAI-compatible API)
|
||||
- **[Unsloth](https://unsloth.ai/)** — alternative backend for translation, chat, and VLM (OpenAI-compatible API); needs an API key set via `UNSLOTH_API_KEY` in a `.env` file, or Unsloth's Keyless API access → "Chat and inference" turned on — `pip install -r requirements.txt` for `python-dotenv`
|
||||
|
||||
@@ -23,7 +23,7 @@ A comprehensive workflow, asset management, and generation UI plugin for [ComfyU
|
||||
- Built-in AI tools (translation and more)
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -257,6 +257,7 @@ An experimental batch generator: runs the workflow currently loaded in GenerateU
|
||||
- **GenerateUI Model Tab Highlight** (v0.3.98) — pick the color used to mark Checkpoint/VAE/Diffusion Model/Text Encoder/ControlNet/Hypernetwork labels and the LoRA column header in GenerateUI's Model tab when the loaded workflow actually contains that type of node; default bright green; changes apply immediately, Reset Defaults restores it; saved to `localStorage` under `wfm_settings.modelTabActiveColor`; the same color and mechanism also drives the Video tab's field highlighting (v0.5.4, see above)
|
||||
- **Wildcard Integration** — link the WFS wildcard directory to ComfyUI-Impact-Pack's `wildcards/` directory (directory junction on Windows, symlink on other OS); existing WFS wildcard files are automatically migrated; requires [ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack)
|
||||
- **G'MIC-Qt Integration** — set the path to `gmic_qt.exe` for the Image Edit Filter tool; download G'MIC-Qt Standalone from `gmic.eu/download.html` (Windows: `gmic_qt-win64.zip`), extract anywhere, then enter the full path to `gmic_qt.exe` and click Save
|
||||
- **Prompt Translation (native language → English)** (v0.9.9) — off by default. Adds a 🌐 button to the GenerateUI Positive/Negative prompts, the Lab prompt modal and the Video Plan block prompt that translates only the parts written in your own language into English with a local Ollama model, keeping prompt syntax as it is (commas, `(word:1.2)`, `[ ]`, `{a|b}`, `<lora:...>`, `__wildcards__`, `embedding:name`, `BREAK`); "Original" restores the text. Model: [PLaMo 3 Translate](https://huggingface.co/pfnet/plamo-3-translate-2b-GGUF) 0.8B / 2B / 8B / 31B (Download button pulls it into Ollama; free for personal and non-commercial use under the PLaMo Community License) or any other Ollama model. Source language auto-detect or one of 51 languages; run on GPU (about 0.1 s per phrase, optionally unloaded after each translation) or **CPU kept loaded** (about 0.3 s per phrase, no VRAM, preloaded when GenerateUI opens). **Translate automatically when running**: GenerateUI Generate (replaces the prompt text), Lab Run and Video Plan Run (translate only what is sent; the plan keeps your text); if translation fails nothing is generated. Server routes: `POST /api/wfm/translate`, `GET /api/wfm/translate/config|models`, `POST /api/wfm/translate/pull|warmup|unload`
|
||||
- **Language** — English / Japanese / Chinese
|
||||
|
||||
</details>
|
||||
@@ -607,12 +608,12 @@ Samples for the Feeder tab: `ws_sdxl_image_feeder_i2i` / `ws_dwpose_image_feeder
|
||||
|
||||
## Manual
|
||||
|
||||
A beginner-oriented manual (covers v0.9.6, text-only edition without screenshots) is available in [`docs/manual/`](docs/manual/):
|
||||
A beginner-oriented manual (covers v0.9.9, text-only edition without screenshots) is available in [`docs/manual/`](docs/manual/):
|
||||
|
||||
- English: [`docs/manual/en/`](docs/manual/en/) — ZIP: `Workflow_Studio_Manual_v0.9.6_en.zip`
|
||||
- 日本語: [`docs/manual/`](docs/manual/) — ZIP: `Workflow_Studio_Manual_v0.9.6.zip`
|
||||
- English: [`docs/manual/en/`](docs/manual/en/) — ZIP: `Workflow_Studio_Manual_v0.9.9_en.zip`
|
||||
- 日本語: [`docs/manual/`](docs/manual/) — ZIP: `Workflow_Studio_Manual_v0.9.9.zip`
|
||||
|
||||
An EPUB edition with screenshots (Japanese and English, v0.9.6) is available on [Google Drive](https://drive.google.com/drive/folders/14mOHcnLYrFgNrOo6fy7ELyFjjWrAoU-_?usp=sharing).
|
||||
An EPUB edition with screenshots (Japanese and English, v0.9.9) is available on [Google Drive](https://drive.google.com/drive/folders/14mOHcnLYrFgNrOo6fy7ELyFjjWrAoU-_?usp=sharing).
|
||||
|
||||
---
|
||||
|
||||
@@ -650,7 +651,7 @@ Click the **camera icon** (next to the W button) in ComfyUI's top bar to capture
|
||||
|
||||
### Optional
|
||||
|
||||
- **[Ollama](https://ollama.com/)** — for AI chat assistant, translation, and VLM features
|
||||
- **[Ollama](https://ollama.com/)** — for AI chat assistant, translation, and VLM features; also runs PLaMo 3 Translate for Settings → Prompt Translation (`ollama pull hf.co/pfnet/plamo-3-translate-2b-GGUF:Q4_K_M`, or the Download button there)
|
||||
- **[LM Studio](https://lmstudio.ai/)** — alternative backend for translation and VLM (OpenAI-compatible API)
|
||||
- **[Lemonade Server](https://lemonade-server.ai/)** — alternative backend for translation, chat, and VLM (OpenAI-compatible API)
|
||||
- **[Unsloth](https://unsloth.ai/)** — alternative backend for translation, chat, and VLM (OpenAI-compatible API); needs an API key set via `UNSLOTH_API_KEY` in a `.env` file, or Unsloth's Keyless API access → "Chat and inference" turned on — `pip install -r requirements.txt` for `python-dotenv`
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
ノードが苦手でも大丈夫 ― 画像生成をもっと手軽に、もっと整理して
|
||||
|
||||
- 対象バージョン:Workflow Studio v0.9.6
|
||||
- 対象バージョン:Workflow Studio v0.9.9
|
||||
- 著者:ketle-man
|
||||
- © 2026 ketle-man
|
||||
|
||||
このマニュアルは、Kindle版をもとにした **画像なしのテキスト版** です。
|
||||
章ごとのファイルは `chapters/` フォルダ、全章をまとめたファイルは `Workflow_Studio_Manual_v0.9.6.md` です。
|
||||
章ごとのファイルは `chapters/` フォルダ、全章をまとめたファイルは `Workflow_Studio_Manual_v0.9.9.md` です。
|
||||
画面の例は省略しているため、実際の画面はWorkflow Studioのヘルプタブとあわせてご覧ください。
|
||||
|
||||
## 目次
|
||||
@@ -29,4 +29,4 @@
|
||||
- [13_付録](chapters/13_付録.md)
|
||||
- [14_おわりに](chapters/14_おわりに.md)
|
||||
|
||||
全章まとめ:[Workflow_Studio_Manual_v0.9.6.md](Workflow_Studio_Manual_v0.9.6.md)
|
||||
全章まとめ:[Workflow_Studio_Manual_v0.9.9.md](Workflow_Studio_Manual_v0.9.9.md)
|
||||
|
||||
Binary file not shown.
+147
-3
@@ -2,12 +2,12 @@
|
||||
|
||||
ノードが苦手でも大丈夫 ― 画像生成をもっと手軽に、もっと整理して
|
||||
|
||||
- 対象バージョン:Workflow Studio v0.9.6
|
||||
- 対象バージョン:Workflow Studio v0.9.9
|
||||
- 著者:ketle-man
|
||||
- © 2026 ketle-man
|
||||
|
||||
このマニュアルは、Kindle版をもとにした **画像なしのテキスト版** です。
|
||||
章ごとのファイルは `chapters/` フォルダ、全章をまとめたファイルは `Workflow_Studio_Manual_v0.9.6.md` です。
|
||||
章ごとのファイルは `chapters/` フォルダ、全章をまとめたファイルは `Workflow_Studio_Manual_v0.9.9.md` です。
|
||||
画面の例は省略しているため、実際の画面はWorkflow Studioのヘルプタブとあわせてご覧ください。
|
||||
|
||||
---
|
||||
@@ -143,7 +143,7 @@ ComfyUIの「自由さ」はそのままに、「分かりやすさ」と「手
|
||||
|
||||
## 本書の対象バージョンと環境
|
||||
|
||||
- **Workflow Studio**:v0.9.6
|
||||
- **Workflow Studio**:v0.9.9
|
||||
- **ComfyUI**:本書執筆時点の最新版
|
||||
- **OS**:Windows(画面の例はWindowsのものです)
|
||||
|
||||
@@ -1547,12 +1547,127 @@ v0.8.6〜は、こうしたノードを含むワークフローも生成UIで実
|
||||
> 画像のメタデータを見るMetadataタブとサイドパネルの **I** タブでも、Nano Bananaで作った画像を読めます(v0.8.5〜)。
|
||||
> プロンプトは、Positive/Negativeに分けずに全文が1件で表示されます。
|
||||
|
||||
## 5-10 日本語のままプロンプトを書く(プロンプト翻訳、v0.9.9〜)
|
||||
|
||||
多くの画像生成モデルは、英語のプロンプトで学習されています。
|
||||
**プロンプト翻訳** をオンにすると、プロンプトを日本語で書き、ボタン1つで英語に直せます。
|
||||
Stable Diffusion web UI(A1111)の翻訳拡張機能と同じように、プロンプトの中の **日本語の部分だけ** を翻訳します。
|
||||
|
||||
翻訳には、パソコンで動くAI(**Ollama**)と翻訳専用のモデル **PLaMo 3 Translate** を使います。
|
||||
インターネット上の翻訳サービスにプロンプトを送ることはありません。
|
||||
|
||||
> **事前に必要なもの**
|
||||
> **Ollama** をインストールして起動しておきます(第10章 10-1)。翻訳モデルのダウンロードは、設定画面からできます(下記)。
|
||||
|
||||
### 準備:設定でオンにする
|
||||
1. **設定** タブの **プロンプト翻訳(自国語 → 英語)** を開きます。
|
||||
2. **生成UIでプロンプト翻訳を使う** にチェックを入れます。
|
||||
3. **翻訳モデル** で、使うモデルを選びます。まだOllamaに入っていないモデルには **(未取得)** と付いています。選んで **ダウンロード** を押すと、Ollamaに取り込まれます。
|
||||
4. **テスト** を押して、見本のプロンプトが英語になれば準備完了です。
|
||||
|
||||
PLaMo 3 Translate には4つの大きさがあります。
|
||||
|
||||
| モデル | 大きさ | 特徴 |
|
||||
| --- | --- | --- |
|
||||
| 0.8B | 約0.6GB | 最速。短いタグの翻訳なら十分 |
|
||||
| 2B | 約1.8GB | おすすめ。文章の翻訳も正確 |
|
||||
| 8B | 約5.3GB | より高品質 |
|
||||
| 31B | 約19.5GB | 最高品質。メモリに余裕がある人向け |
|
||||
|
||||
> **ポイント**
|
||||
> 0.8Bは、文章の翻訳で意味がずれることがあります(例:「カメラは後ろから追いかける」が「her camera following behind」=「彼女のカメラが後ろについてくる」になった)。
|
||||
> 動画のプロンプトのように文章で書くことが多いなら、2B以上を選びましょう。
|
||||
|
||||
設定のほかの項目は、次のとおりです。
|
||||
|
||||
| 項目 | 内容 |
|
||||
| --- | --- |
|
||||
| 入力する言語 | ふつうは **自動判定** のままでよい。日本語だけで書くなら「日本語」を選ぶと確実 |
|
||||
| Ollama URL | Ollamaのアドレス。空欄なら `http://localhost:11434` |
|
||||
| 翻訳モデルの実行場所 | **GPU** か **CPUで常駐** か(下記) |
|
||||
| 実行時に自動で翻訳する | Generateなどを押したときに自動で翻訳する(下記) |
|
||||
|
||||
PLaMo以外のOllamaのモデル(ふだんチャットに使っているモデルなど)も、**その他のOllamaモデル** から選べます。その場合は「英語に翻訳して」という指示を付けて送ります。
|
||||
|
||||
### 翻訳モデルをどこで動かすか
|
||||
| 実行場所 | 1語句あたりの時間 | 向いている人 |
|
||||
| --- | --- | --- |
|
||||
| GPU(1回の翻訳が最速・VRAMを使う) | 約0.1秒 | GPUのメモリに余裕がある |
|
||||
| CPUで常駐(VRAMを使わず、再読み込みの待ちなし) | 約0.2〜0.3秒 | 画像生成にGPUのメモリをすべて使いたい |
|
||||
|
||||
GPUのときは、**翻訳後に翻訳モデルをVRAMから解放する** をオンにしておくと、画像生成の前にGPUのメモリを空けられます(初期値はオン)。
|
||||
ただし、翻訳のたびにモデルの読み込み直しで **約4秒** 待つことになります。
|
||||
|
||||
**CPUで常駐** にすると、モデルをメインメモリに置いたままにします(2Bで約2.7GB)。
|
||||
GPUのメモリを使わず、生成UIにワークフローを読み込んだときに裏で読み込んでおくので、最初の翻訳から待たされません。
|
||||
|
||||
> **ポイント**
|
||||
> Ollamaは、ほかのモデル(AI TOOLのチャット用など)を読み込むときに、常駐させた翻訳モデルを外すことがあります。
|
||||
> その場合は、次の翻訳のときに自動で読み込み直します(その1回だけ数秒かかります)。
|
||||
|
||||
### 使い方:🌐ボタンで翻訳する
|
||||
プロンプト翻訳をオンにすると、InputタブのPositive Prompt・Negative Promptの **↺** ボタンの右に **🌐** ボタンが表示されます。
|
||||
|
||||
1. プロンプト欄に、日本語で書きます。英語のタグと混ぜてもかまいません。
|
||||
|
||||
```
|
||||
masterpiece, 夕焼けの海辺に立つ少女、(長い黒髪:1.2), <lora:test_lora:0.8>, {猫耳|狐耳}, 水彩画風
|
||||
```
|
||||
|
||||
2. **🌐** を押します。
|
||||
3. 日本語の部分だけが英語になります。
|
||||
|
||||
```
|
||||
masterpiece, A girl standing by the seaside at sunset, (long black hair:1.2), <lora:test_lora:0.8>, {cat ears|fox ears}, watercolor style
|
||||
```
|
||||
|
||||
次のようなプロンプトの書き方(記法)は、翻訳せずにそのまま残します。
|
||||
|
||||
- カンマ・改行
|
||||
- 重み付け `(語句:1.2)`、`[ ]`
|
||||
- ランダムに選ぶ `{A|B}`、ワイルドカード `__名前__`
|
||||
- LoRAの指定 `<lora:名前:0.8>`、`embedding:名前`
|
||||
- `BREAK`、`AND`
|
||||
|
||||
翻訳したあとに **原文** ボタンが表示されます。押すと、翻訳する前の文に戻ります(Ctrl+Zでも戻せます)。
|
||||
|
||||
> **注意**
|
||||
> 翻訳されるのは、英数字以外の文字を含む部分だけです。英語だけの部分は、そのまま残ります。
|
||||
> 反対に、é などのアクセント付きの文字を含む英単語は、翻訳の対象になります。
|
||||
|
||||
### 実行時に自動で翻訳する
|
||||
設定の **実行時に自動で翻訳する** で、どの実行のときに自動で翻訳するかを、チェックボックスで選べます。
|
||||
|
||||
| チェックボックス | 動き |
|
||||
| --- | --- |
|
||||
| 生成UI - Generate | Generateを押すと、Positive/Negativeの欄の日本語を英語に置き換えてから生成する |
|
||||
| 生成UI Lab - Run | Labの実行で、送る内容だけを翻訳する。Labのプランには日本語が残る |
|
||||
| Video Plan - Run | Videoタブ Planの実行で、送るプロンプトだけを翻訳する。プランには日本語が残る |
|
||||
|
||||
LabとVideo Planでは、プランを日本語のまま残すので、あとから日本語で書き直せます。
|
||||
生成した画像や動画の情報(メタデータ)には、実際に送った英語のプロンプトが記録されます。
|
||||
|
||||
> **注意**
|
||||
> 自動翻訳がオンのときに翻訳に失敗すると(Ollamaが起動していないなど)、日本語のまま生成しないよう、生成を止めます。
|
||||
> LabとVideo Planでは、止まった理由が進行状況の欄に表示されます。
|
||||
|
||||
### Labのモーダルと、Video Planでも使える
|
||||
🌐ボタンは、次の欄にもあります。使い方は同じです。
|
||||
|
||||
- Lab(5-7)のプロンプト列のセルをダブルクリックして開く画面の、Positive・Negativeの欄
|
||||
- Videoタブ Planサブタブの、ブロックの **Prompt** の欄(第11章 11-4)
|
||||
|
||||
> **ポイント**
|
||||
> PLaMo 3 Translate は、個人の利用や趣味、研究などの非商用の利用なら無料で使えます(PLaMoコミュニティライセンス)。
|
||||
> 商用で使う場合は、ライセンスの条件を確認してください。
|
||||
|
||||
> **この章(後半)のまとめ**
|
||||
> - Batchサブタブで、モデル・プロンプト・サンプラーなどを1種類ずつ変えながらまとめて生成できる
|
||||
> - Batch Queueの列のチェックで種類を選び、Generateで開始。終わったらチェックを外す
|
||||
> - Feederは、たくさんの画像を1枚ずつワークフローに送り込む機能。専用ノードが必要
|
||||
> - Labは、複数の設定を別々のタイミングで切り替えられる上級者向けの機能
|
||||
> - パートナーAPIノード(Nano Bananaなど)は、`.env` の `COMFY_API_KEY` を設定すれば実行できる。クレジットが消費される
|
||||
> - プロンプト翻訳をオンにすると、日本語で書いたプロンプトを🌐ボタンで英語にできる。記法はそのまま残り、Generate・Lab・Video Planの実行時に自動で翻訳することもできる
|
||||
|
||||
---
|
||||
|
||||
@@ -1972,6 +2087,10 @@ Workflow Studioにはたくさんのスタイルが付属しています。
|
||||
|
||||
英語のプロンプトの意味を知りたいときは、反対に **EN→JA** で日本語に訳せます。
|
||||
|
||||
> **ポイント**
|
||||
> 生成UIのプロンプト欄で直接翻訳したいときは、第5章 5-10 の **プロンプト翻訳**(🌐ボタン)が便利です。重み付けやLoRAの指定を残したまま、日本語の部分だけを英語にできます。
|
||||
> AI Assistantの翻訳にも、翻訳専用のモデル **PLaMo 3 Translate** を選べます(v0.9.9〜。以前は指示文まで翻訳されたり、ZH→ENで日本語が出たりしていました)。
|
||||
|
||||
### AIと相談しながらプロンプトを作る
|
||||
入力欄に質問を書いて **Send**(または Enter キー)を押すと、AIとチャットできます。
|
||||
|
||||
@@ -2621,6 +2740,11 @@ Workflow Studio本体の **AI TOOL** タブを開きます。
|
||||
> 小さなモデルは、翻訳せずに元の文章をそのまま返してしまうことがあります。
|
||||
> その場合は「モデルが原文をそのまま返しました」と表示されるので、別のモデルを試してください。
|
||||
|
||||
> **ポイント**
|
||||
> 翻訳専用のモデル **PLaMo 3 Translate**(第5章 5-10)を、設定のモデルに選ぶこともできます(v0.9.9〜)。
|
||||
> 日本語・英語・中国語のほか、Free(自由入力)では French・フランス語・français のような言語名で、PLaMoが対応する言語を指定できます。PLaMoが対応していない言語(ポルトガル語など)を翻訳先にすると、エラーになります。
|
||||
> WS LibraryのAタブの翻訳も同じです。
|
||||
|
||||
### Chat
|
||||
入力欄に話しかけて、**Enter** で送信します(**Shift+Enter** で改行)。
|
||||
会話の流れを覚えているので、「もっと明るい雰囲気にして」のような続けての依頼もできます。
|
||||
@@ -3107,6 +3231,14 @@ Image Editのいちばん便利な使い方が、**インペイント** です
|
||||
|
||||
そのほか、動画の好きな場面を静止画として保存する **Frame** や、動画をGIFアニメに変換する **GIF** の機能もあります。
|
||||
|
||||
> **ポイント**
|
||||
> Planサブタブのブロックの **Prompt** の欄にも、プロンプト翻訳の **🌐** ボタンがあります(第5章 5-10)。
|
||||
> 設定の **Video Plan - Run** をオンにすると、日本語のプロンプトのまま実行して、送る内容だけを英語にできます。
|
||||
|
||||
> **注意**
|
||||
> v0.9.8までは、新しいComfyUIで保存したワークフローを使うと、Planで入力したプロンプト・長さ(Duration)・Turboの設定が反映されず、ワークフローに保存されている値のまま生成されていました(v0.9.9で修正)。
|
||||
> 以前と同じPlanを実行しても、Planの値が反映されるようになったため、結果が変わることがあります。
|
||||
|
||||
### アセット登録と音声変換(v0.8.9〜)
|
||||
- **アセット登録** - 左側の動画ソースパネルのボタンです。パソコン内の動画を、outputフォルダ(`output/video`)へコピーして「Video Assets」グループに登録します。
|
||||
登録すると、Assetサブタブの一覧に出て、Editなどで使えるようになります。
|
||||
@@ -3157,6 +3289,7 @@ Image Editのいちばん便利な使い方が、**インペイント** です
|
||||
| CivitAI APIキー | Civitaiの情報取得用(省略可) | 第6章 |
|
||||
| Eagle 連携 (オプション) | 生成画像をEagleに自動保存 | 第8章 |
|
||||
| VRAM調整(Ollama) | 生成前にAIモデルをメモリから降ろす | 第10章 |
|
||||
| プロンプト翻訳(自国語 → 英語) | 日本語のプロンプトを🌐ボタンや実行時の自動翻訳で英語にする | 第5章 5-10 |
|
||||
| データ管理 | 設定やデータのバックアップ・復元 | 下記 |
|
||||
|
||||
### デフォルトワークフロー
|
||||
@@ -3396,6 +3529,15 @@ Batchサブタブを開き、Batch Queueの列のチェックをすべてオフ
|
||||
|
||||
いつも同じモデルを使いたい場合は、設定タブの **デフォルトCheckpoint** を使うと便利です(第11章 11-5)。
|
||||
|
||||
### プロンプト翻訳の🌐ボタンが出ない・翻訳できない
|
||||
| 症状 | 原因 | 対処 |
|
||||
| --- | --- | --- |
|
||||
| 🌐ボタンが表示されない | プロンプト翻訳がオフになっている | 設定タブの **プロンプト翻訳(自国語 → 英語)** で **生成UIでプロンプト翻訳を使う** にチェックを入れる(第5章 5-10) |
|
||||
| 「翻訳エラー」と出る | Ollamaが起動していない、または翻訳モデルがOllamaに無い | Ollamaを起動する。設定の **翻訳モデル** に **(未取得)** と出ていれば **ダウンロード** を押す |
|
||||
| 押しても何も変わらない(「翻訳する部分がありません」) | 英語(英数字)だけのプロンプトになっている | 翻訳されるのは英数字以外の文字を含む部分だけ。英語の部分はそのまま残る |
|
||||
| 自動翻訳がオンのとき、生成が始まらない | 翻訳に失敗したので、日本語のまま生成しないよう止めた | 上の「翻訳エラー」と同じ対処をする。LabとVideo Planでは、理由が進行状況の欄に出る |
|
||||
| 訳がおかしい | 小さいモデル(0.8B)は、文章で意味がずれることがある | 設定の **翻訳モデル** で2B以上を選ぶ |
|
||||
|
||||
### 同じ設定なのに、同じ画像にならない
|
||||
生成パネルのシードのモードが **Random** になっていませんか?
|
||||
同じ画像を作るには、シードを **Workflow**(ワークフローのシードをそのまま使う)か **Fixed**(Seed欄の値で固定)にする必要があります。
|
||||
@@ -3595,6 +3737,7 @@ Workflow Studioの **ヘルプ** タブにも、トラブルシューティン
|
||||
| LoRA | 絵柄やキャラクターなどを追加で覚えさせる小さなモデル | 第6章 |
|
||||
| OCR | 画像に写っている文字を読み取ること | 第10章 |
|
||||
| Ollama | ローカルAIを手軽に動かせるソフト | 第10章 |
|
||||
| PLaMo 3 Translate | Preferred Networks(PFN)の翻訳専用AIモデル。プロンプト翻訳で使う | 第5章 |
|
||||
| T2I(Text to Image) | 文章(プロンプト)から画像を作ること | 第5章 |
|
||||
| TagSpace | タグ・プロンプト・生成画像を意味の近さで3Dの空間に並べて表示するタブ | 第11章 |
|
||||
| VAE | 画像の色合いや細部を仕上げるモデル | 第6章 |
|
||||
@@ -3668,6 +3811,7 @@ Workflow Studioの **ヘルプ** タブにも、トラブルシューティン
|
||||
| --- | --- | --- |
|
||||
| Civitai(https://civitai.com/) | モデルの入手・情報取得 | 第6章 |
|
||||
| Ollama(https://ollama.com/) | ローカルAI | 第10章 |
|
||||
| PLaMo 3 Translate(https://huggingface.co/pfnet/plamo-3-translate-2b-GGUF) | プロンプト翻訳の翻訳モデル | 第5章 |
|
||||
| LM Studio(https://lmstudio.ai/) | ローカルAI | 第10章 |
|
||||
| Lemonade Server(https://lemonade-server.ai/) | ローカルAI | 第10章 |
|
||||
| Eagle(https://eagle.cool/) | 画像管理ソフト | 第8章 |
|
||||
Binary file not shown.
@@ -129,7 +129,7 @@ ComfyUIの「自由さ」はそのままに、「分かりやすさ」と「手
|
||||
|
||||
## 本書の対象バージョンと環境
|
||||
|
||||
- **Workflow Studio**:v0.9.6
|
||||
- **Workflow Studio**:v0.9.9
|
||||
- **ComfyUI**:本書執筆時点の最新版
|
||||
- **OS**:Windows(画面の例はWindowsのものです)
|
||||
|
||||
|
||||
@@ -235,9 +235,124 @@ v0.8.6〜は、こうしたノードを含むワークフローも生成UIで実
|
||||
> 画像のメタデータを見るMetadataタブとサイドパネルの **I** タブでも、Nano Bananaで作った画像を読めます(v0.8.5〜)。
|
||||
> プロンプトは、Positive/Negativeに分けずに全文が1件で表示されます。
|
||||
|
||||
## 5-10 日本語のままプロンプトを書く(プロンプト翻訳、v0.9.9〜)
|
||||
|
||||
多くの画像生成モデルは、英語のプロンプトで学習されています。
|
||||
**プロンプト翻訳** をオンにすると、プロンプトを日本語で書き、ボタン1つで英語に直せます。
|
||||
Stable Diffusion web UI(A1111)の翻訳拡張機能と同じように、プロンプトの中の **日本語の部分だけ** を翻訳します。
|
||||
|
||||
翻訳には、パソコンで動くAI(**Ollama**)と翻訳専用のモデル **PLaMo 3 Translate** を使います。
|
||||
インターネット上の翻訳サービスにプロンプトを送ることはありません。
|
||||
|
||||
> **事前に必要なもの**
|
||||
> **Ollama** をインストールして起動しておきます(第10章 10-1)。翻訳モデルのダウンロードは、設定画面からできます(下記)。
|
||||
|
||||
### 準備:設定でオンにする
|
||||
1. **設定** タブの **プロンプト翻訳(自国語 → 英語)** を開きます。
|
||||
2. **生成UIでプロンプト翻訳を使う** にチェックを入れます。
|
||||
3. **翻訳モデル** で、使うモデルを選びます。まだOllamaに入っていないモデルには **(未取得)** と付いています。選んで **ダウンロード** を押すと、Ollamaに取り込まれます。
|
||||
4. **テスト** を押して、見本のプロンプトが英語になれば準備完了です。
|
||||
|
||||
PLaMo 3 Translate には4つの大きさがあります。
|
||||
|
||||
| モデル | 大きさ | 特徴 |
|
||||
| --- | --- | --- |
|
||||
| 0.8B | 約0.6GB | 最速。短いタグの翻訳なら十分 |
|
||||
| 2B | 約1.8GB | おすすめ。文章の翻訳も正確 |
|
||||
| 8B | 約5.3GB | より高品質 |
|
||||
| 31B | 約19.5GB | 最高品質。メモリに余裕がある人向け |
|
||||
|
||||
> **ポイント**
|
||||
> 0.8Bは、文章の翻訳で意味がずれることがあります(例:「カメラは後ろから追いかける」が「her camera following behind」=「彼女のカメラが後ろについてくる」になった)。
|
||||
> 動画のプロンプトのように文章で書くことが多いなら、2B以上を選びましょう。
|
||||
|
||||
設定のほかの項目は、次のとおりです。
|
||||
|
||||
| 項目 | 内容 |
|
||||
| --- | --- |
|
||||
| 入力する言語 | ふつうは **自動判定** のままでよい。日本語だけで書くなら「日本語」を選ぶと確実 |
|
||||
| Ollama URL | Ollamaのアドレス。空欄なら `http://localhost:11434` |
|
||||
| 翻訳モデルの実行場所 | **GPU** か **CPUで常駐** か(下記) |
|
||||
| 実行時に自動で翻訳する | Generateなどを押したときに自動で翻訳する(下記) |
|
||||
|
||||
PLaMo以外のOllamaのモデル(ふだんチャットに使っているモデルなど)も、**その他のOllamaモデル** から選べます。その場合は「英語に翻訳して」という指示を付けて送ります。
|
||||
|
||||
### 翻訳モデルをどこで動かすか
|
||||
| 実行場所 | 1語句あたりの時間 | 向いている人 |
|
||||
| --- | --- | --- |
|
||||
| GPU(1回の翻訳が最速・VRAMを使う) | 約0.1秒 | GPUのメモリに余裕がある |
|
||||
| CPUで常駐(VRAMを使わず、再読み込みの待ちなし) | 約0.2〜0.3秒 | 画像生成にGPUのメモリをすべて使いたい |
|
||||
|
||||
GPUのときは、**翻訳後に翻訳モデルをVRAMから解放する** をオンにしておくと、画像生成の前にGPUのメモリを空けられます(初期値はオン)。
|
||||
ただし、翻訳のたびにモデルの読み込み直しで **約4秒** 待つことになります。
|
||||
|
||||
**CPUで常駐** にすると、モデルをメインメモリに置いたままにします(2Bで約2.7GB)。
|
||||
GPUのメモリを使わず、生成UIにワークフローを読み込んだときに裏で読み込んでおくので、最初の翻訳から待たされません。
|
||||
|
||||
> **ポイント**
|
||||
> Ollamaは、ほかのモデル(AI TOOLのチャット用など)を読み込むときに、常駐させた翻訳モデルを外すことがあります。
|
||||
> その場合は、次の翻訳のときに自動で読み込み直します(その1回だけ数秒かかります)。
|
||||
|
||||
### 使い方:🌐ボタンで翻訳する
|
||||
プロンプト翻訳をオンにすると、InputタブのPositive Prompt・Negative Promptの **↺** ボタンの右に **🌐** ボタンが表示されます。
|
||||
|
||||
1. プロンプト欄に、日本語で書きます。英語のタグと混ぜてもかまいません。
|
||||
|
||||
```
|
||||
masterpiece, 夕焼けの海辺に立つ少女、(長い黒髪:1.2), <lora:test_lora:0.8>, {猫耳|狐耳}, 水彩画風
|
||||
```
|
||||
|
||||
2. **🌐** を押します。
|
||||
3. 日本語の部分だけが英語になります。
|
||||
|
||||
```
|
||||
masterpiece, A girl standing by the seaside at sunset, (long black hair:1.2), <lora:test_lora:0.8>, {cat ears|fox ears}, watercolor style
|
||||
```
|
||||
|
||||
次のようなプロンプトの書き方(記法)は、翻訳せずにそのまま残します。
|
||||
|
||||
- カンマ・改行
|
||||
- 重み付け `(語句:1.2)`、`[ ]`
|
||||
- ランダムに選ぶ `{A|B}`、ワイルドカード `__名前__`
|
||||
- LoRAの指定 `<lora:名前:0.8>`、`embedding:名前`
|
||||
- `BREAK`、`AND`
|
||||
|
||||
翻訳したあとに **原文** ボタンが表示されます。押すと、翻訳する前の文に戻ります(Ctrl+Zでも戻せます)。
|
||||
|
||||
> **注意**
|
||||
> 翻訳されるのは、英数字以外の文字を含む部分だけです。英語だけの部分は、そのまま残ります。
|
||||
> 反対に、é などのアクセント付きの文字を含む英単語は、翻訳の対象になります。
|
||||
|
||||
### 実行時に自動で翻訳する
|
||||
設定の **実行時に自動で翻訳する** で、どの実行のときに自動で翻訳するかを、チェックボックスで選べます。
|
||||
|
||||
| チェックボックス | 動き |
|
||||
| --- | --- |
|
||||
| 生成UI - Generate | Generateを押すと、Positive/Negativeの欄の日本語を英語に置き換えてから生成する |
|
||||
| 生成UI Lab - Run | Labの実行で、送る内容だけを翻訳する。Labのプランには日本語が残る |
|
||||
| Video Plan - Run | Videoタブ Planの実行で、送るプロンプトだけを翻訳する。プランには日本語が残る |
|
||||
|
||||
LabとVideo Planでは、プランを日本語のまま残すので、あとから日本語で書き直せます。
|
||||
生成した画像や動画の情報(メタデータ)には、実際に送った英語のプロンプトが記録されます。
|
||||
|
||||
> **注意**
|
||||
> 自動翻訳がオンのときに翻訳に失敗すると(Ollamaが起動していないなど)、日本語のまま生成しないよう、生成を止めます。
|
||||
> LabとVideo Planでは、止まった理由が進行状況の欄に表示されます。
|
||||
|
||||
### Labのモーダルと、Video Planでも使える
|
||||
🌐ボタンは、次の欄にもあります。使い方は同じです。
|
||||
|
||||
- Lab(5-7)のプロンプト列のセルをダブルクリックして開く画面の、Positive・Negativeの欄
|
||||
- Videoタブ Planサブタブの、ブロックの **Prompt** の欄(第11章 11-4)
|
||||
|
||||
> **ポイント**
|
||||
> PLaMo 3 Translate は、個人の利用や趣味、研究などの非商用の利用なら無料で使えます(PLaMoコミュニティライセンス)。
|
||||
> 商用で使う場合は、ライセンスの条件を確認してください。
|
||||
|
||||
> **この章(後半)のまとめ**
|
||||
> - Batchサブタブで、モデル・プロンプト・サンプラーなどを1種類ずつ変えながらまとめて生成できる
|
||||
> - Batch Queueの列のチェックで種類を選び、Generateで開始。終わったらチェックを外す
|
||||
> - Feederは、たくさんの画像を1枚ずつワークフローに送り込む機能。専用ノードが必要
|
||||
> - Labは、複数の設定を別々のタイミングで切り替えられる上級者向けの機能
|
||||
> - パートナーAPIノード(Nano Bananaなど)は、`.env` の `COMFY_API_KEY` を設定すれば実行できる。クレジットが消費される
|
||||
> - プロンプト翻訳をオンにすると、日本語で書いたプロンプトを🌐ボタンで英語にできる。記法はそのまま残り、Generate・Lab・Video Planの実行時に自動で翻訳することもできる
|
||||
|
||||
@@ -227,6 +227,10 @@ Workflow Studioにはたくさんのスタイルが付属しています。
|
||||
|
||||
英語のプロンプトの意味を知りたいときは、反対に **EN→JA** で日本語に訳せます。
|
||||
|
||||
> **ポイント**
|
||||
> 生成UIのプロンプト欄で直接翻訳したいときは、第5章 5-10 の **プロンプト翻訳**(🌐ボタン)が便利です。重み付けやLoRAの指定を残したまま、日本語の部分だけを英語にできます。
|
||||
> AI Assistantの翻訳にも、翻訳専用のモデル **PLaMo 3 Translate** を選べます(v0.9.9〜。以前は指示文まで翻訳されたり、ZH→ENで日本語が出たりしていました)。
|
||||
|
||||
### AIと相談しながらプロンプトを作る
|
||||
入力欄に質問を書いて **Send**(または Enter キー)を押すと、AIとチャットできます。
|
||||
|
||||
|
||||
@@ -110,6 +110,11 @@ Workflow Studio本体の **AI TOOL** タブを開きます。
|
||||
> 小さなモデルは、翻訳せずに元の文章をそのまま返してしまうことがあります。
|
||||
> その場合は「モデルが原文をそのまま返しました」と表示されるので、別のモデルを試してください。
|
||||
|
||||
> **ポイント**
|
||||
> 翻訳専用のモデル **PLaMo 3 Translate**(第5章 5-10)を、設定のモデルに選ぶこともできます(v0.9.9〜)。
|
||||
> 日本語・英語・中国語のほか、Free(自由入力)では French・フランス語・français のような言語名で、PLaMoが対応する言語を指定できます。PLaMoが対応していない言語(ポルトガル語など)を翻訳先にすると、エラーになります。
|
||||
> WS LibraryのAタブの翻訳も同じです。
|
||||
|
||||
### Chat
|
||||
入力欄に話しかけて、**Enter** で送信します(**Shift+Enter** で改行)。
|
||||
会話の流れを覚えているので、「もっと明るい雰囲気にして」のような続けての依頼もできます。
|
||||
|
||||
@@ -155,6 +155,14 @@ Image Editのいちばん便利な使い方が、**インペイント** です
|
||||
|
||||
そのほか、動画の好きな場面を静止画として保存する **Frame** や、動画をGIFアニメに変換する **GIF** の機能もあります。
|
||||
|
||||
> **ポイント**
|
||||
> Planサブタブのブロックの **Prompt** の欄にも、プロンプト翻訳の **🌐** ボタンがあります(第5章 5-10)。
|
||||
> 設定の **Video Plan - Run** をオンにすると、日本語のプロンプトのまま実行して、送る内容だけを英語にできます。
|
||||
|
||||
> **注意**
|
||||
> v0.9.8までは、新しいComfyUIで保存したワークフローを使うと、Planで入力したプロンプト・長さ(Duration)・Turboの設定が反映されず、ワークフローに保存されている値のまま生成されていました(v0.9.9で修正)。
|
||||
> 以前と同じPlanを実行しても、Planの値が反映されるようになったため、結果が変わることがあります。
|
||||
|
||||
### アセット登録と音声変換(v0.8.9〜)
|
||||
- **アセット登録** - 左側の動画ソースパネルのボタンです。パソコン内の動画を、outputフォルダ(`output/video`)へコピーして「Video Assets」グループに登録します。
|
||||
登録すると、Assetサブタブの一覧に出て、Editなどで使えるようになります。
|
||||
@@ -205,6 +213,7 @@ Image Editのいちばん便利な使い方が、**インペイント** です
|
||||
| CivitAI APIキー | Civitaiの情報取得用(省略可) | 第6章 |
|
||||
| Eagle 連携 (オプション) | 生成画像をEagleに自動保存 | 第8章 |
|
||||
| VRAM調整(Ollama) | 生成前にAIモデルをメモリから降ろす | 第10章 |
|
||||
| プロンプト翻訳(自国語 → 英語) | 日本語のプロンプトを🌐ボタンや実行時の自動翻訳で英語にする | 第5章 5-10 |
|
||||
| データ管理 | 設定やデータのバックアップ・復元 | 下記 |
|
||||
|
||||
### デフォルトワークフロー
|
||||
|
||||
@@ -88,6 +88,15 @@ Batchサブタブを開き、Batch Queueの列のチェックをすべてオフ
|
||||
|
||||
いつも同じモデルを使いたい場合は、設定タブの **デフォルトCheckpoint** を使うと便利です(第11章 11-5)。
|
||||
|
||||
### プロンプト翻訳の🌐ボタンが出ない・翻訳できない
|
||||
| 症状 | 原因 | 対処 |
|
||||
| --- | --- | --- |
|
||||
| 🌐ボタンが表示されない | プロンプト翻訳がオフになっている | 設定タブの **プロンプト翻訳(自国語 → 英語)** で **生成UIでプロンプト翻訳を使う** にチェックを入れる(第5章 5-10) |
|
||||
| 「翻訳エラー」と出る | Ollamaが起動していない、または翻訳モデルがOllamaに無い | Ollamaを起動する。設定の **翻訳モデル** に **(未取得)** と出ていれば **ダウンロード** を押す |
|
||||
| 押しても何も変わらない(「翻訳する部分がありません」) | 英語(英数字)だけのプロンプトになっている | 翻訳されるのは英数字以外の文字を含む部分だけ。英語の部分はそのまま残る |
|
||||
| 自動翻訳がオンのとき、生成が始まらない | 翻訳に失敗したので、日本語のまま生成しないよう止めた | 上の「翻訳エラー」と同じ対処をする。LabとVideo Planでは、理由が進行状況の欄に出る |
|
||||
| 訳がおかしい | 小さいモデル(0.8B)は、文章で意味がずれることがある | 設定の **翻訳モデル** で2B以上を選ぶ |
|
||||
|
||||
### 同じ設定なのに、同じ画像にならない
|
||||
生成パネルのシードのモードが **Random** になっていませんか?
|
||||
同じ画像を作るには、シードを **Workflow**(ワークフローのシードをそのまま使う)か **Fixed**(Seed欄の値で固定)にする必要があります。
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
| LoRA | 絵柄やキャラクターなどを追加で覚えさせる小さなモデル | 第6章 |
|
||||
| OCR | 画像に写っている文字を読み取ること | 第10章 |
|
||||
| Ollama | ローカルAIを手軽に動かせるソフト | 第10章 |
|
||||
| PLaMo 3 Translate | Preferred Networks(PFN)の翻訳専用AIモデル。プロンプト翻訳で使う | 第5章 |
|
||||
| T2I(Text to Image) | 文章(プロンプト)から画像を作ること | 第5章 |
|
||||
| TagSpace | タグ・プロンプト・生成画像を意味の近さで3Dの空間に並べて表示するタブ | 第11章 |
|
||||
| VAE | 画像の色合いや細部を仕上げるモデル | 第6章 |
|
||||
@@ -127,6 +128,7 @@
|
||||
| --- | --- | --- |
|
||||
| Civitai(https://civitai.com/) | モデルの入手・情報取得 | 第6章 |
|
||||
| Ollama(https://ollama.com/) | ローカルAI | 第10章 |
|
||||
| PLaMo 3 Translate(https://huggingface.co/pfnet/plamo-3-translate-2b-GGUF) | プロンプト翻訳の翻訳モデル | 第5章 |
|
||||
| LM Studio(https://lmstudio.ai/) | ローカルAI | 第10章 |
|
||||
| Lemonade Server(https://lemonade-server.ai/) | ローカルAI | 第10章 |
|
||||
| Eagle(https://eagle.cool/) | 画像管理ソフト | 第8章 |
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
Don't Be Afraid of Nodes - Generate Images More Easily and Keep Them Organized
|
||||
|
||||
- Covers: Workflow Studio v0.9.6
|
||||
- Covers: Workflow Studio v0.9.9
|
||||
- Author: ketle-man
|
||||
- (c) 2026 ketle-man
|
||||
|
||||
This is the **text-only edition** (without screenshots) of the Kindle book.
|
||||
Chapter files are in the `chapters/` folder; the whole book in one file is `Workflow_Studio_Manual_v0.9.6_en.md`.
|
||||
Chapter files are in the `chapters/` folder; the whole book in one file is `Workflow_Studio_Manual_v0.9.9_en.md`.
|
||||
Screen examples are omitted, so please use the book together with Workflow Studio's Help tab.
|
||||
|
||||
## Contents
|
||||
@@ -29,4 +29,4 @@ Screen examples are omitted, so please use the book together with Workflow Studi
|
||||
- [13_Appendix](chapters/13_Appendix.md)
|
||||
- [14_Afterword](chapters/14_Afterword.md)
|
||||
|
||||
Whole book: [Workflow_Studio_Manual_v0.9.6_en.md](Workflow_Studio_Manual_v0.9.6_en.md)
|
||||
Whole book: [Workflow_Studio_Manual_v0.9.9_en.md](Workflow_Studio_Manual_v0.9.9_en.md)
|
||||
|
||||
Binary file not shown.
+145
-3
@@ -2,12 +2,12 @@
|
||||
|
||||
Don't Be Afraid of Nodes - Generate Images More Easily and Keep Them Organized
|
||||
|
||||
- Covers: Workflow Studio v0.9.6
|
||||
- Covers: Workflow Studio v0.9.9
|
||||
- Author: ketle-man
|
||||
- (c) 2026 ketle-man
|
||||
|
||||
This is the **text-only edition** (without screenshots) of the Kindle book.
|
||||
Chapter files are in the `chapters/` folder; the whole book in one file is `Workflow_Studio_Manual_v0.9.6_en.md`.
|
||||
Chapter files are in the `chapters/` folder; the whole book in one file is `Workflow_Studio_Manual_v0.9.9_en.md`.
|
||||
Screen examples are omitted, so please use the book together with Workflow Studio's Help tab.
|
||||
|
||||
---
|
||||
@@ -143,7 +143,7 @@ It is written so that you can follow it if you know basic computer operations (c
|
||||
|
||||
## Version and environment covered
|
||||
|
||||
- **Workflow Studio**: v0.9.6
|
||||
- **Workflow Studio**: v0.9.9
|
||||
- **ComfyUI**: the latest version at the time of writing
|
||||
- **OS**: Windows (screen examples are from Windows)
|
||||
|
||||
@@ -1542,12 +1542,125 @@ The workflows you can use in GenerateUI have increased as follows. They are used
|
||||
> The Metadata tab and the side panel's **I** tab can also read images made with Nano Banana (v0.8.5 and later).
|
||||
> The prompt appears as one whole entry, not split into Positive/Negative.
|
||||
|
||||
## 5-10 Writing prompts in your own language (Prompt Translation, v0.9.9 and later)
|
||||
|
||||
Most image generation models were trained on English prompts.
|
||||
With **Prompt Translation** turned on, you can write a prompt in your own language and turn it into English with one button.
|
||||
Like the translation extensions for Stable Diffusion web UI (A1111), it translates **only the parts written in your own language**.
|
||||
|
||||
Translation uses AI running on your PC (**Ollama**) and the translation model **PLaMo 3 Translate**, which supports 51 languages besides English (Japanese, Chinese, Korean, French, German, Spanish, Russian, Thai, Vietnamese and more).
|
||||
Your prompts are not sent to an online translation service.
|
||||
|
||||
> **What you need first**
|
||||
> Install and start **Ollama** (Chapter 10, section 10-1). The translation model can be downloaded from the Settings screen (see below).
|
||||
|
||||
### Preparation: turn it on in Settings
|
||||
1. In the **Settings** tab, open **Prompt Translation (native language -> English)**.
|
||||
2. Check **Enable prompt translation in GenerateUI**.
|
||||
3. Choose the model in **Translation model**. Models not yet in Ollama are marked **(not downloaded)**; choose one and press **Download** to pull it into Ollama.
|
||||
4. Press **Test**. When the sample prompt comes back in English, you are ready.
|
||||
|
||||
PLaMo 3 Translate comes in four sizes.
|
||||
|
||||
| Model | Size | Notes |
|
||||
| --- | --- | --- |
|
||||
| 0.8B | about 0.6 GB | Fastest. Enough for short tags |
|
||||
| 2B | about 1.8 GB | Recommended. Translates sentences accurately too |
|
||||
| 8B | about 5.3 GB | Higher quality |
|
||||
| 31B | about 19.5 GB | Highest quality, for PCs with plenty of memory |
|
||||
|
||||
> **Tip**
|
||||
> 0.8B sometimes shifts the meaning of a sentence (for example, "the camera follows from behind" in Japanese became "her camera following behind").
|
||||
> If you often write prompts as sentences, such as video prompts, choose 2B or larger.
|
||||
|
||||
The other settings are:
|
||||
|
||||
| Setting | What it does |
|
||||
| --- | --- |
|
||||
| Source language | Usually leave it at **Auto-detect**. If you always write in one language, choosing it makes detection certain |
|
||||
| Ollama URL | Ollama's address. Blank means `http://localhost:11434` |
|
||||
| Where to run the translation model | **GPU** or **CPU, kept loaded** (see below) |
|
||||
| Translate automatically when running | Translates automatically when you press Generate and so on (see below) |
|
||||
|
||||
Other Ollama models (such as the one you use for chat) can also be chosen under **Other Ollama models**. They are sent the text with a "translate into English" instruction.
|
||||
|
||||
### Where to run the translation model
|
||||
| Where | Time per phrase | Good for |
|
||||
| --- | --- | --- |
|
||||
| GPU (fastest per translation, uses VRAM) | about 0.1 s | GPUs with VRAM to spare |
|
||||
| CPU, kept loaded (no VRAM, no reload wait) | about 0.2-0.3 s | Keeping all VRAM for image generation |
|
||||
|
||||
On GPU, turning on **Unload the translation model from VRAM after translating** frees VRAM before image generation (on by default).
|
||||
However, every translation then waits **about 4 seconds** while the model loads again.
|
||||
|
||||
**CPU, kept loaded** keeps the model in system RAM (about 2.7 GB for 2B).
|
||||
It uses no VRAM and is loaded in the background when a workflow is loaded into GenerateUI, so even the first translation does not wait.
|
||||
|
||||
> **Tip**
|
||||
> When Ollama loads another model (for example the AI TOOL chat model), it may drop the resident translation model.
|
||||
> It then loads again automatically at the next translation (that one translation takes a few seconds).
|
||||
|
||||
### Using it: translate with the globe button
|
||||
With Prompt Translation on, a **globe (🌐)** button appears to the right of the **↺** button of Positive Prompt and Negative Prompt on the Input tab.
|
||||
|
||||
1. Write the prompt in your own language. Mixing in English tags is fine.
|
||||
|
||||
```
|
||||
masterpiece, 夕焼けの海辺に立つ少女、(長い黒髪:1.2), <lora:test_lora:0.8>, {猫耳|狐耳}, 水彩画風
|
||||
```
|
||||
|
||||
2. Press the globe button.
|
||||
3. Only the parts in your language become English.
|
||||
|
||||
```
|
||||
masterpiece, A girl standing by the seaside at sunset, (long black hair:1.2), <lora:test_lora:0.8>, {cat ears|fox ears}, watercolor style
|
||||
```
|
||||
|
||||
The following prompt syntax is kept as it is, without translating:
|
||||
|
||||
- Commas and line breaks
|
||||
- Weights `(word:1.2)` and `[ ]`
|
||||
- Random choice `{A|B}` and wildcards `__name__`
|
||||
- LoRA `<lora:name:0.8>` and `embedding:name`
|
||||
- `BREAK` and `AND`
|
||||
|
||||
After translating, an **Original** button appears. Press it to go back to the text before translation (Ctrl+Z also works).
|
||||
|
||||
> **Note**
|
||||
> Only parts that contain letters outside ASCII are translated; English-only parts stay as they are.
|
||||
> Conversely, an English word that contains an accented letter such as é is translated too.
|
||||
|
||||
### Translating automatically when running
|
||||
Under **Translate automatically when running** in Settings, checkboxes choose when to translate automatically.
|
||||
|
||||
- **GenerateUI - Generate**: pressing Generate replaces the text of the Positive/Negative fields with the translation, then generates
|
||||
- **GenerateUI Lab - Run**: a Lab run translates only what is sent. The Lab plan keeps your text
|
||||
- **Video Plan - Run**: a Video tab Plan run translates only the prompts that are sent. The plan keeps your text
|
||||
|
||||
Because Lab and Video Plan keep your text, you can keep editing the plan in your own language.
|
||||
The information saved with the generated images and videos (metadata) records the English prompt that was actually sent.
|
||||
|
||||
> **Note**
|
||||
> If translation fails while automatic translation is on (for example, Ollama is not running), generation stops so that untranslated text is not sent.
|
||||
> For Lab and Video Plan, the reason is shown in the progress line.
|
||||
|
||||
### Also in the Lab modal and Video Plan
|
||||
The globe button is also on these fields, and works the same way:
|
||||
|
||||
- The Positive and Negative fields of the screen that opens when you double-click a cell in Lab's prompt column (5-7)
|
||||
- The **Prompt** field of a block in the Video tab's Plan subtab (Chapter 11, section 11-4)
|
||||
|
||||
> **Tip**
|
||||
> PLaMo 3 Translate is free for personal use, hobbies, research and other non-commercial use (PLaMo Community License).
|
||||
> Check the license terms before using it commercially.
|
||||
|
||||
> **Summary of this chapter (second half)**
|
||||
> - With the Batch subtab, you can generate in bulk while changing the model, prompt, sampler and so on, one kind at a time
|
||||
> - Choose the kind with the Batch Queue column checkbox and start with Generate. Turn the check off when done
|
||||
> - Feeder is a feature that feeds many images one at a time into a workflow. A dedicated node is required
|
||||
> - Lab is an advanced feature that can switch several settings at separate timings
|
||||
> - Partner API nodes (Nano Banana and others) run once `COMFY_API_KEY` is set in `.env`. They consume credits
|
||||
> - With Prompt Translation on, the globe button turns a prompt written in your own language into English. Prompt syntax is kept, and Generate, Lab and Video Plan runs can translate automatically
|
||||
|
||||
---
|
||||
|
||||
@@ -1963,6 +2076,10 @@ With the buttons shown below the translation result, you can use the result.
|
||||
|
||||
When you want to know the meaning of an English prompt, you can conversely translate it with **EN->JA**.
|
||||
|
||||
> **Tip**
|
||||
> To translate directly in GenerateUI's prompt fields, **Prompt Translation** (the globe button) in Chapter 5, section 5-10 is handy. It turns only the parts in your language into English and keeps weights and LoRA tags.
|
||||
> The AI Assistant's translation can also use the translation model **PLaMo 3 Translate** (v0.9.9 and later; before that, the instruction text was translated too, and ZH->EN came out in Japanese).
|
||||
|
||||
### Making prompts while consulting the AI
|
||||
Write a question in the input box and press **Send** (or the Enter key) to chat with the AI.
|
||||
|
||||
@@ -2611,6 +2728,11 @@ The screen is divided into four parts, all shown at the same time.
|
||||
> Small models sometimes return the original text as it is without translating.
|
||||
> In that case a message says the model returned the original text, so try another model.
|
||||
|
||||
> **Tip**
|
||||
> You can also choose the translation model **PLaMo 3 Translate** (Chapter 5, section 5-10) as the model in Settings (v0.9.9 and later).
|
||||
> Besides Japanese, English and Chinese, **Free** accepts language names such as French, フランス語 or français for any language PLaMo supports. A target language PLaMo does not support (such as Portuguese) gives an error.
|
||||
> The same applies to the translation in WS Library's A tab.
|
||||
|
||||
### Chat
|
||||
Talk in the input box and send with **Enter** (**Shift + Enter** for a line break).
|
||||
It remembers the flow of the conversation, so follow-up requests such as "make it a brighter mood" also work.
|
||||
@@ -3096,6 +3218,14 @@ You load a matching workflow with **Load in Video** in the Workflow tab and use
|
||||
|
||||
There are also the **Frame** feature, which saves a favorite scene of a video as a still image, and **GIF**, which converts a video to an animated GIF.
|
||||
|
||||
> **Tip**
|
||||
> The **Prompt** field of a Plan block also has the Prompt Translation globe button (Chapter 5, section 5-10).
|
||||
> With **Video Plan - Run** turned on in Settings, you can run the plan with prompts in your own language and send only English.
|
||||
|
||||
> **Note**
|
||||
> Up to v0.9.8, with workflows saved by newer ComfyUI versions, the prompt, length (Duration) and Turbo settings entered in Plan were not applied, and the values saved in the workflow were used instead (fixed in v0.9.9).
|
||||
> Because Plan values are applied now, running the same plan as before may give different results.
|
||||
|
||||
### Register as Asset and audio conversion (v0.8.9 and later)
|
||||
- **Register as Asset** - a button in the Video Source panel on the left. It copies a video from your PC into the output folder (`output/video`) and registers it in the "Video Assets" group.
|
||||
Once registered, it appears in the Asset subtab list and can be used in Edit and elsewhere.
|
||||
@@ -3144,6 +3274,7 @@ Here we mainly introduce items not covered in earlier chapters.
|
||||
| CivitAI API Key | For fetching Civitai information (optional) | Chapter 6 |
|
||||
| Eagle integration (optional) | Automatically save generated images to Eagle | Chapter 8 |
|
||||
| VRAM Management (Ollama) | Unload AI models from memory before generation | Chapter 10 |
|
||||
| Prompt Translation (native language -> English) | Turn prompts in your own language into English with the globe button or automatic translation when running | Chapter 5, 5-10 |
|
||||
| Data Management | Backing up and restoring settings and data | Below |
|
||||
|
||||
### Default Workflow
|
||||
@@ -3379,6 +3510,15 @@ In the **Model** subtab of GenerateUI, choose the model you want to use again an
|
||||
|
||||
If you always want to use the same model, the **Default Checkpoint** in the Settings tab is handy (Chapter 11, section 11-5).
|
||||
|
||||
### The Prompt Translation globe button does not appear or translation fails
|
||||
| Symptom | Cause | Fix |
|
||||
| --- | --- | --- |
|
||||
| No globe button | Prompt Translation is off | In the Settings tab, open **Prompt Translation (native language -> English)** and check **Enable prompt translation in GenerateUI** (Chapter 5, section 5-10) |
|
||||
| "Translation error" appears | Ollama is not running, or the translation model is not in Ollama | Start Ollama. If **Translation model** shows **(not downloaded)**, press **Download** |
|
||||
| Nothing changes ("Nothing to translate") | The prompt is English (ASCII) only | Only parts with letters outside ASCII are translated; English parts stay as they are |
|
||||
| Generation does not start with automatic translation on | Translation failed, so generation stopped instead of sending untranslated text | Same fix as "Translation error". In Lab and Video Plan the reason is shown in the progress line |
|
||||
| The translation is off | Small models (0.8B) can shift the meaning of sentences | Choose 2B or larger in **Translation model** |
|
||||
|
||||
### It does not become the same image even with the same settings
|
||||
Is the seed mode in the generate panel set to **Random**?
|
||||
To make the same image, you need to set the seed to **Workflow** (use the workflow's seed as it is) or **Fixed** (fix it to the value in the Seed field).
|
||||
@@ -3558,6 +3698,7 @@ The main terms that appear in this book, in alphabetical order.
|
||||
| Node set | Several nodes and their connections saved together | Chapter 9 |
|
||||
| OCR | Reading the text shown in an image | Chapter 10 |
|
||||
| Ollama | Software for running local AI easily | Chapter 10 |
|
||||
| PLaMo 3 Translate | A translation AI model by Preferred Networks (PFN), used by Prompt Translation | Chapter 5 |
|
||||
| Port number | A number for distinguishing which software to communicate with inside one computer | Chapter 2 |
|
||||
| Positive prompt | A prompt where you write what you want drawn | Chapter 7 |
|
||||
| Preset | A pair of positive and negative prompts saved together | Chapter 7 |
|
||||
@@ -3646,6 +3787,7 @@ The main terms that appear in this book, in alphabetical order.
|
||||
| --- | --- | --- |
|
||||
| Civitai (https://civitai.com/) | Getting models, fetching information | Chapter 6 |
|
||||
| Ollama (https://ollama.com/) | Local AI | Chapter 10 |
|
||||
| PLaMo 3 Translate (https://huggingface.co/pfnet/plamo-3-translate-2b-GGUF) | Translation model for Prompt Translation | Chapter 5 |
|
||||
| LM Studio (https://lmstudio.ai/) | Local AI | Chapter 10 |
|
||||
| Lemonade Server (https://lemonade-server.ai/) | Local AI | Chapter 10 |
|
||||
| Eagle (https://eagle.cool/) | Image-management software | Chapter 8 |
|
||||
Binary file not shown.
@@ -129,7 +129,7 @@ It is written so that you can follow it if you know basic computer operations (c
|
||||
|
||||
## Version and environment covered
|
||||
|
||||
- **Workflow Studio**: v0.9.6
|
||||
- **Workflow Studio**: v0.9.9
|
||||
- **ComfyUI**: the latest version at the time of writing
|
||||
- **OS**: Windows (screen examples are from Windows)
|
||||
|
||||
|
||||
@@ -232,9 +232,122 @@ The workflows you can use in GenerateUI have increased as follows. They are used
|
||||
> The Metadata tab and the side panel's **I** tab can also read images made with Nano Banana (v0.8.5 and later).
|
||||
> The prompt appears as one whole entry, not split into Positive/Negative.
|
||||
|
||||
## 5-10 Writing prompts in your own language (Prompt Translation, v0.9.9 and later)
|
||||
|
||||
Most image generation models were trained on English prompts.
|
||||
With **Prompt Translation** turned on, you can write a prompt in your own language and turn it into English with one button.
|
||||
Like the translation extensions for Stable Diffusion web UI (A1111), it translates **only the parts written in your own language**.
|
||||
|
||||
Translation uses AI running on your PC (**Ollama**) and the translation model **PLaMo 3 Translate**, which supports 51 languages besides English (Japanese, Chinese, Korean, French, German, Spanish, Russian, Thai, Vietnamese and more).
|
||||
Your prompts are not sent to an online translation service.
|
||||
|
||||
> **What you need first**
|
||||
> Install and start **Ollama** (Chapter 10, section 10-1). The translation model can be downloaded from the Settings screen (see below).
|
||||
|
||||
### Preparation: turn it on in Settings
|
||||
1. In the **Settings** tab, open **Prompt Translation (native language -> English)**.
|
||||
2. Check **Enable prompt translation in GenerateUI**.
|
||||
3. Choose the model in **Translation model**. Models not yet in Ollama are marked **(not downloaded)**; choose one and press **Download** to pull it into Ollama.
|
||||
4. Press **Test**. When the sample prompt comes back in English, you are ready.
|
||||
|
||||
PLaMo 3 Translate comes in four sizes.
|
||||
|
||||
| Model | Size | Notes |
|
||||
| --- | --- | --- |
|
||||
| 0.8B | about 0.6 GB | Fastest. Enough for short tags |
|
||||
| 2B | about 1.8 GB | Recommended. Translates sentences accurately too |
|
||||
| 8B | about 5.3 GB | Higher quality |
|
||||
| 31B | about 19.5 GB | Highest quality, for PCs with plenty of memory |
|
||||
|
||||
> **Tip**
|
||||
> 0.8B sometimes shifts the meaning of a sentence (for example, "the camera follows from behind" in Japanese became "her camera following behind").
|
||||
> If you often write prompts as sentences, such as video prompts, choose 2B or larger.
|
||||
|
||||
The other settings are:
|
||||
|
||||
| Setting | What it does |
|
||||
| --- | --- |
|
||||
| Source language | Usually leave it at **Auto-detect**. If you always write in one language, choosing it makes detection certain |
|
||||
| Ollama URL | Ollama's address. Blank means `http://localhost:11434` |
|
||||
| Where to run the translation model | **GPU** or **CPU, kept loaded** (see below) |
|
||||
| Translate automatically when running | Translates automatically when you press Generate and so on (see below) |
|
||||
|
||||
Other Ollama models (such as the one you use for chat) can also be chosen under **Other Ollama models**. They are sent the text with a "translate into English" instruction.
|
||||
|
||||
### Where to run the translation model
|
||||
| Where | Time per phrase | Good for |
|
||||
| --- | --- | --- |
|
||||
| GPU (fastest per translation, uses VRAM) | about 0.1 s | GPUs with VRAM to spare |
|
||||
| CPU, kept loaded (no VRAM, no reload wait) | about 0.2-0.3 s | Keeping all VRAM for image generation |
|
||||
|
||||
On GPU, turning on **Unload the translation model from VRAM after translating** frees VRAM before image generation (on by default).
|
||||
However, every translation then waits **about 4 seconds** while the model loads again.
|
||||
|
||||
**CPU, kept loaded** keeps the model in system RAM (about 2.7 GB for 2B).
|
||||
It uses no VRAM and is loaded in the background when a workflow is loaded into GenerateUI, so even the first translation does not wait.
|
||||
|
||||
> **Tip**
|
||||
> When Ollama loads another model (for example the AI TOOL chat model), it may drop the resident translation model.
|
||||
> It then loads again automatically at the next translation (that one translation takes a few seconds).
|
||||
|
||||
### Using it: translate with the globe button
|
||||
With Prompt Translation on, a **globe (🌐)** button appears to the right of the **↺** button of Positive Prompt and Negative Prompt on the Input tab.
|
||||
|
||||
1. Write the prompt in your own language. Mixing in English tags is fine.
|
||||
|
||||
```
|
||||
masterpiece, 夕焼けの海辺に立つ少女、(長い黒髪:1.2), <lora:test_lora:0.8>, {猫耳|狐耳}, 水彩画風
|
||||
```
|
||||
|
||||
2. Press the globe button.
|
||||
3. Only the parts in your language become English.
|
||||
|
||||
```
|
||||
masterpiece, A girl standing by the seaside at sunset, (long black hair:1.2), <lora:test_lora:0.8>, {cat ears|fox ears}, watercolor style
|
||||
```
|
||||
|
||||
The following prompt syntax is kept as it is, without translating:
|
||||
|
||||
- Commas and line breaks
|
||||
- Weights `(word:1.2)` and `[ ]`
|
||||
- Random choice `{A|B}` and wildcards `__name__`
|
||||
- LoRA `<lora:name:0.8>` and `embedding:name`
|
||||
- `BREAK` and `AND`
|
||||
|
||||
After translating, an **Original** button appears. Press it to go back to the text before translation (Ctrl+Z also works).
|
||||
|
||||
> **Note**
|
||||
> Only parts that contain letters outside ASCII are translated; English-only parts stay as they are.
|
||||
> Conversely, an English word that contains an accented letter such as é is translated too.
|
||||
|
||||
### Translating automatically when running
|
||||
Under **Translate automatically when running** in Settings, checkboxes choose when to translate automatically.
|
||||
|
||||
- **GenerateUI - Generate**: pressing Generate replaces the text of the Positive/Negative fields with the translation, then generates
|
||||
- **GenerateUI Lab - Run**: a Lab run translates only what is sent. The Lab plan keeps your text
|
||||
- **Video Plan - Run**: a Video tab Plan run translates only the prompts that are sent. The plan keeps your text
|
||||
|
||||
Because Lab and Video Plan keep your text, you can keep editing the plan in your own language.
|
||||
The information saved with the generated images and videos (metadata) records the English prompt that was actually sent.
|
||||
|
||||
> **Note**
|
||||
> If translation fails while automatic translation is on (for example, Ollama is not running), generation stops so that untranslated text is not sent.
|
||||
> For Lab and Video Plan, the reason is shown in the progress line.
|
||||
|
||||
### Also in the Lab modal and Video Plan
|
||||
The globe button is also on these fields, and works the same way:
|
||||
|
||||
- The Positive and Negative fields of the screen that opens when you double-click a cell in Lab's prompt column (5-7)
|
||||
- The **Prompt** field of a block in the Video tab's Plan subtab (Chapter 11, section 11-4)
|
||||
|
||||
> **Tip**
|
||||
> PLaMo 3 Translate is free for personal use, hobbies, research and other non-commercial use (PLaMo Community License).
|
||||
> Check the license terms before using it commercially.
|
||||
|
||||
> **Summary of this chapter (second half)**
|
||||
> - With the Batch subtab, you can generate in bulk while changing the model, prompt, sampler and so on, one kind at a time
|
||||
> - Choose the kind with the Batch Queue column checkbox and start with Generate. Turn the check off when done
|
||||
> - Feeder is a feature that feeds many images one at a time into a workflow. A dedicated node is required
|
||||
> - Lab is an advanced feature that can switch several settings at separate timings
|
||||
> - Partner API nodes (Nano Banana and others) run once `COMFY_API_KEY` is set in `.env`. They consume credits
|
||||
> - With Prompt Translation on, the globe button turns a prompt written in your own language into English. Prompt syntax is kept, and Generate, Lab and Video Plan runs can translate automatically
|
||||
|
||||
@@ -223,6 +223,10 @@ With the buttons shown below the translation result, you can use the result.
|
||||
|
||||
When you want to know the meaning of an English prompt, you can conversely translate it with **EN->JA**.
|
||||
|
||||
> **Tip**
|
||||
> To translate directly in GenerateUI's prompt fields, **Prompt Translation** (the globe button) in Chapter 5, section 5-10 is handy. It turns only the parts in your language into English and keeps weights and LoRA tags.
|
||||
> The AI Assistant's translation can also use the translation model **PLaMo 3 Translate** (v0.9.9 and later; before that, the instruction text was translated too, and ZH->EN came out in Japanese).
|
||||
|
||||
### Making prompts while consulting the AI
|
||||
Write a question in the input box and press **Send** (or the Enter key) to chat with the AI.
|
||||
|
||||
|
||||
@@ -110,6 +110,11 @@ The screen is divided into four parts, all shown at the same time.
|
||||
> Small models sometimes return the original text as it is without translating.
|
||||
> In that case a message says the model returned the original text, so try another model.
|
||||
|
||||
> **Tip**
|
||||
> You can also choose the translation model **PLaMo 3 Translate** (Chapter 5, section 5-10) as the model in Settings (v0.9.9 and later).
|
||||
> Besides Japanese, English and Chinese, **Free** accepts language names such as French, フランス語 or français for any language PLaMo supports. A target language PLaMo does not support (such as Portuguese) gives an error.
|
||||
> The same applies to the translation in WS Library's A tab.
|
||||
|
||||
### Chat
|
||||
Talk in the input box and send with **Enter** (**Shift + Enter** for a line break).
|
||||
It remembers the flow of the conversation, so follow-up requests such as "make it a brighter mood" also work.
|
||||
|
||||
@@ -155,6 +155,14 @@ You load a matching workflow with **Load in Video** in the Workflow tab and use
|
||||
|
||||
There are also the **Frame** feature, which saves a favorite scene of a video as a still image, and **GIF**, which converts a video to an animated GIF.
|
||||
|
||||
> **Tip**
|
||||
> The **Prompt** field of a Plan block also has the Prompt Translation globe button (Chapter 5, section 5-10).
|
||||
> With **Video Plan - Run** turned on in Settings, you can run the plan with prompts in your own language and send only English.
|
||||
|
||||
> **Note**
|
||||
> Up to v0.9.8, with workflows saved by newer ComfyUI versions, the prompt, length (Duration) and Turbo settings entered in Plan were not applied, and the values saved in the workflow were used instead (fixed in v0.9.9).
|
||||
> Because Plan values are applied now, running the same plan as before may give different results.
|
||||
|
||||
### Register as Asset and audio conversion (v0.8.9 and later)
|
||||
- **Register as Asset** - a button in the Video Source panel on the left. It copies a video from your PC into the output folder (`output/video`) and registers it in the "Video Assets" group.
|
||||
Once registered, it appears in the Asset subtab list and can be used in Edit and elsewhere.
|
||||
@@ -203,6 +211,7 @@ Here we mainly introduce items not covered in earlier chapters.
|
||||
| CivitAI API Key | For fetching Civitai information (optional) | Chapter 6 |
|
||||
| Eagle integration (optional) | Automatically save generated images to Eagle | Chapter 8 |
|
||||
| VRAM Management (Ollama) | Unload AI models from memory before generation | Chapter 10 |
|
||||
| Prompt Translation (native language -> English) | Turn prompts in your own language into English with the globe button or automatic translation when running | Chapter 5, 5-10 |
|
||||
| Data Management | Backing up and restoring settings and data | Below |
|
||||
|
||||
### Default Workflow
|
||||
|
||||
@@ -88,6 +88,15 @@ In the **Model** subtab of GenerateUI, choose the model you want to use again an
|
||||
|
||||
If you always want to use the same model, the **Default Checkpoint** in the Settings tab is handy (Chapter 11, section 11-5).
|
||||
|
||||
### The Prompt Translation globe button does not appear or translation fails
|
||||
| Symptom | Cause | Fix |
|
||||
| --- | --- | --- |
|
||||
| No globe button | Prompt Translation is off | In the Settings tab, open **Prompt Translation (native language -> English)** and check **Enable prompt translation in GenerateUI** (Chapter 5, section 5-10) |
|
||||
| "Translation error" appears | Ollama is not running, or the translation model is not in Ollama | Start Ollama. If **Translation model** shows **(not downloaded)**, press **Download** |
|
||||
| Nothing changes ("Nothing to translate") | The prompt is English (ASCII) only | Only parts with letters outside ASCII are translated; English parts stay as they are |
|
||||
| Generation does not start with automatic translation on | Translation failed, so generation stopped instead of sending untranslated text | Same fix as "Translation error". In Lab and Video Plan the reason is shown in the progress line |
|
||||
| The translation is off | Small models (0.8B) can shift the meaning of sentences | Choose 2B or larger in **Translation model** |
|
||||
|
||||
### It does not become the same image even with the same settings
|
||||
Is the seed mode in the generate panel set to **Random**?
|
||||
To make the same image, you need to set the seed to **Workflow** (use the workflow's seed as it is) or **Fixed** (fix it to the value in the Seed field).
|
||||
|
||||
@@ -34,6 +34,7 @@ The main terms that appear in this book, in alphabetical order.
|
||||
| Node set | Several nodes and their connections saved together | Chapter 9 |
|
||||
| OCR | Reading the text shown in an image | Chapter 10 |
|
||||
| Ollama | Software for running local AI easily | Chapter 10 |
|
||||
| PLaMo 3 Translate | A translation AI model by Preferred Networks (PFN), used by Prompt Translation | Chapter 5 |
|
||||
| Port number | A number for distinguishing which software to communicate with inside one computer | Chapter 2 |
|
||||
| Positive prompt | A prompt where you write what you want drawn | Chapter 7 |
|
||||
| Preset | A pair of positive and negative prompts saved together | Chapter 7 |
|
||||
@@ -122,6 +123,7 @@ The main terms that appear in this book, in alphabetical order.
|
||||
| --- | --- | --- |
|
||||
| Civitai (https://civitai.com/) | Getting models, fetching information | Chapter 6 |
|
||||
| Ollama (https://ollama.com/) | Local AI | Chapter 10 |
|
||||
| PLaMo 3 Translate (https://huggingface.co/pfnet/plamo-3-translate-2b-GGUF) | Translation model for Prompt Translation | Chapter 5 |
|
||||
| LM Studio (https://lmstudio.ai/) | Local AI | Chapter 10 |
|
||||
| Lemonade Server (https://lemonade-server.ai/) | Local AI | Chapter 10 |
|
||||
| Eagle (https://eagle.cool/) | Image-management software | Chapter 8 |
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Prompt translation routes (GenerateUI native-language prompt input)."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from ..services import translate_service as svc
|
||||
from ..services.url_guard import require_backend_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_routes(app: web.Application):
|
||||
app.router.add_get("/api/wfm/translate/config", handle_config)
|
||||
app.router.add_get("/api/wfm/translate/models", handle_models)
|
||||
app.router.add_post("/api/wfm/translate", handle_translate)
|
||||
app.router.add_post("/api/wfm/translate/pull", handle_pull)
|
||||
app.router.add_post("/api/wfm/translate/warmup", handle_warmup)
|
||||
app.router.add_post("/api/wfm/translate/unload", handle_unload)
|
||||
|
||||
|
||||
async def handle_config(request: web.Request) -> web.Response:
|
||||
"""GET /api/wfm/translate/config - effective settings + PLaMo presets."""
|
||||
cfg = await asyncio.to_thread(svc.get_config)
|
||||
return web.json_response({"config": cfg, "presets": svc.PLAMO_PRESETS})
|
||||
|
||||
|
||||
async def handle_models(request: web.Request) -> web.Response:
|
||||
"""GET /api/wfm/translate/models - models installed in the translation Ollama."""
|
||||
try:
|
||||
models = await asyncio.to_thread(svc.list_models)
|
||||
return web.json_response({"status": "success", "models": models, "presets": svc.PLAMO_PRESETS})
|
||||
except Exception as e:
|
||||
logger.warning("Translate models error: %s", e)
|
||||
return web.json_response({"status": "error", "message": str(e), "presets": svc.PLAMO_PRESETS}, status=502)
|
||||
|
||||
|
||||
async def handle_translate(request: web.Request) -> web.Response:
|
||||
"""POST /api/wfm/translate {texts: [...], model?, source_lang?} -> {translations: [...]}"""
|
||||
try:
|
||||
body = await request.json()
|
||||
texts = body.get("texts")
|
||||
translations, cached = await asyncio.to_thread(
|
||||
svc.translate_texts, texts, body.get("model") or None, body.get("source_lang"),
|
||||
)
|
||||
return web.json_response({"status": "success", "translations": translations, "cached": cached})
|
||||
except ValueError as e:
|
||||
return web.json_response({"status": "error", "message": str(e)}, status=400)
|
||||
except Exception as e:
|
||||
logger.error("Translate error: %s", e)
|
||||
return web.json_response({"status": "error", "message": str(e)}, status=502)
|
||||
|
||||
|
||||
async def handle_warmup(request: web.Request) -> web.Response:
|
||||
"""POST /api/wfm/translate/warmup - preload the model when CPU-resident mode is on."""
|
||||
try:
|
||||
loaded = await asyncio.to_thread(svc.warmup)
|
||||
return web.json_response({"status": "success", "loaded": loaded})
|
||||
except Exception as e:
|
||||
logger.warning("Translate warmup error: %s", e)
|
||||
return web.json_response({"status": "error", "message": str(e)}, status=502)
|
||||
|
||||
|
||||
async def handle_unload(request: web.Request) -> web.Response:
|
||||
"""POST /api/wfm/translate/unload - release the resident model (device switch / disable)."""
|
||||
try:
|
||||
cfg = await asyncio.to_thread(svc.get_config)
|
||||
base_url = require_backend_url(cfg["prompt_translate_url"])
|
||||
await asyncio.to_thread(svc.unload, base_url, cfg["prompt_translate_model"])
|
||||
return web.json_response({"status": "success"})
|
||||
except Exception as e:
|
||||
return web.json_response({"status": "error", "message": str(e)}, status=502)
|
||||
|
||||
|
||||
async def handle_pull(request: web.Request) -> web.Response:
|
||||
"""POST /api/wfm/translate/pull {model} - pull one of the PLaMo presets into Ollama."""
|
||||
try:
|
||||
body = await request.json()
|
||||
model = body.get("model", "")
|
||||
if model not in svc.PLAMO_PRESETS:
|
||||
return web.json_response({"status": "error", "message": "only PLaMo presets can be pulled"}, status=400)
|
||||
cfg = await asyncio.to_thread(svc.get_config)
|
||||
base_url = require_backend_url(cfg["prompt_translate_url"])
|
||||
data = await asyncio.to_thread(
|
||||
svc._post_json, f"{base_url}/api/pull", {"model": model, "stream": False}, 3600,
|
||||
)
|
||||
if data.get("error"):
|
||||
return web.json_response({"status": "error", "message": data["error"]}, status=502)
|
||||
return web.json_response({"status": "success", "result": data.get("status", "")})
|
||||
except Exception as e:
|
||||
logger.error("Translate pull error: %s", e)
|
||||
return web.json_response({"status": "error", "message": str(e)}, status=502)
|
||||
@@ -0,0 +1,219 @@
|
||||
"""Prompt translation service (native language -> English) via Ollama.
|
||||
|
||||
PLaMo 3 Translate (pfnet/plamo-3-translate-*-GGUF) models are driven with a raw
|
||||
PFML3 prompt on /api/generate, which is the route the model card recommends for
|
||||
Ollama. Any other Ollama model is used through /api/chat with a short
|
||||
"translate into English" system prompt, so a general LLM or another MT model
|
||||
can be selected too.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import threading
|
||||
import urllib.request
|
||||
from collections import OrderedDict
|
||||
|
||||
from .settings_service import SettingsService
|
||||
from .url_guard import require_backend_url, urlopen
|
||||
|
||||
DEFAULTS = {
|
||||
"prompt_translate_enabled": False,
|
||||
"prompt_translate_url": "http://localhost:11434",
|
||||
"prompt_translate_model": "hf.co/pfnet/plamo-3-translate-2b-GGUF:Q4_K_M",
|
||||
"prompt_translate_source_lang": "auto",
|
||||
"prompt_translate_auto": False, # GenerateUI Generate
|
||||
"prompt_translate_auto_lab": False, # GenerateUI Lab Run
|
||||
"prompt_translate_auto_video": False, # Video Plan Run
|
||||
"prompt_translate_unload": True,
|
||||
# "gpu" (default) or "cpu". CPU mode keeps the model resident in system RAM:
|
||||
# no VRAM use, so it never needs unloading and skips Ollama's ~4 s reload.
|
||||
"prompt_translate_device": "gpu",
|
||||
}
|
||||
|
||||
CPU_KEEP_ALIVE = -1 # stay loaded until Ollama restarts (about 2 GB RAM for 2B Q4_K_M)
|
||||
|
||||
# Suggested PLaMo 3 Translate builds (shown in Settings even before they are pulled).
|
||||
PLAMO_PRESETS = [
|
||||
"hf.co/pfnet/plamo-3-translate-0.8b-GGUF:Q4_K_M",
|
||||
"hf.co/pfnet/plamo-3-translate-2b-GGUF:Q4_K_M",
|
||||
"hf.co/pfnet/plamo-3-translate-8b-GGUF:Q4_K_M",
|
||||
"hf.co/pfnet/plamo-3-translate-31b-GGUF:Q4_K_M",
|
||||
]
|
||||
|
||||
_PLAMO_STOP = ["<|plamo:tag|>", "<|plamo:bos|>"]
|
||||
_PLAMO_SPECIAL = re.compile(r"<\|plamo:[a-z_]+\|>")
|
||||
_LANG_RE = re.compile(r"^[a-z]{2,3}(-[A-Za-z]{2,4})?$")
|
||||
_MAX_TEXT_LEN = 4000
|
||||
_MAX_TEXTS = 200
|
||||
|
||||
_CHAT_SYSTEM = (
|
||||
"You translate text for image generation prompts into English. "
|
||||
"Output only the English translation, with no explanations, quotes or notes. "
|
||||
"Keep proper nouns, numbers and any ASCII text unchanged."
|
||||
)
|
||||
|
||||
_cache: "OrderedDict[tuple, str]" = OrderedDict()
|
||||
_CACHE_MAX = 1024
|
||||
_cache_lock = threading.Lock()
|
||||
|
||||
_settings = SettingsService()
|
||||
|
||||
|
||||
def get_config():
|
||||
data = _settings.load()
|
||||
cfg = {k: data.get(k, v) for k, v in DEFAULTS.items()}
|
||||
cfg["prompt_translate_url"] = (cfg["prompt_translate_url"] or DEFAULTS["prompt_translate_url"]).rstrip("/")
|
||||
cfg["prompt_translate_model"] = cfg["prompt_translate_model"] or DEFAULTS["prompt_translate_model"]
|
||||
return cfg
|
||||
|
||||
|
||||
def is_plamo(model: str) -> bool:
|
||||
return "plamo" in (model or "").lower()
|
||||
|
||||
|
||||
def build_pfml3(text: str, target_lang: str = "en", source_lang: str = "") -> str:
|
||||
"""Build a PFML3 translation prompt (BOS is added by llama.cpp/Ollama)."""
|
||||
src = _PLAMO_SPECIAL.sub("", text)
|
||||
src_attr = f"<|plamo:key|>lang<|plamo:val|>{source_lang}" if source_lang else ""
|
||||
return (
|
||||
"<|plamo:tag|>dataset<|plamo:msg|>translation"
|
||||
f"<|plamo:tag|>input{src_attr}<|plamo:msg|>{src}"
|
||||
f"<|plamo:tag|>output<|plamo:key|>lang<|plamo:val|>{target_lang}<|plamo:msg|>"
|
||||
)
|
||||
|
||||
|
||||
def _post_json(url: str, payload: dict, timeout: float):
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def _num_predict(text: str) -> int:
|
||||
# CJK -> English roughly doubles-to-triples the token count; leave headroom.
|
||||
return max(64, min(2048, len(text) * 4))
|
||||
|
||||
|
||||
def _device_options(device):
|
||||
return {"num_gpu": 0} if device == "cpu" else {}
|
||||
|
||||
|
||||
def _translate_one(base_url, model, text, source_lang, keep_alive, device="gpu"):
|
||||
dev = _device_options(device)
|
||||
if is_plamo(model):
|
||||
data = _post_json(f"{base_url}/api/generate", {
|
||||
"model": model,
|
||||
"prompt": build_pfml3(text, "en", source_lang),
|
||||
"raw": True,
|
||||
"stream": False,
|
||||
"keep_alive": keep_alive,
|
||||
"options": {"temperature": 0, "num_predict": _num_predict(text), "stop": _PLAMO_STOP, **dev},
|
||||
}, timeout=180)
|
||||
out = data.get("response", "")
|
||||
else:
|
||||
data = _post_json(f"{base_url}/api/chat", {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "system", "content": _CHAT_SYSTEM},
|
||||
{"role": "user", "content": text},
|
||||
],
|
||||
"stream": False,
|
||||
"think": False,
|
||||
"keep_alive": keep_alive,
|
||||
"options": {"temperature": 0, "num_predict": _num_predict(text), **dev},
|
||||
}, timeout=180)
|
||||
out = (data.get("message") or {}).get("content", "")
|
||||
out = _PLAMO_SPECIAL.sub("", out)
|
||||
return out.strip()
|
||||
|
||||
|
||||
def unload(base_url: str, model: str):
|
||||
try:
|
||||
_post_json(f"{base_url}/api/generate", {"model": model, "keep_alive": 0}, timeout=15)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def translate_texts(texts, model=None, source_lang=None):
|
||||
"""Translate each string in ``texts`` into English. Blocking (run in a thread).
|
||||
|
||||
Returns (translations, cached_count). Identical inputs are served from a
|
||||
bounded cache keyed by (model, source_lang, text).
|
||||
"""
|
||||
if not isinstance(texts, list) or len(texts) > _MAX_TEXTS:
|
||||
raise ValueError(f"texts must be a list of at most {_MAX_TEXTS} strings")
|
||||
cfg = get_config()
|
||||
base_url = require_backend_url(cfg["prompt_translate_url"])
|
||||
model = (model or cfg["prompt_translate_model"]).strip()
|
||||
src = (source_lang if source_lang is not None else cfg["prompt_translate_source_lang"]) or "auto"
|
||||
src = "" if src == "auto" else src
|
||||
if src and not _LANG_RE.match(src):
|
||||
raise ValueError("invalid source language")
|
||||
|
||||
device = "cpu" if cfg.get("prompt_translate_device") == "cpu" else "gpu"
|
||||
keep_alive = CPU_KEEP_ALIVE if device == "cpu" else "5m"
|
||||
|
||||
results = []
|
||||
cached = 0
|
||||
used_model = False
|
||||
for raw in texts:
|
||||
text = str(raw or "")[:_MAX_TEXT_LEN]
|
||||
if not text.strip():
|
||||
results.append(text)
|
||||
continue
|
||||
key = (model, src, text)
|
||||
with _cache_lock:
|
||||
hit = _cache.get(key)
|
||||
if hit is not None:
|
||||
_cache.move_to_end(key)
|
||||
if hit is not None:
|
||||
results.append(hit)
|
||||
cached += 1
|
||||
continue
|
||||
out = _translate_one(base_url, model, text, src, keep_alive, device)
|
||||
used_model = True
|
||||
with _cache_lock:
|
||||
_cache[key] = out
|
||||
_cache.move_to_end(key)
|
||||
while len(_cache) > _CACHE_MAX:
|
||||
_cache.popitem(last=False)
|
||||
results.append(out)
|
||||
|
||||
if used_model and device == "gpu" and cfg.get("prompt_translate_unload", True):
|
||||
# Free VRAM for the image model that usually runs right after.
|
||||
unload(base_url, model)
|
||||
return results, cached
|
||||
|
||||
|
||||
def warmup():
|
||||
"""Load the translation model ahead of the first translation (CPU mode).
|
||||
|
||||
In CPU mode the model is kept resident, so loading it once in the background
|
||||
when GenerateUI opens hides Ollama's ~4 s startup from the first translation.
|
||||
Returns False when there is nothing to do.
|
||||
"""
|
||||
cfg = get_config()
|
||||
if not cfg.get("prompt_translate_enabled") or cfg.get("prompt_translate_device") != "cpu":
|
||||
return False
|
||||
base_url = require_backend_url(cfg["prompt_translate_url"])
|
||||
model = cfg["prompt_translate_model"]
|
||||
# A request without a prompt only loads the model; the options decide CPU placement.
|
||||
_post_json(f"{base_url}/api/generate", {
|
||||
"model": model,
|
||||
"keep_alive": CPU_KEEP_ALIVE,
|
||||
"options": _device_options("cpu"),
|
||||
}, timeout=180)
|
||||
return True
|
||||
|
||||
|
||||
def list_models():
|
||||
cfg = get_config()
|
||||
base_url = require_backend_url(cfg["prompt_translate_url"])
|
||||
req = urllib.request.Request(f"{base_url}/api/tags")
|
||||
with urlopen(req, timeout=5) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
return [m.get("name") for m in data.get("models", []) if m.get("name")]
|
||||
@@ -99,7 +99,7 @@ class WorkflowStudio:
|
||||
app.router.add_get("/wfm", serve_index_page)
|
||||
|
||||
# API routes
|
||||
from .routes import workflow_routes, settings_routes, ollama_routes, unsloth_routes, eagle_routes, nodes_routes, prompts_routes, models_routes, gallery_routes, wildcard_routes, tagger_routes, gmic_routes, skill_routes, lab_routes, video_routes, vram_routes, partner_routes, tagspace_routes
|
||||
from .routes import workflow_routes, settings_routes, ollama_routes, unsloth_routes, eagle_routes, nodes_routes, prompts_routes, models_routes, gallery_routes, wildcard_routes, tagger_routes, gmic_routes, skill_routes, lab_routes, video_routes, vram_routes, partner_routes, tagspace_routes, translate_routes
|
||||
|
||||
workflow_routes.setup_routes(app)
|
||||
settings_routes.setup_routes(app)
|
||||
@@ -119,5 +119,6 @@ class WorkflowStudio:
|
||||
video_routes.setup_routes(app)
|
||||
vram_routes.setup_routes(app)
|
||||
tagspace_routes.setup_routes(app)
|
||||
translate_routes.setup_routes(app)
|
||||
|
||||
logger.info("Workflow Studio: Routes registered successfully")
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-workflow-studio"
|
||||
description = "A comprehensive workflow, asset management, and generation UI plugin for ComfyUI. Manage workflows, models, images, and prompts; batch generation and Image Feeder in the GenerateUI tab; library side panel with canvas drag & drop and AI tools."
|
||||
version = "0.9.8"
|
||||
version = "0.9.9"
|
||||
license = { file = "LICENSE" }
|
||||
requires-python = ">= 3.10"
|
||||
dependencies = ["jinja2"]
|
||||
|
||||
+8
-2
@@ -15,6 +15,7 @@ import {
|
||||
imageUrlToJpegBase64, svgToJpegBase64, parseSvgParts, svgChecklist, DEFAULT_JUDGE_MODEL,
|
||||
} from "./chat-image-check.js";
|
||||
import { renderMarkdown } from "./chat-markdown.js";
|
||||
import { isPlamoModel, buildPlamoMessages, cleanPlamoOutput } from "./plamo-chat.js";
|
||||
import { buildWorkflowDetailContext, buildLogsContext } from "./chat-context.js";
|
||||
import { openLabPlanData } from "./lab-tab.js";
|
||||
import { listOcrModels, supportsBoxes, runOcr, renderOcrOverlay } from "./ocr-tools.js";
|
||||
@@ -245,6 +246,8 @@ async function callLLM(url, backend, model, prompt, settings = {}) {
|
||||
function buildTranslationMessages(text, srcLang, dstLang, settings) {
|
||||
const srcName = srcLang === "free" ? (settings.freeSrcLang || "").trim() : LANG_NAMES[srcLang];
|
||||
const dstName = dstLang === "free" ? (settings.freeDstLang || "English").trim() || "English" : LANG_NAMES[dstLang];
|
||||
// PLaMo Translate ignores instruction prompts (see plamo-chat.js)
|
||||
if (isPlamoModel(settings.model)) return buildPlamoMessages(text, srcName, dstName);
|
||||
const fromPart = srcName ? ` from ${srcName}` : "";
|
||||
const system = `You are a professional translator. Translate the text the user sends${fromPart} into ${dstName}. `
|
||||
+ `Reply with ONLY the translated text and nothing else: no explanations, no notes, no preamble like "Here is the translation:", `
|
||||
@@ -342,8 +345,11 @@ function initTranslateTab() {
|
||||
|
||||
try {
|
||||
const messages = buildTranslationMessages(text, srcLangSel.value, dstLangSel.value, settings);
|
||||
const { content } = await callChat(url, backend, model, messages, undefined, settings);
|
||||
const cleaned = cleanTranslationOutput(content);
|
||||
const plamo = isPlamoModel(model);
|
||||
// PLaMo has no thinking mode and translates deterministically at temperature 0
|
||||
const callSettings = plamo ? { ...settings, thinkingMode: false, temperature: 0 } : settings;
|
||||
const { content } = await callChat(url, backend, model, messages, undefined, callSettings);
|
||||
const cleaned = plamo ? cleanPlamoOutput(content) : cleanTranslationOutput(content);
|
||||
outputEl.value = cleaned;
|
||||
|
||||
if (srcLangSel.value !== dstLangSel.value && looksUntranslated(text, cleaned)) {
|
||||
|
||||
@@ -523,6 +523,7 @@ function applyI18nToHtml() {
|
||||
"wfm-help-gen-30": "helpGen30",
|
||||
"wfm-help-gen-31": "helpGen31",
|
||||
"wfm-help-gen-32": "helpGen32",
|
||||
"wfm-help-gen-33": "helpGen33",
|
||||
"wfm-help-feeder-title": "helpFeederTitle",
|
||||
"wfm-help-feeder-desc": "helpFeederDesc",
|
||||
"wfm-help-feeder-imgloop-title": "helpFeederImgloopTitle",
|
||||
@@ -591,6 +592,7 @@ function applyI18nToHtml() {
|
||||
"wfm-help-settings-20": "helpSettings20", "wfm-help-settings-21": "helpSettings21",
|
||||
"wfm-help-settings-22": "helpSettings22",
|
||||
"wfm-help-settings-23": "helpSettings23", "wfm-help-settings-24": "helpSettings24", "wfm-help-settings-25": "helpSettings25",
|
||||
"wfm-help-settings-26": "helpSettings26",
|
||||
"wfm-help-nodes-1": "helpNodes1", "wfm-help-nodes-2": "helpNodes2",
|
||||
"wfm-help-nodes-3": "helpNodes3", "wfm-help-nodes-4": "helpNodes4",
|
||||
"wfm-help-nodes-5": "helpNodes5", "wfm-help-nodes-6": "helpNodes6",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { syncJsonHighlight } from "./json-highlight.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { escapeHtml, setupSearchClearBtn } from "./util.js";
|
||||
import { showToast } from "./app.js";
|
||||
import { translateButtonsHtml, attachTranslateButtons } from "./prompt-translate.js";
|
||||
|
||||
// ── Latent Image preset state ─────────────────────────────
|
||||
const _LATENT_PRESET_KEY = "wfm_latent_presets";
|
||||
@@ -645,6 +646,7 @@ export const comfyEditor = {
|
||||
${nodeOpts}
|
||||
</select>
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-prompt-pos-revert" title="${t("revertPromptTitle")}">↺</button>
|
||||
${translateButtonsHtml("wfm-prompt-pos-text")}
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-prompt-pos-apply" title="Apply (Alt+Click: Apply & Generate)">Apply</button>
|
||||
</div>
|
||||
<textarea class="wfm-textarea" id="wfm-prompt-pos-text" rows="6">${positiveNodes[0]?.text || ""}</textarea>
|
||||
@@ -657,6 +659,7 @@ export const comfyEditor = {
|
||||
${nodeOpts}
|
||||
</select>
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-prompt-neg-revert" title="${t("revertPromptTitle")}">↺</button>
|
||||
${translateButtonsHtml("wfm-prompt-neg-text")}
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-prompt-neg-apply">Apply</button>
|
||||
</div>
|
||||
<textarea class="wfm-textarea" id="wfm-prompt-neg-text" rows="6">${negativeNodes[0]?.text || ""}</textarea>
|
||||
@@ -719,6 +722,9 @@ export const comfyEditor = {
|
||||
ta.value = wfNode.inputs[textKey] ?? "";
|
||||
});
|
||||
|
||||
// 🌐 native-language prompt translation (Settings → Prompt Translation)
|
||||
attachTranslateButtons(el);
|
||||
|
||||
// Track last focused prompt textarea for Paste button
|
||||
["wfm-prompt-pos-text", "wfm-prompt-neg-text"].forEach((taId) => {
|
||||
const ta = document.getElementById(taId);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { initFeederTab, refreshFeederNodeList } from "./feeder-tab.js";
|
||||
import { initLabTab, refreshLabLiveDefaults } from "./lab-tab.js";
|
||||
import { getSettings, readJsonStorage, escapeHtml, getEagleSettings, saveToEagle } from "./util.js";
|
||||
import { isVideoFilename } from "./video-utils.js";
|
||||
import { autoTranslateBeforeGenerate } from "./prompt-translate.js";
|
||||
|
||||
// ============================================
|
||||
// Gallery Metadata - ワークフロー保存
|
||||
@@ -2032,6 +2033,22 @@ async function handleGenerate(workflowOverride = null, genOptions = {}) {
|
||||
}
|
||||
if (comfyUI.generating) return;
|
||||
|
||||
// 設定「プロンプト翻訳」で生成前の自動翻訳がオンなら、Positive/Negative欄の自国語部分を英語へ置き換えてから同期する。
|
||||
// 失敗時は未翻訳のまま生成しないよう中断する。
|
||||
if (!workflowOverride) {
|
||||
const genBtnEl = document.getElementById("wfm-gen-generate-btn");
|
||||
if (genBtnEl) genBtnEl.disabled = true;
|
||||
try {
|
||||
await autoTranslateBeforeGenerate();
|
||||
} catch (err) {
|
||||
if (genBtnEl) genBtnEl.disabled = false;
|
||||
const progressText = document.getElementById("wfm-gen-progress-text");
|
||||
if (progressText) progressText.textContent = "";
|
||||
showToast(t("promptTranslateError", err.message), "error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// オーバーライド実行時は GenerateUI 側の表示中プロンプトを別ワークフローへ誤って書き込まないよう同期をスキップする
|
||||
if (!workflowOverride) comfyEditor.syncToWorkflow();
|
||||
|
||||
|
||||
@@ -1080,6 +1080,7 @@ const LANGUAGES = {
|
||||
helpGen29: "Bypass button (⛔, in the LoRA column header, next to Refresh Stack group): forces the target LoRA node's strength to 0 — strength_model/strength_clip for a standard LoraLoader, every entry's strength/clipStrength for a Lora Manager node (its LoRA list is kept intact, not replaced), or every row's strength on both native Load LoRA nodes (Model and Text Encoder; rows kept) — without touching the actual ComfyUI node's mode; click again to restore the exact values that were there before toggling; targets whichever node Single/Stack Apply default to (the workflow's Lora Manager node if it has one, else the first LoRA node); clicking a Single or Stack Apply button clears the bypass state so it can't silently reassert itself on a later toggle",
|
||||
helpGen30: "The Save button (right end of the subtab bar) is hidden while the Batch or Lab subtab is active, since those work on workflow copies / their own Plan files rather than the currently loaded workflow; it reappears on Input/Model/Settings/Feeder",
|
||||
helpGen32: "Partner API nodes (Nano Banana / Gemini image nodes, etc.): GenerateUI can run workflows that contain ComfyUI's partner API nodes. These nodes need Comfy credentials in the /prompt request — ComfyUI's own screen adds them automatically, but a separate page cannot — so add COMFY_API_KEY=... (a Comfy API key issued at platform.comfy.org) to the plugin's .env file and restart ComfyUI. With the key set, GenerateUI sends the request through the plugin server, which adds the key only when the workflow contains a partner API node (the key never reaches the browser); usage is billed as Comfy credits. A Gemini image node's prompt appears in the Prompt tab, and its reference images go through LoadImage nodes in the Image tab. Sample: ws_nano_banana_2_1_t2i",
|
||||
helpGen33: "🌐 prompt translation: when Settings → Prompt Translation is on, a 🌐 button sits next to the Positive/Negative prompts (Input tab), the Positive/Negative fields of the Lab prompt modal, and the Video Plan block prompt. It translates only the parts written in your own language into English and leaves prompt syntax as it is — commas, (word:1.2), [ ], {a|b}, <lora:...>, __wildcards__, embedding:name and BREAK; \"Original\" puts back the text from before translation (also Ctrl+Z) while that field stays open. Settings can also translate automatically when Generate, Lab Run or Video Plan Run is pressed",
|
||||
helpFeederTitle: "GenerateUI Tab — Feeder subtab",
|
||||
helpFeederDesc: "Two modes switchable via the [Image Loop] / [Gallery] toggle at the top of the left pane.",
|
||||
helpFeederImgloopTitle: "Image Loop mode",
|
||||
@@ -1188,6 +1189,7 @@ const LANGUAGES = {
|
||||
helpSettings23: "Models Folder: add extra model root folders (e.g. Stability Matrix's Models) — their subfolders (StableDiffusion / Lora / VAE / ControlNet / TextEncoders / DiffusionModels / Embeddings / HyperNetworks, or ComfyUI names like checkpoints / loras) are listed in the Models tab in addition to ComfyUI's own folders; separate several roots with ; — Apply saves, Default clears",
|
||||
helpSettings24: "Windows Stability: \"Use the Selector event loop on Windows\" switches ComfyUI's asyncio loop from Proactor to Selector (applied at the next ComfyUI start). Turn it on if ComfyUI freezes entirely after switching Models lists that load many preview images; the trade-offs are no asyncio subprocess support and a connection limit of about 512. The same switch is available through the WFS_SELECTOR_LOOP=1 environment variable",
|
||||
helpSettings25: "VRAM Management (Ollama): right before every image generation from Workflow Studio (GenerateUI, Lab, batches, Video, AI TOOL chat generation, etc.), unload the models Ollama has loaded to free VRAM. Before generating: \"Do nothing\" (default) / \"Unload only as much as needed when free VRAM is below the target\" (unloads the models using the most VRAM one at a time and stops once the target is reached) / \"Unload everything every time\". Free VRAM is measured with nvidia-smi (ComfyUI's own figure may not change when Ollama loads a model; non-NVIDIA GPUs use ComfyUI's value). Targets Ollama on localhost — the default 127.0.0.1:11434 plus the AI TOOL / Prompt / Tagger AI settings and the Decision Model when they use Ollama (other hosts only if allowed by WFS_ALLOWED_BACKEND_HOSTS); LM Studio etc. and ComfyUI's own models are not touched. Unloaded models reload the next time they are used; a toast appears when models are unloaded, the target can't be reached, or the step fails, and generation continues either way. \"Unload all now\" unloads every loaded Ollama model immediately; the same \"Unload all now\" is also in the sidepanel A tab → Settings → VRAM (Ollama). Running a workflow from the ComfyUI canvas itself is not covered",
|
||||
helpSettings26: "Prompt Translation (native language → English): off by default. Translates prompts with a local Ollama model — PLaMo 3 Translate (0.8B / 2B / 8B / 31B; models not yet in Ollama can be fetched with the Download button) or any other Ollama model through a translation instruction. Options: source language (auto-detect or one of the 51 languages PLaMo supports), Ollama URL, where to run the model — GPU (about 0.1 s per phrase; with \"Unload after translating\" on, each translation first waits about 4 s for the model to reload) or CPU kept loaded (about 0.3 s per phrase, no VRAM, loaded in the background when GenerateUI opens) — and \"Translate automatically when running\" for GenerateUI Generate (replaces the prompt text with the translation), Lab Run and Video Plan Run (translate only what is sent; the plan keeps your text). If translation fails, nothing is generated. Test translates a sample prompt. 2B translates sentences more accurately than 0.8B. PLaMo 3 Translate is free for personal and non-commercial use (PLaMo Community License). PLaMo models also work for the AI TOOL / Prompt tab AI Assistant / sidepanel A tab translation buttons (the languages are passed in the format PLaMo expects)",
|
||||
helpSettings9: "All sections are collapsible accordion panels",
|
||||
helpSettings10: "Wildcard Integration: link WFS wildcard dir to ComfyUI-Impact-Pack wildcards (directory junction on Windows / symlink on other OS); existing files migrated automatically",
|
||||
helpSettings11: "Text Size: one slider (10–28 px) adjusts font size for all prompt and chat textareas at once — Generate UI positive/negative prompts, AI Assistant chat input, Preset positive/negative, Wildcard prompt and file editor, and Metadata prompt full preview; takes effect immediately and saved with Save Settings",
|
||||
@@ -1532,6 +1534,39 @@ const LANGUAGES = {
|
||||
selectorLoopLabel: "Use the Selector event loop on Windows (restart required)",
|
||||
selectorLoopHint: "Turn this on if ComfyUI freezes (all tabs stop responding, even generation) after switching Models lists with many previews. Switches the Windows asyncio loop from Proactor to Selector. Takes effect after restarting ComfyUI; asyncio subprocess features are unavailable and the connection limit is about 512. Leave off if you have no problem.",
|
||||
selectorLoopSaved: "Saved. Restart ComfyUI to apply.",
|
||||
promptTranslateSection: "Prompt Translation (native language → English)",
|
||||
promptTranslateEnable: "Enable prompt translation in GenerateUI",
|
||||
promptTranslateHint: "Type prompts in your own language and translate them into English with a local Ollama model (PLaMo 3 Translate by default). A 🌐 button appears next to the Positive/Negative prompts. Only parts containing non-ASCII letters are translated; commas, (weights:1.2), [ ], {a|b}, <lora:...>, __wildcards__, embedding:name and BREAK stay unchanged. PLaMo 3 Translate is free for personal and non-commercial use (PLaMo Community License).",
|
||||
promptTranslateAutoHeading: "Translate automatically when running",
|
||||
promptTranslateAuto: "GenerateUI — Generate",
|
||||
promptTranslateAutoLab: "GenerateUI Lab — Run",
|
||||
promptTranslateAutoVideo: "Video Plan — Run",
|
||||
promptTranslateAutoHint: "Generate replaces the Positive/Negative text with the translation. Lab Run and Video Plan Run translate only what is sent, so the plan keeps your original text. If translation fails, nothing is generated.",
|
||||
promptTranslateUnload: "Unload the translation model from VRAM after translating (slower next time, frees VRAM for image generation)",
|
||||
promptTranslateSourceLang: "Source language",
|
||||
promptTranslateSourceAuto: "Auto-detect",
|
||||
promptTranslateUrl: "Ollama URL",
|
||||
promptTranslateModel: "Translation model",
|
||||
promptTranslateModelHint: "PLaMo 3 Translate sizes: 0.8B (0.6 GB, fastest), 2B (1.8 GB, recommended), 8B (5.3 GB), 31B (19.5 GB). Models not yet in Ollama can be downloaded with the Download button. Other Ollama models are translated through a chat prompt.",
|
||||
promptTranslatePull: "Download",
|
||||
promptTranslatePulling: "Downloading…",
|
||||
promptTranslatePulled: "Model downloaded",
|
||||
promptTranslateNotPulled: "Not downloaded",
|
||||
promptTranslateNotPulledMark: "(not downloaded)",
|
||||
promptTranslateOtherModels: "Other Ollama models",
|
||||
promptTranslateSaved: "Prompt translation settings saved",
|
||||
promptTranslateRunning: "Translating prompt…",
|
||||
promptTranslateNothing: "Nothing to translate (no non-ASCII text)",
|
||||
promptTranslateBtnTitle: "Translate the native-language parts of this prompt into English",
|
||||
promptTranslateRestore: "Original",
|
||||
promptTranslateRestoreTitle: "Restore the text before translation",
|
||||
aiTranslating: "Translating…",
|
||||
promptTranslateDevice: "Where to run the translation model",
|
||||
promptTranslateDeviceGpu: "GPU (fastest per translation, uses VRAM)",
|
||||
promptTranslateDeviceCpu: "CPU, kept loaded (no VRAM, no reload wait)",
|
||||
promptTranslateDeviceHint: "CPU mode keeps the model in system RAM (about 2 GB for 2B) and loads it in the background when GenerateUI opens, so translations take about 0.3 s per phrase with no VRAM use and no ~4 s reload. GPU mode is faster per phrase (about 0.1 s) but, when unloading after translation is on, every translation waits about 4 s for the model to reload.",
|
||||
promptTranslateError: (msg) => `Translation error: ${msg}`,
|
||||
promptTranslateModelsError: (msg) => `Could not get the model list: ${msg}`,
|
||||
settingsTitle: "Settings",
|
||||
langLabel: "UI Language",
|
||||
summaryLangLabel: "Summary Language",
|
||||
@@ -2992,6 +3027,7 @@ const LANGUAGES = {
|
||||
helpGen29: "バイパスボタン(⛔、LoRA列ヘッダー内、Refresh Stack groupボタンの隣):対象のLoRAノードの強度を強制的に0にします — 通常のLoraLoaderならstrength_model/strength_clip、Lora Managerノードならリスト内の全エントリのstrength/clipStrength(LoRAリスト自体は差し替えず維持)、ComfyUI標準の Load LoRA ノードなら両ノード(Model/Text Encoder)の全行のstrength(行は維持)が対象で、実際のComfyUIノードのmodeには一切触れません;もう一度クリックすると、切り替え前の値に正確に戻ります;対象はSingle/Stack Applyがデフォルトで対象とするノード(ワークフローにLora Managerノードがあればそれ、無ければ最初のLoRAノード);SingleまたはStackのApplyボタンをクリックするとバイパス状態はクリアされ、後から無言で再適用されることはありません",
|
||||
helpGen30: "Saveボタン(サブタブバー右端)は、BatchまたはLabサブタブがアクティブな間は非表示になります — これらは現在読み込み中のワークフロー本体ではなく、ワークフローの複製や独自のPlanファイルを操作するためです。Input/Model/Settings/Feederでは再表示されます",
|
||||
helpGen32: "パートナーAPIノード(Nano Banana/Gemini画像ノードなど):GenerateUIで、ComfyUIのパートナーAPIノードを含むワークフローを実行できます。これらのノードは /prompt リクエストにComfyの認証情報が必要で、ComfyUI本体の画面は自動で付けますが別ページからは付けられないため、プラグインの .env に COMFY_API_KEY=...(platform.comfy.org で発行するComfy APIキー)を追加して ComfyUI を再起動してください。キーを設定すると、GenerateUIはプラグインのサーバー経由でリクエストを送り、ワークフローにパートナーAPIノードがあるときだけキーを付与します(キーはブラウザへは出ません)。課金はComfyクレジットです。Gemini画像ノードのプロンプトはPromptタブに表示され、参考画像はImageタブのLoadImageノード経由で指定します。サンプル: ws_nano_banana_2_1_t2i",
|
||||
helpGen33: "🌐 プロンプト翻訳:設定 →「プロンプト翻訳」をオンにすると、Positive/Negativeプロンプト(Inputタブ)、Labのプロンプトモーダルのpositive/negative欄、Video Planのブロックのプロンプト欄の横に🌐ボタンが出ます。自国語で書いた部分だけを英語に翻訳し、カンマ・(語句:1.2)・[ ]・{a|b}・<lora:...>・__ワイルドカード__・embedding:名前・BREAKなどのプロンプト記法はそのまま残します。「原文」で翻訳前の文に戻せます(その欄を開いている間はCtrl+Zでも可)。設定で、Generate・Lab Run・Video Plan Runの実行時に自動で翻訳することもできます",
|
||||
helpFeederTitle: "GenerateUI Tab — Feeder サブタブ",
|
||||
helpFeederDesc: "左ペイン上部の [Image Loop] / [Gallery] トグルで2つのモードを切り替えられます。",
|
||||
helpFeederImgloopTitle: "Image Loop モード",
|
||||
@@ -3100,6 +3136,7 @@ const LANGUAGES = {
|
||||
helpSettings23: "モデルフォルダ:追加のモデルルートフォルダ(例: Stability Matrix の Models)を指定すると、その中のサブフォルダ(StableDiffusion / Lora / VAE / ControlNet / TextEncoders / DiffusionModels / Embeddings / HyperNetworks、またはcheckpoints / lorasなどComfyUI名)がComfyUI本来のフォルダに加えてModelsタブに表示されます。複数のルートは ; 区切り。「適用」で保存、「デフォルト」でクリア",
|
||||
helpSettings24: "Windowsの安定性:「WindowsでSelectorイベントループを使用する」をオンにすると、ComfyUIのasyncioループがProactorからSelectorに切り替わります(次回のComfyUI起動時に反映)。プレビュー画像を多数読み込むModelsの一覧を切り替えたあとにComfyUIが完全に固まる場合にオンにしてください。トレードオフとして、asyncioのサブプロセス機能が使えず、同時接続数は約512が上限です。環境変数 WFS_SELECTOR_LOOP=1 でも同じ切り替えができます",
|
||||
helpSettings25: "VRAM調整(Ollama):Workflow Studioからの画像生成(GenerateUI・Lab・バッチ・Video・AI TOOLのChat生成など、どこから実行しても同じ)の直前に、Ollamaでロード中のモデルをアンロードして空きVRAMを作ります。生成前の動作は「何もしない」(既定)/「空きが目標未満のときだけ必要な分をアンロード」(VRAM使用量の大きいモデルから1台ずつアンロードし、目標の空きに届いたら止める)/「毎回すべてアンロード」。空きVRAMはnvidia-smiの実測で判定します(ComfyUIが示す空きは、Ollamaがモデルを読み込んでも変わらないことがあるため。NVIDIA以外のGPUではComfyUIの値を使用)。対象はローカルホストのOllamaで、既定の127.0.0.1:11434と、Ollamaを使っているAI TOOL・Prompt・TaggerのAI設定および意思決定モデルです(他のホストはWFS_ALLOWED_BACKEND_HOSTSで許可した場合のみ)。LM Studio等のモデルやComfyUI自身のモデルは対象外です。アンロードしたモデルは次に使うときに読み込み直しになります。アンロードしたとき・目標に届かないとき・失敗したときはトーストで通知し、失敗しても生成は続けます。「今すぐすべてアンロード」でロード中のOllamaのモデルをその場でアンロードできます。サイドパネルのAタブ → Settings →「VRAM (Ollama)」にも同じ「Unload all now」ボタンがあります。ComfyUIのキャンバスで直接実行する生成は対象外です",
|
||||
helpSettings26: "プロンプト翻訳(自国語 → 英語):既定はオフ。ローカルのOllamaモデルでプロンプトを翻訳します。PLaMo 3 Translate(0.8B/2B/8B/31B。Ollamaに未取得のモデルは「ダウンロード」ボタンで取得)のほか、他のOllamaモデルも翻訳指示付きで使えます。設定項目は、入力する言語(自動判定、またはPLaMoが対応する51言語)、Ollama URL、翻訳モデルの実行場所(GPU:1語句約0.1秒。「翻訳後にVRAMから解放」がオンだと毎回モデルの再読み込みで約4秒待つ/CPUで常駐:1語句約0.3秒・VRAMを使わず、生成UIを開いたときに裏で読み込む)、「実行時に自動で翻訳する」(生成UIのGenerate:欄の文を翻訳結果に置き換え/Lab Run・Video Plan Run:送信する内容だけ翻訳し、プランには元の文が残る)です。翻訳に失敗した場合は生成しません。「テスト」で見本のプロンプトを翻訳します。文章の翻訳は0.8Bより2Bのほうが正確です。PLaMo 3 Translateは個人・非商用利用なら無料です(PLaMoコミュニティライセンス)。PLaMoのモデルは、AI TOOL・PromptタブのAIアシスタント・サイドパネルAタブの翻訳でも使えます(PLaMoが求める形式で言語を指定して送ります)",
|
||||
helpSettings9: "各セクションはアコーディオン式で折りたたみ可能",
|
||||
helpSettings10: "Wildcard Integration:WFSのワイルドカードディレクトリをComfyUI-Impact-Packのwildcardsディレクトリへリンク(Windowsはジャンクション、他OSはシンボリックリンク)。既存ファイルは自動で移行されます",
|
||||
helpSettings11: "テキストサイズ:スライダー1本(10〜28px)でプロンプト・チャット全テキストエリアのフォントサイズを一括調整 — GenerateUI ポジティブ/ネガティブプロンプト、AIアシスタントチャット入力、プリセット ポジティブ/ネガティブ、ワイルドカードプロンプト・ファイルエディタ、メタデータプロンプト全文プレビュー;即時反映・保存で確定",
|
||||
@@ -3444,6 +3481,39 @@ const LANGUAGES = {
|
||||
selectorLoopLabel: "WindowsでSelectorイベントループを使用する(要再起動)",
|
||||
selectorLoopHint: "プレビュー画像が多いModelsの一覧を切り替えたあと、ComfyUIが固まる(全タブが無応答になり、生成もできなくなる)場合にオンにしてください。WindowsのasyncioループをProactorからSelectorに切り替えます。ComfyUIの再起動後に有効になります。asyncioのサブプロセス機能は使えなくなり、同時接続数は約512が上限です。問題が出ていなければオフのままで構いません。",
|
||||
selectorLoopSaved: "保存しました。反映にはComfyUIの再起動が必要です。",
|
||||
promptTranslateSection: "プロンプト翻訳(自国語 → 英語)",
|
||||
promptTranslateEnable: "生成UIでプロンプト翻訳を使う",
|
||||
promptTranslateHint: "プロンプトを自分の言語で入力し、ローカルのOllamaモデル(既定はPLaMo 3 Translate)で英語に翻訳します。Positive/Negativeプロンプトの横に🌐ボタンが表示されます。翻訳されるのは英数字以外の文字を含む部分だけで、カンマ・(重み:1.2)・[ ]・{a|b}・<lora:...>・__ワイルドカード__・embedding:名前・BREAK はそのまま残ります。PLaMo 3 Translateは個人・非商用利用なら無料です(PLaMoコミュニティライセンス)。",
|
||||
promptTranslateAutoHeading: "実行時に自動で翻訳する",
|
||||
promptTranslateAuto: "生成UI — Generate",
|
||||
promptTranslateAutoLab: "生成UI Lab — Run",
|
||||
promptTranslateAutoVideo: "Video Plan — Run",
|
||||
promptTranslateAutoHint: "Generateでは、Positive/Negative欄の文が翻訳結果に置き換わります。Lab RunとVideo Plan Runでは送信する内容だけを翻訳し、プランには元の文が残ります。翻訳に失敗した場合は生成しません。",
|
||||
promptTranslateUnload: "翻訳後に翻訳モデルをVRAMから解放する(次回の翻訳は遅くなるが、画像生成用のVRAMを空ける)",
|
||||
promptTranslateSourceLang: "入力する言語",
|
||||
promptTranslateSourceAuto: "自動判定",
|
||||
promptTranslateUrl: "Ollama URL",
|
||||
promptTranslateModel: "翻訳モデル",
|
||||
promptTranslateModelHint: "PLaMo 3 Translateのサイズ: 0.8B(0.6GB・最速)、2B(1.8GB・推奨)、8B(5.3GB)、31B(19.5GB)。Ollamaに未取得のモデルは「ダウンロード」ボタンで取得できます。PLaMo以外のOllamaモデルはチャット形式の指示で翻訳します。",
|
||||
promptTranslatePull: "ダウンロード",
|
||||
promptTranslatePulling: "ダウンロード中…",
|
||||
promptTranslatePulled: "モデルをダウンロードしました",
|
||||
promptTranslateNotPulled: "未取得",
|
||||
promptTranslateNotPulledMark: "(未取得)",
|
||||
promptTranslateOtherModels: "その他のOllamaモデル",
|
||||
promptTranslateSaved: "プロンプト翻訳の設定を保存しました",
|
||||
promptTranslateRunning: "プロンプトを翻訳中…",
|
||||
promptTranslateNothing: "翻訳する部分がありません(英数字以外の文字がありません)",
|
||||
promptTranslateBtnTitle: "このプロンプトの自国語部分を英語に翻訳",
|
||||
promptTranslateRestore: "原文",
|
||||
promptTranslateRestoreTitle: "翻訳前のテキストに戻す",
|
||||
aiTranslating: "翻訳中…",
|
||||
promptTranslateDevice: "翻訳モデルの実行場所",
|
||||
promptTranslateDeviceGpu: "GPU(1回の翻訳が最速・VRAMを使う)",
|
||||
promptTranslateDeviceCpu: "CPUで常駐(VRAMを使わず、再読み込みの待ちなし)",
|
||||
promptTranslateDeviceHint: "CPUモードではモデルをメインメモリに常駐させ(2Bで約2GB)、生成UIを開いたときに裏で読み込んでおきます。VRAMを使わず約4秒の再読み込みも発生しないため、1語句あたり約0.3秒で翻訳できます。GPUモードは1語句あたり約0.1秒と速い反面、「翻訳後にVRAMから解放」がオンだと毎回モデルの再読み込みで約4秒待ちます。",
|
||||
promptTranslateError: (msg) => `翻訳エラー: ${msg}`,
|
||||
promptTranslateModelsError: (msg) => `モデル一覧を取得できません: ${msg}`,
|
||||
settingsTitle: "設定",
|
||||
langLabel: "UI言語",
|
||||
summaryLangLabel: "要約言語",
|
||||
@@ -4904,6 +4974,7 @@ const LANGUAGES = {
|
||||
helpGen29: "绕过按钮(⛔,位于LoRA列表头,紧邻Refresh Stack group):将目标LoRA节点的强度强制设为0——普通LoraLoader对应strength_model/strength_clip,Lora Manager节点则对应列表中每一项的strength/clipStrength(LoRA列表本身保持不变,不会被替换),ComfyUI原生 Load LoRA 节点则对应两个节点(Model/Text Encoder)所有行的strength(行保持不变),不会改动实际ComfyUI节点的mode;再次点击可精确恢复切换前的数值;作用于Single/Stack Apply默认所用的节点(工作流中若有Lora Manager节点则用它,否则用第一个LoRA节点);点击Single或Stack的Apply按钮会清除绕过状态,避免其在之后被无声地重新生效",
|
||||
helpGen30: "Save按钮(子标签栏右端)在Batch或Lab子标签处于激活状态时会隐藏,因为它们操作的是工作流副本或各自的Plan文件,而不是当前加载的工作流本身;切换回Input/Model/Settings/Feeder时会重新显示",
|
||||
helpGen32: "合作伙伴API节点(Nano Banana/Gemini图像节点等):GenerateUI可以运行包含ComfyUI合作伙伴API节点的工作流。这些节点需要在 /prompt 请求中携带Comfy认证信息——ComfyUI自带界面会自动添加,独立页面则无法添加——因此请在插件的 .env 中添加 COMFY_API_KEY=...(在 platform.comfy.org 获取的Comfy API密钥)并重启ComfyUI。设置密钥后,GenerateUI会通过插件服务器发送请求,仅当工作流包含合作伙伴API节点时才附加密钥(密钥不会到达浏览器);费用按Comfy积分计算。Gemini图像节点的提示词显示在Prompt标签中,参考图通过Image标签的LoadImage节点指定。示例:ws_nano_banana_2_1_t2i",
|
||||
helpGen33: "🌐 提示词翻译:在 设置 →“提示词翻译”中启用后,Positive/Negative 提示词(Input 标签)、Lab 提示词弹窗的 Positive/Negative 栏以及 Video Plan 区块的提示词栏旁会出现🌐按钮。只把用母语写的部分翻译成英语,逗号、(词语:1.2)、[ ]、{a|b}、<lora:...>、__通配符__、embedding:名称、BREAK 等提示词语法保持不变。“原文”可恢复翻译前的文本(该栏打开期间也可用 Ctrl+Z)。还可以在设置中让 Generate、Lab Run、Video Plan Run 运行时自动翻译",
|
||||
helpFeederTitle: "GenerateUI Tab — Feeder 子标签",
|
||||
helpFeederDesc: "通过左侧面板顶部的 [图片循环] / [图库] 切换按钮可在两种模式间切换。",
|
||||
helpFeederImgloopTitle: "图片循环模式",
|
||||
@@ -5012,6 +5083,7 @@ const LANGUAGES = {
|
||||
helpSettings23: "模型文件夹:可添加额外的模型根文件夹(例如 Stability Matrix 的 Models),其中的子文件夹(StableDiffusion / Lora / VAE / ControlNet / TextEncoders / DiffusionModels / Embeddings / HyperNetworks,或 checkpoints / loras 等ComfyUI名称)会与ComfyUI自带文件夹一起显示在Models标签页;多个根文件夹用 ; 分隔,“应用”保存,“默认”清除",
|
||||
helpSettings24: "Windows 稳定性:开启“在Windows上使用Selector事件循环”会把ComfyUI的asyncio循环从Proactor切换为Selector(下次启动ComfyUI时生效)。如果切换加载大量预览图的Models列表后ComfyUI完全卡死,请开启。代价是不支持asyncio子进程,并发连接数上限约为512。也可通过环境变量 WFS_SELECTOR_LOOP=1 切换",
|
||||
helpSettings25: "显存调整(Ollama):在 Workflow Studio 每次生成图像之前(GenerateUI・Lab・批量・Video・AI TOOL 的 Chat 生成等,无论从哪里执行都相同),卸载 Ollama 已加载的模型以腾出显存。生成前的操作:“不做任何操作”(默认)/“仅在空闲显存低于目标时卸载所需的部分”(从显存占用最大的模型开始逐个卸载,达到目标后停止)/“每次全部卸载”。空闲显存以 nvidia-smi 的实测值判断(ComfyUI 显示的空闲显存在 Ollama 加载模型后可能不变;非 NVIDIA GPU 使用 ComfyUI 的值)。对象为本机的 Ollama:默认的 127.0.0.1:11434,以及使用 Ollama 的 AI TOOL・Prompt・Tagger 的 AI 设置和决策模型(其他主机仅在 WFS_ALLOWED_BACKEND_HOSTS 允许时)。LM Studio 等的模型和 ComfyUI 自身的模型不在范围内。卸载的模型在下次使用时会重新加载;卸载时、未达到目标时或失败时会以提示通知,失败也会继续生成。“立即全部卸载”可当场卸载所有已加载的 Ollama 模型。侧面板的 A 标签 → Settings →“VRAM (Ollama)”中也有相同的“Unload all now”按钮。直接在 ComfyUI 画布上运行的生成不在范围内",
|
||||
helpSettings26: "提示词翻译(母语 → 英语):默认关闭。使用本地 Ollama 模型翻译提示词:PLaMo 3 Translate(0.8B/2B/8B/31B;Ollama 中尚未下载的模型可用“下载”按钮获取),也可通过翻译指令使用其他 Ollama 模型。设置项包括:输入语言(自动检测或 PLaMo 支持的 51 种语言)、Ollama URL、翻译模型的运行位置(GPU:每个短语约 0.1 秒;开启“翻译后从显存卸载”时,每次都要等待约 4 秒重新加载模型/CPU 常驻:每个短语约 0.3 秒,不占显存,打开生成UI时在后台加载),以及“运行时自动翻译”(生成UI 的 Generate:用翻译结果替换栏中文本/Lab Run・Video Plan Run:只翻译发送的内容,计划中保留原文)。翻译失败时不会生成。“测试”会翻译一段示例提示词。翻译句子时 2B 比 0.8B 更准确。PLaMo 3 Translate 可免费用于个人和非商业用途(PLaMo 社区许可证)。PLaMo 模型也可用于 AI TOOL、Prompt 标签的 AI 助手和侧面板 A 标签的翻译(以 PLaMo 要求的格式指定语言)",
|
||||
helpSettings9: "各部分可折叠展开",
|
||||
helpSettings10: "Wildcard Integration:将WFS通配符目录链接到ComfyUI-Impact-Pack的wildcards目录(Windows为目录联结,其他OS为符号链接);现有文件自动迁移",
|
||||
helpSettings11: "文字大小:单个滑块(10–28 px)可一次性调整所有提示词和聊天文本框的字体大小 — GenerateUI正/负向提示词、AI助手聊天输入、预设正/负向提示、通配符提示和文件编辑器、元数据提示词全文预览;即时生效并通过「保存设置」保存",
|
||||
@@ -5356,6 +5428,39 @@ const LANGUAGES = {
|
||||
selectorLoopLabel: "在Windows上使用Selector事件循环(需重启)",
|
||||
selectorLoopHint: "如果在切换含大量预览图的Models列表后ComfyUI卡死(所有标签页无响应,也无法生成),请开启此项。它会把Windows的asyncio循环从Proactor切换为Selector。重启ComfyUI后生效;asyncio子进程功能将不可用,并发连接数上限约为512。没有问题时请保持关闭。",
|
||||
selectorLoopSaved: "已保存。重启ComfyUI后生效。",
|
||||
promptTranslateSection: "提示词翻译(母语 → 英语)",
|
||||
promptTranslateEnable: "在生成UI中启用提示词翻译",
|
||||
promptTranslateHint: "用自己的语言输入提示词,并通过本地Ollama模型(默认PLaMo 3 Translate)翻译成英语。Positive/Negative提示词旁会显示🌐按钮。只翻译包含非ASCII字母的部分;逗号、(权重:1.2)、[ ]、{a|b}、<lora:...>、__通配符__、embedding:名称和BREAK保持不变。PLaMo 3 Translate可免费用于个人和非商业用途(PLaMo社区许可证)。",
|
||||
promptTranslateAutoHeading: "运行时自动翻译",
|
||||
promptTranslateAuto: "生成UI — Generate",
|
||||
promptTranslateAutoLab: "生成UI Lab — Run",
|
||||
promptTranslateAutoVideo: "Video Plan — Run",
|
||||
promptTranslateAutoHint: "Generate会将Positive/Negative栏的文本替换为翻译结果。Lab Run和Video Plan Run只翻译发送的内容,计划中保留原文。翻译失败时不会生成。",
|
||||
promptTranslateUnload: "翻译后从显存卸载翻译模型(下次翻译变慢,但为图像生成腾出显存)",
|
||||
promptTranslateSourceLang: "输入语言",
|
||||
promptTranslateSourceAuto: "自动检测",
|
||||
promptTranslateUrl: "Ollama URL",
|
||||
promptTranslateModel: "翻译模型",
|
||||
promptTranslateModelHint: "PLaMo 3 Translate尺寸:0.8B(0.6GB,最快)、2B(1.8GB,推荐)、8B(5.3GB)、31B(19.5GB)。Ollama中尚未下载的模型可用“下载”按钮获取。其他Ollama模型通过聊天指令进行翻译。",
|
||||
promptTranslatePull: "下载",
|
||||
promptTranslatePulling: "下载中…",
|
||||
promptTranslatePulled: "模型已下载",
|
||||
promptTranslateNotPulled: "未下载",
|
||||
promptTranslateNotPulledMark: "(未下载)",
|
||||
promptTranslateOtherModels: "其他Ollama模型",
|
||||
promptTranslateSaved: "已保存提示词翻译设置",
|
||||
promptTranslateRunning: "正在翻译提示词…",
|
||||
promptTranslateNothing: "没有需要翻译的部分(没有非ASCII文字)",
|
||||
promptTranslateBtnTitle: "将此提示词中的母语部分翻译成英语",
|
||||
promptTranslateRestore: "原文",
|
||||
promptTranslateRestoreTitle: "恢复翻译前的文本",
|
||||
aiTranslating: "翻译中…",
|
||||
promptTranslateDevice: "翻译模型的运行位置",
|
||||
promptTranslateDeviceGpu: "GPU(单次翻译最快,占用显存)",
|
||||
promptTranslateDeviceCpu: "CPU常驻(不占显存,无需等待重新加载)",
|
||||
promptTranslateDeviceHint: "CPU模式将模型常驻在内存中(2B约2GB),并在打开生成UI时于后台加载。由于不占用显存,也不会发生约4秒的重新加载,每个短语约0.3秒即可完成翻译。GPU模式每个短语约0.1秒更快,但开启“翻译后从显存卸载”时,每次翻译都要等待约4秒重新加载模型。",
|
||||
promptTranslateError: (msg) => `翻译错误: ${msg}`,
|
||||
promptTranslateModelsError: (msg) => `无法获取模型列表: ${msg}`,
|
||||
settingsTitle: "设置",
|
||||
langLabel: "UI语言",
|
||||
summaryLangLabel: "摘要语言",
|
||||
|
||||
+31
-3
@@ -19,6 +19,7 @@ import { syncJsonHighlight, syncScroll } from "./json-highlight.js";
|
||||
import { _expandWildcardsInWorkflow } from "./generate-tab.js";
|
||||
import { pmGroups, getPresetsInGroup } from "./prompt-presets.js";
|
||||
import { isVideoFilename, extractLastFrameBlob } from "./video-utils.js";
|
||||
import { translateButtonsHtml, attachTranslateButtons, isAutoTranslateOn, translateWorkflowPrompts } from "./prompt-translate.js";
|
||||
|
||||
// Checkpoint/LoRA/VAE are three fully independent keyframe timelines — same as
|
||||
// Prompt/KSampler — each with its own +/-, its own atIteration sequence, its own
|
||||
@@ -834,11 +835,17 @@ function _openCellModal(col, idx) {
|
||||
<button type="button" id="wfm-lab-modal-clear" class="wfm-btn wfm-btn-sm">${t("labClearPrompt")}</button>
|
||||
</div>
|
||||
<div class="wfm-lab-modal-row">
|
||||
<label>Positive</label>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<label style="flex:1;">Positive</label>
|
||||
${translateButtonsHtml("wfm-lab-modal-positive")}
|
||||
</div>
|
||||
<textarea id="wfm-lab-modal-positive" class="wfm-input" rows="3">${escapeHtml(v.positive || "")}</textarea>
|
||||
</div>
|
||||
<div class="wfm-lab-modal-row">
|
||||
<label>Negative</label>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<label style="flex:1;">Negative</label>
|
||||
${translateButtonsHtml("wfm-lab-modal-negative")}
|
||||
</div>
|
||||
<textarea id="wfm-lab-modal-negative" class="wfm-input" rows="3">${escapeHtml(v.negative || "")}</textarea>
|
||||
</div>
|
||||
<div class="wfm-lab-modal-checkbox">
|
||||
@@ -915,7 +922,10 @@ function _openCellModal(col, idx) {
|
||||
<button id="wfm-lab-modal-save" class="wfm-btn wfm-btn-primary">${t("save")}</button>
|
||||
</div>`;
|
||||
openModal(`${col} #${idx + 1}`, html);
|
||||
if (col === "prompt") _wireLabPromptModalExtras(idx);
|
||||
if (col === "prompt") {
|
||||
_wireLabPromptModalExtras(idx);
|
||||
attachTranslateButtons(document.getElementById("wfm-lab-modal-fields") || document);
|
||||
}
|
||||
if (col === "checkpoint" || col === "vae") _wireLabValueFilter(col);
|
||||
if (col === "lora") _wireLabLoraFilter();
|
||||
|
||||
@@ -1452,6 +1462,10 @@ async function _runLabBatch() {
|
||||
// mid-batch (see comfyWorkflow.isVideoWorkflow and generate-tab.js's own use of this).
|
||||
const isVideoWf = comfyWorkflow.isVideoWorkflow(comfyUI.currentAnalysis);
|
||||
|
||||
// Settings -> Prompt Translation -> "Lab Run": translate each iteration's prompts
|
||||
// (the plan itself keeps the original text).
|
||||
const autoTranslate = await isAutoTranslateOn("lab");
|
||||
|
||||
let completed = 0, failed = 0;
|
||||
let prevLoraInjection = null;
|
||||
try {
|
||||
@@ -1465,6 +1479,20 @@ async function _runLabBatch() {
|
||||
const { workflow, seed, loraInjection } = _buildWorkflowForIteration(i, chainRef, prevLoraInjection);
|
||||
prevLoraInjection = loraInjection;
|
||||
const workflowExpanded = await _expandWildcardsInWorkflow(workflow);
|
||||
if (autoTranslate) {
|
||||
try {
|
||||
if (progressText) progressText.textContent = `[${i}/${_lab.batchCount}] ${t("promptTranslateRunning")}`;
|
||||
await translateWorkflowPrompts(workflowExpanded, comfyUI.currentAnalysis.prompt_nodes);
|
||||
} catch (err) {
|
||||
// Do not send untranslated text - stop the batch like GenerateUI does.
|
||||
// The end-of-batch "stopped" toast replaces this one, so also keep the
|
||||
// reason in the progress line.
|
||||
showToast(t("promptTranslateError", err.message), "error");
|
||||
if (progressText) progressText.textContent = t("promptTranslateError", err.message);
|
||||
_aborted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { images } = await comfyUI.generate(workflowExpanded, {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* PLaMo Translate over a chat API (Ollama /api/chat, LM Studio, ...).
|
||||
*
|
||||
* PLaMo Translate is a translation-only model. Its chat template treats the user
|
||||
* message as source text and a system message that does not start with
|
||||
* "[plamo-translate]" as mere context, so instruction prompts are translated or
|
||||
* ignored and the direction falls back to Japanese <-> English auto-switching.
|
||||
* Per the model card, chat clients pass the languages in a leading
|
||||
* "[plamo-translate]" system message and send the text alone.
|
||||
*
|
||||
* Keep in sync with the inline copy in web/comfyui/node_sets_menu.js (side panel A tab),
|
||||
* which cannot import from static/.
|
||||
*/
|
||||
|
||||
// Languages PLaMo 3 Translate supports.
|
||||
export const PLAMO_LANGS = [
|
||||
"ar", "az", "be", "bg", "bn", "ca", "ceb", "de", "en", "es", "et", "eu", "fa", "fr", "gu", "he",
|
||||
"hi", "hr", "hu", "hy", "id", "it", "ja", "jv", "ka", "kk", "km", "ko", "lt", "lv", "ml", "mr",
|
||||
"ms", "my", "ne", "nl", "pa", "pl", "ru", "si", "sk", "sl", "sr", "sw", "ta", "te", "th", "tl",
|
||||
"ur", "uz", "vi", "zh",
|
||||
];
|
||||
|
||||
export const PLAMO_STOP = ["<|plamo:tag|>", "<|plamo:bos|>"];
|
||||
|
||||
export function isPlamoModel(model) {
|
||||
return /plamo/i.test(model || "");
|
||||
}
|
||||
|
||||
let _nameMap = null;
|
||||
function _buildNameMap() {
|
||||
const map = new Map();
|
||||
const locales = ["en", "ja", "zh", typeof navigator !== "undefined" ? navigator.language : "en"];
|
||||
for (const code of PLAMO_LANGS) {
|
||||
map.set(code, code);
|
||||
for (const loc of [...locales, code]) { // `code` adds the endonym (français, 한국어, ...)
|
||||
try {
|
||||
const name = new Intl.DisplayNames([loc], { type: "language" }).of(code);
|
||||
if (name) map.set(name.toLowerCase(), code);
|
||||
} catch { /* Intl.DisplayNames unavailable */ }
|
||||
}
|
||||
}
|
||||
// Common spellings Intl does not produce
|
||||
[["chinese", "zh"], ["中文", "zh"], ["中国語", "zh"], ["filipino", "tl"], ["tagalog", "tl"], ["persian", "fa"], ["farsi", "fa"]]
|
||||
.forEach(([k, v]) => map.set(k, v));
|
||||
return map;
|
||||
}
|
||||
|
||||
/** "ja" / "Japanese" / "日本語" / "French" -> PLaMo language code, or "" when unknown. */
|
||||
export function plamoLangCode(nameOrCode) {
|
||||
const key = (nameOrCode || "").trim().toLowerCase();
|
||||
if (!key) return "";
|
||||
if (!_nameMap) _nameMap = _buildNameMap();
|
||||
return _nameMap.get(key) || _nameMap.get(key.split(/[-_]/)[0]) || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat messages for translating ``text`` with PLaMo. ``src``/``dst`` are codes or
|
||||
* language names; an unknown source is left to auto-detection. Throws when the
|
||||
* target language is not one PLaMo supports.
|
||||
*/
|
||||
export function buildPlamoMessages(text, src, dst) {
|
||||
const dstCode = plamoLangCode(dst);
|
||||
if (!dstCode) throw new Error(`PLaMo Translate does not support the target language "${dst}"`);
|
||||
const srcCode = plamoLangCode(src);
|
||||
const header = `[plamo-translate]\ntarget_lang=${dstCode}` + (srcCode && srcCode !== dstCode ? `\nsource_lang=${srcCode}` : "");
|
||||
return [
|
||||
{ role: "system", content: header },
|
||||
{ role: "user", content: text },
|
||||
];
|
||||
}
|
||||
|
||||
export function cleanPlamoOutput(text) {
|
||||
return (text || "").replace(/<\|plamo:[a-z_]+\|>/g, "").trim();
|
||||
}
|
||||
@@ -46,30 +46,37 @@ function _stripThinkingTags(text) {
|
||||
}
|
||||
|
||||
// Ollamaは `think` (bool) と `options.num_predict`、OpenAI互換は `max_tokens` で制御
|
||||
function _applyGenOptions(body, backend, cfg) {
|
||||
// extra: { temperature, stop, noThink } — 翻訳専用モデル(PLaMo)向けの追加指定
|
||||
function _applyGenOptions(body, backend, cfg, extra = {}) {
|
||||
if (backend === "ollama") {
|
||||
body.think = !!cfg.thinkingMode;
|
||||
if (cfg.maxTokens > 0) body.options = { ...(body.options || {}), num_predict: cfg.maxTokens };
|
||||
} else if (cfg.maxTokens > 0) {
|
||||
body.max_tokens = cfg.maxTokens;
|
||||
if (!extra.noThink) body.think = !!cfg.thinkingMode;
|
||||
const opts = { ...(body.options || {}) };
|
||||
if (cfg.maxTokens > 0) opts.num_predict = cfg.maxTokens;
|
||||
if (extra.temperature !== undefined) opts.temperature = extra.temperature;
|
||||
if (extra.stop) opts.stop = extra.stop;
|
||||
if (Object.keys(opts).length) body.options = opts;
|
||||
} else {
|
||||
if (cfg.maxTokens > 0) body.max_tokens = cfg.maxTokens;
|
||||
if (extra.temperature !== undefined) body.temperature = extra.temperature;
|
||||
if (extra.stop) body.stop = extra.stop;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
async function chatWithAi(model, messages) {
|
||||
async function chatWithAi(model, messages, extra = {}) {
|
||||
const cfg = getPromptAiConfig();
|
||||
const { backend, url } = cfg;
|
||||
if (backend === "ollama") {
|
||||
const res = await fetch(`${url}/api/chat`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(_applyGenOptions({ model, messages, stream: false }, backend, cfg)),
|
||||
body: JSON.stringify(_applyGenOptions({ model, messages, stream: false }, backend, cfg, extra)),
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const text = (await res.json()).message?.content || "";
|
||||
return cfg.thinkingMode ? text : _stripThinkingTags(text);
|
||||
} else if (backend === "unsloth") {
|
||||
const body = _applyGenOptions({ model, messages: _toOpenAiMessages(messages), stream: false }, backend, cfg);
|
||||
const body = _applyGenOptions({ model, messages: _toOpenAiMessages(messages), stream: false }, backend, cfg, extra);
|
||||
const data = await unslothProxy(url, "/v1/chat/completions", "POST", body);
|
||||
const msg = data.choices?.[0]?.message;
|
||||
// Unslothはreasoningを別フィールドで返すため<think>タグに畳んで表示に混ぜる
|
||||
@@ -80,7 +87,7 @@ async function chatWithAi(model, messages) {
|
||||
const res = await fetch(`${url}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(_applyGenOptions({ model, messages: _toOpenAiMessages(messages), stream: false }, backend, cfg)),
|
||||
body: JSON.stringify(_applyGenOptions({ model, messages: _toOpenAiMessages(messages), stream: false }, backend, cfg, extra)),
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const text = (await res.json()).choices?.[0]?.message?.content || "";
|
||||
@@ -141,6 +148,25 @@ function addChatMessage(role, content) {
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
}
|
||||
|
||||
// 応答待ちの間だけ表示するプレースホルダ(初回はモデル読み込みで十数秒かかることがある)
|
||||
function _addPendingMessage(text) {
|
||||
const chat = document.getElementById("wfm-ollama-chat");
|
||||
if (!chat) return null;
|
||||
const msg = document.createElement("div");
|
||||
msg.className = "wfm-ollama-msg assistant wfm-ollama-pending";
|
||||
const roleLabel = document.createElement("div");
|
||||
roleLabel.className = "wfm-ollama-msg-role";
|
||||
roleLabel.textContent = "AI";
|
||||
const contentDiv = document.createElement("div");
|
||||
contentDiv.className = "wfm-ollama-msg-content";
|
||||
contentDiv.style.opacity = "0.6";
|
||||
contentDiv.textContent = text;
|
||||
msg.append(roleLabel, contentDiv);
|
||||
chat.appendChild(msg);
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
return msg;
|
||||
}
|
||||
|
||||
function clearChat() {
|
||||
ollamaState.chatHistory = [];
|
||||
ollamaState.attachedImage = null;
|
||||
@@ -243,13 +269,16 @@ async function sendMessage() {
|
||||
const sendBtn = document.getElementById("wfm-ollama-send-btn");
|
||||
if (sendBtn) sendBtn.disabled = true;
|
||||
|
||||
const pending = _addPendingMessage("…");
|
||||
try {
|
||||
const reply = await chatWithAi(model, ollamaState.chatHistory);
|
||||
pending?.remove();
|
||||
addChatMessage("assistant", reply);
|
||||
ollamaState.chatHistory.push({ role: "assistant", content: reply });
|
||||
ollamaState.attachedImage = null;
|
||||
updateAttachmentDisplay();
|
||||
} catch (err) {
|
||||
pending?.remove();
|
||||
showToast(t("error") + ": " + err.message, "error");
|
||||
} finally {
|
||||
if (sendBtn) sendBtn.disabled = false;
|
||||
@@ -281,7 +310,24 @@ async function sendTranslate(direction) {
|
||||
.map((id) => document.getElementById(id));
|
||||
btns.forEach((b) => { if (b) b.disabled = true; });
|
||||
|
||||
const pending = _addPendingMessage(t("aiTranslating"));
|
||||
try {
|
||||
// PLaMo Translate is a translation-only model: anything in the user message is
|
||||
// treated as source text, so an instruction prompt gets translated too, and without
|
||||
// an explicit target it only switches between Japanese and English (ZH→EN / EN→ZH
|
||||
// came out in Japanese). Per its model card, Ollama/LM Studio clients pass the
|
||||
// languages in a leading "[plamo-translate]" system message and send the text alone.
|
||||
if (/plamo/i.test(model)) {
|
||||
const [src, dst] = { ja2en: ["ja", "en"], en2ja: ["en", "ja"], zh2en: ["zh", "en"], en2zh: ["en", "zh"] }[direction];
|
||||
const reply = await chatWithAi(model, [
|
||||
{ role: "system", content: `[plamo-translate]\ntarget_lang=${dst}\nsource_lang=${src}` },
|
||||
{ role: "user", content: text },
|
||||
], { temperature: 0, stop: ["<|plamo:tag|>", "<|plamo:bos|>"], noThink: true });
|
||||
pending?.remove();
|
||||
addChatMessage("assistant", reply.replace(/<\|plamo:[a-z_]+\|>/g, "").trim());
|
||||
return;
|
||||
}
|
||||
|
||||
const prompts = {
|
||||
ja2en: `Translate the following Japanese text into English. Output only the translated text, nothing else.\n\n${text}`,
|
||||
en2ja: `以下の英語のテキストを日本語に翻訳してください。翻訳結果のみを出力し、他の説明は不要です。\n\n${text}`,
|
||||
@@ -291,8 +337,10 @@ async function sendTranslate(direction) {
|
||||
const prompt = prompts[direction];
|
||||
|
||||
const reply = await chatWithAi(model, [{ role: "user", content: prompt }]);
|
||||
pending?.remove();
|
||||
addChatMessage("assistant", reply);
|
||||
} catch (err) {
|
||||
pending?.remove();
|
||||
showToast(t("error") + ": " + err.message, "error");
|
||||
} finally {
|
||||
btns.forEach((b) => { if (b) b.disabled = false; });
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Settings tab section for Prompt Translate (see prompt-translate.js).
|
||||
* Values live in the server settings.json so the backend can read them.
|
||||
*/
|
||||
|
||||
import { showToast } from "./app.js";
|
||||
import { t, getLang } from "./i18n.js";
|
||||
import { escapeHtml } from "./util.js";
|
||||
import { setTranslateConfig, translatePrompt } from "./prompt-translate.js";
|
||||
|
||||
const DEFAULT_URL = "http://localhost:11434";
|
||||
const DEFAULT_MODEL = "hf.co/pfnet/plamo-3-translate-2b-GGUF:Q4_K_M";
|
||||
|
||||
// Languages PLaMo 3 Translate supports (English is the target, so it is left out).
|
||||
const SOURCE_LANGS = [
|
||||
"ja", "zh", "ko", "ar", "az", "be", "bg", "bn", "ca", "ceb", "de", "es", "et", "eu", "fa", "fr",
|
||||
"gu", "he", "hi", "hr", "hu", "hy", "id", "it", "jv", "ka", "kk", "km", "lt", "lv", "ml", "mr",
|
||||
"ms", "my", "ne", "nl", "pa", "pl", "ru", "si", "sk", "sl", "sr", "sw", "ta", "te", "th", "tl",
|
||||
"ur", "uz", "vi",
|
||||
];
|
||||
|
||||
const SAMPLE_TEXT = {
|
||||
ja: "夕焼けの海辺に立つ少女、(長い黒髪:1.2), masterpiece",
|
||||
zh: "站在夕阳海边的少女, (长长的黑发:1.2), masterpiece",
|
||||
};
|
||||
|
||||
function _langName(code) {
|
||||
try {
|
||||
const dn = new Intl.DisplayNames([getLang() || "en"], { type: "language" });
|
||||
return dn.of(code) || code;
|
||||
} catch {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
export function promptTranslateSettingsHtml(s) {
|
||||
const enabled = !!s.prompt_translate_enabled;
|
||||
const src = s.prompt_translate_source_lang || "auto";
|
||||
const unload = s.prompt_translate_unload !== false;
|
||||
const cpu = s.prompt_translate_device === "cpu";
|
||||
return `
|
||||
<details class="wfm-settings-section" id="wfm-pt-settings">
|
||||
<summary class="wfm-settings-summary">${t("promptTranslateSection")}</summary>
|
||||
<div class="wfm-form-group">
|
||||
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;">
|
||||
<input type="checkbox" id="wfm-pt-enabled" ${enabled ? "checked" : ""}>
|
||||
<span>${t("promptTranslateEnable")}</span>
|
||||
</label>
|
||||
<small style="color:var(--wfm-text-secondary);font-size:11px;display:block;margin-top:4px;">
|
||||
${t("promptTranslateHint")}
|
||||
</small>
|
||||
</div>
|
||||
<div id="wfm-pt-body" style="${enabled ? "" : "opacity:0.55;"}">
|
||||
<div class="wfm-form-group">
|
||||
<label>${t("promptTranslateAutoHeading")}</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;">
|
||||
<input type="checkbox" id="wfm-pt-auto" ${s.prompt_translate_auto ? "checked" : ""}>
|
||||
<span>${t("promptTranslateAuto")}</span>
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;margin-top:2px;">
|
||||
<input type="checkbox" id="wfm-pt-auto-lab" ${s.prompt_translate_auto_lab ? "checked" : ""}>
|
||||
<span>${t("promptTranslateAutoLab")}</span>
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;margin-top:2px;">
|
||||
<input type="checkbox" id="wfm-pt-auto-video" ${s.prompt_translate_auto_video ? "checked" : ""}>
|
||||
<span>${t("promptTranslateAutoVideo")}</span>
|
||||
</label>
|
||||
<small style="color:var(--wfm-text-secondary);font-size:11px;display:block;margin-top:4px;">
|
||||
${t("promptTranslateAutoHint")}
|
||||
</small>
|
||||
</div>
|
||||
<div class="wfm-form-group">
|
||||
<label>${t("promptTranslateDevice")}</label>
|
||||
<select class="wfm-select" id="wfm-pt-device" style="width:100%;">
|
||||
<option value="gpu" ${cpu ? "" : "selected"}>${t("promptTranslateDeviceGpu")}</option>
|
||||
<option value="cpu" ${cpu ? "selected" : ""}>${t("promptTranslateDeviceCpu")}</option>
|
||||
</select>
|
||||
<label id="wfm-pt-unload-label" style="display:flex;align-items:center;gap:6px;cursor:pointer;margin-top:6px;${cpu ? "opacity:0.5;" : ""}">
|
||||
<input type="checkbox" id="wfm-pt-unload" ${unload ? "checked" : ""} ${cpu ? "disabled" : ""}>
|
||||
<span>${t("promptTranslateUnload")}</span>
|
||||
</label>
|
||||
<small style="color:var(--wfm-text-secondary);font-size:11px;display:block;margin-top:4px;">
|
||||
${t("promptTranslateDeviceHint")}
|
||||
</small>
|
||||
</div>
|
||||
<div class="wfm-form-group">
|
||||
<label>${t("promptTranslateSourceLang")}</label>
|
||||
<select class="wfm-select" id="wfm-pt-source-lang" style="width:100%;">
|
||||
<option value="auto" ${src === "auto" ? "selected" : ""}>${t("promptTranslateSourceAuto")}</option>
|
||||
${SOURCE_LANGS.map((c) => `<option value="${c}" ${src === c ? "selected" : ""}>${escapeHtml(_langName(c))} (${c})</option>`).join("")}
|
||||
</select>
|
||||
</div>
|
||||
<div class="wfm-form-group">
|
||||
<label>${t("promptTranslateUrl")}</label>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<input type="text" class="wfm-input" id="wfm-pt-url" value="${escapeHtml(s.prompt_translate_url || "")}" placeholder="${DEFAULT_URL}">
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-pt-url-save">${t("save")}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wfm-form-group">
|
||||
<label>${t("promptTranslateModel")}</label>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<select class="wfm-select" id="wfm-pt-model" style="flex:1;min-width:0;">
|
||||
<option value="${escapeHtml(s.prompt_translate_model || DEFAULT_MODEL)}" selected>${escapeHtml(s.prompt_translate_model || DEFAULT_MODEL)}</option>
|
||||
</select>
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-pt-model-refresh">${t("refresh")}</button>
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-pt-model-pull" style="display:none;">${t("promptTranslatePull")}</button>
|
||||
</div>
|
||||
<small id="wfm-pt-model-hint" style="color:var(--wfm-text-secondary);font-size:11px;display:block;margin-top:4px;">
|
||||
${t("promptTranslateModelHint")}
|
||||
</small>
|
||||
</div>
|
||||
<div class="wfm-form-group">
|
||||
<div style="display:flex;gap:8px;align-items:center;">
|
||||
<button class="wfm-btn wfm-btn-sm" id="wfm-pt-test">${t("test")}</button>
|
||||
<span id="wfm-pt-test-status" style="font-size:12px;word-break:break-all;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>`;
|
||||
}
|
||||
|
||||
export function initPromptTranslateSettings(serverSettings, saveServerSettings) {
|
||||
const $ = (id) => document.getElementById(id);
|
||||
let installed = [];
|
||||
let presets = [];
|
||||
|
||||
const save = async (patch, toast = true) => {
|
||||
try {
|
||||
const res = await saveServerSettings(patch);
|
||||
if (res?.error) throw new Error(res.error);
|
||||
Object.assign(serverSettings, patch);
|
||||
setTranslateConfig(patch);
|
||||
if (toast) showToast(t("promptTranslateSaved"), "success");
|
||||
return true;
|
||||
} catch (err) {
|
||||
showToast(t("errorWithMsg", err.message), "error");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const currentModel = () => serverSettings.prompt_translate_model || DEFAULT_MODEL;
|
||||
|
||||
const renderModelOptions = () => {
|
||||
const sel = $("wfm-pt-model");
|
||||
if (!sel) return;
|
||||
const cur = currentModel();
|
||||
const plamoInstalled = installed.filter((m) => /plamo/i.test(m));
|
||||
// Embedding-only models cannot generate text, so they are not offered.
|
||||
const others = installed.filter((m) => !/plamo/i.test(m) && !/embed|minilm/i.test(m));
|
||||
const missingPresets = presets.filter((p) => !installed.includes(p));
|
||||
const opt = (v, label) => `<option value="${escapeHtml(v)}" ${v === cur ? "selected" : ""}>${escapeHtml(label || v)}</option>`;
|
||||
let html = "";
|
||||
if (plamoInstalled.length) html += `<optgroup label="PLaMo 3 Translate">${plamoInstalled.map((m) => opt(m)).join("")}</optgroup>`;
|
||||
if (missingPresets.length) html += `<optgroup label="${escapeHtml(t("promptTranslateNotPulled"))}">${missingPresets.map((m) => opt(m, `${m} ${t("promptTranslateNotPulledMark")}`)).join("")}</optgroup>`;
|
||||
if (others.length) html += `<optgroup label="${escapeHtml(t("promptTranslateOtherModels"))}">${others.map((m) => opt(m)).join("")}</optgroup>`;
|
||||
if (![...installed, ...presets].includes(cur)) html = opt(cur) + html;
|
||||
sel.innerHTML = html;
|
||||
updatePullButton();
|
||||
};
|
||||
|
||||
const updatePullButton = () => {
|
||||
const btn = $("wfm-pt-model-pull");
|
||||
if (!btn) return;
|
||||
const m = currentModel();
|
||||
btn.style.display = presets.includes(m) && !installed.includes(m) ? "" : "none";
|
||||
};
|
||||
|
||||
const refreshModels = async (quiet = false) => {
|
||||
try {
|
||||
const res = await fetch("/api/wfm/translate/models");
|
||||
const data = await res.json();
|
||||
presets = data.presets || [];
|
||||
if (data.status !== "success") throw new Error(data.message || `HTTP ${res.status}`);
|
||||
installed = data.models || [];
|
||||
} catch (err) {
|
||||
installed = [];
|
||||
if (!quiet) showToast(t("promptTranslateModelsError", err.message), "error");
|
||||
}
|
||||
renderModelOptions();
|
||||
};
|
||||
|
||||
// CPU mode keeps the model resident: preload it now, and release it when leaving CPU mode
|
||||
// or turning the feature off so it does not sit in RAM unused.
|
||||
const postTranslate = (path) => fetch(`/api/wfm/translate/${path}`, { method: "POST" }).catch(() => {});
|
||||
const isCpu = () => serverSettings.prompt_translate_device === "cpu";
|
||||
|
||||
$("wfm-pt-device")?.addEventListener("change", async (e) => {
|
||||
const device = e.target.value === "cpu" ? "cpu" : "gpu";
|
||||
const prev = isCpu() ? "cpu" : "gpu";
|
||||
if (!(await save({ prompt_translate_device: device }))) { e.target.value = prev; return; }
|
||||
const cpu = device === "cpu";
|
||||
const cb = $("wfm-pt-unload");
|
||||
if (cb) cb.disabled = cpu;
|
||||
const lbl = $("wfm-pt-unload-label");
|
||||
if (lbl) lbl.style.opacity = cpu ? "0.5" : "";
|
||||
// Ollama reloads the model for the new placement; drop the old instance first.
|
||||
await postTranslate("unload");
|
||||
if (cpu) postTranslate("warmup");
|
||||
});
|
||||
|
||||
$("wfm-pt-enabled")?.addEventListener("change", async (e) => {
|
||||
const on = !!e.target.checked;
|
||||
if (!(await save({ prompt_translate_enabled: on }))) { e.target.checked = !on; return; }
|
||||
if (isCpu()) postTranslate(on ? "warmup" : "unload");
|
||||
const body = $("wfm-pt-body");
|
||||
if (body) body.style.opacity = on ? "" : "0.55";
|
||||
});
|
||||
[["wfm-pt-auto", "prompt_translate_auto"], ["wfm-pt-auto-lab", "prompt_translate_auto_lab"], ["wfm-pt-auto-video", "prompt_translate_auto_video"]]
|
||||
.forEach(([id, key]) => {
|
||||
$(id)?.addEventListener("change", async (e) => {
|
||||
const on = !!e.target.checked;
|
||||
if (!(await save({ [key]: on }))) e.target.checked = !on;
|
||||
});
|
||||
});
|
||||
$("wfm-pt-unload")?.addEventListener("change", async (e) => {
|
||||
const on = !!e.target.checked;
|
||||
if (!(await save({ prompt_translate_unload: on }))) e.target.checked = !on;
|
||||
});
|
||||
$("wfm-pt-source-lang")?.addEventListener("change", (e) => save({ prompt_translate_source_lang: e.target.value }));
|
||||
$("wfm-pt-url-save")?.addEventListener("click", async () => {
|
||||
const url = ($("wfm-pt-url")?.value || "").trim();
|
||||
if (await save({ prompt_translate_url: url })) refreshModels();
|
||||
});
|
||||
$("wfm-pt-model")?.addEventListener("change", async (e) => {
|
||||
const cpu = isCpu() && serverSettings.prompt_translate_enabled;
|
||||
if (cpu) await postTranslate("unload"); // release the previous resident model
|
||||
await save({ prompt_translate_model: e.target.value });
|
||||
if (cpu) postTranslate("warmup");
|
||||
updatePullButton();
|
||||
});
|
||||
$("wfm-pt-model-refresh")?.addEventListener("click", () => refreshModels());
|
||||
|
||||
$("wfm-pt-model-pull")?.addEventListener("click", async (e) => {
|
||||
const btn = e.currentTarget;
|
||||
const model = currentModel();
|
||||
btn.disabled = true;
|
||||
const label = btn.textContent;
|
||||
btn.textContent = t("promptTranslatePulling");
|
||||
try {
|
||||
const res = await fetch("/api/wfm/translate/pull", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ model }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status !== "success") throw new Error(data.message || `HTTP ${res.status}`);
|
||||
showToast(t("promptTranslatePulled"), "success");
|
||||
await refreshModels(true);
|
||||
} catch (err) {
|
||||
showToast(t("errorWithMsg", err.message), "error");
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = label;
|
||||
}
|
||||
});
|
||||
|
||||
$("wfm-pt-test")?.addEventListener("click", async (e) => {
|
||||
const btn = e.currentTarget;
|
||||
const status = $("wfm-pt-test-status");
|
||||
const sample = SAMPLE_TEXT[getLang()] || SAMPLE_TEXT.ja;
|
||||
btn.disabled = true;
|
||||
if (status) { status.style.color = "var(--wfm-text-secondary)"; status.textContent = t("promptTranslateRunning"); }
|
||||
const t0 = performance.now();
|
||||
try {
|
||||
const out = await translatePrompt(sample);
|
||||
const sec = ((performance.now() - t0) / 1000).toFixed(1);
|
||||
if (status) { status.style.color = "var(--wfm-success, #4caf50)"; status.textContent = `${sample} → ${out} (${sec}s)`; }
|
||||
} catch (err) {
|
||||
if (status) { status.style.color = "var(--wfm-danger, #f44336)"; status.textContent = err.message; }
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Fill the model list when the section is first opened (avoids an Ollama call on every Settings visit).
|
||||
const section = $("wfm-pt-settings");
|
||||
section?.addEventListener("toggle", () => { if (section.open && !installed.length) refreshModels(true); });
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* Prompt Translate — type GenerateUI prompts in your own language and translate
|
||||
* them into English with a local Ollama model (PLaMo 3 Translate by default).
|
||||
*
|
||||
* Works like the A1111 prompt-translator extensions: only the parts of a prompt
|
||||
* that contain non-ASCII letters are sent to the model. Prompt syntax such as
|
||||
* commas, (weights:1.2), [ ], {a|b}, <lora:...>, __wildcards__,
|
||||
* embedding:name and BREAK/AND is kept as it is, so a mixed prompt like
|
||||
* "masterpiece, (赤いドレスの女性:1.2), <lora:foo:0.8>" only translates the Japanese part.
|
||||
*/
|
||||
|
||||
import { showToast } from "./app.js";
|
||||
import { t } from "./i18n.js";
|
||||
|
||||
let _config = null;
|
||||
let _configPromise = null;
|
||||
|
||||
// textarea -> { original, translated } for the "restore original" button
|
||||
const _history = new WeakMap();
|
||||
|
||||
export async function loadTranslateConfig(force = false) {
|
||||
if (_config && !force) return _config;
|
||||
if (!_configPromise || force) {
|
||||
_configPromise = fetch("/api/wfm/translate/config")
|
||||
.then((r) => r.json())
|
||||
.then((d) => { _config = d.config || {}; return _config; })
|
||||
.catch(() => { _config = {}; return _config; })
|
||||
.finally(() => { _configPromise = null; });
|
||||
}
|
||||
return _configPromise;
|
||||
}
|
||||
|
||||
export function getTranslateConfig() {
|
||||
return _config || {};
|
||||
}
|
||||
|
||||
export function isTranslateEnabled() {
|
||||
return !!_config?.prompt_translate_enabled;
|
||||
}
|
||||
|
||||
/** Called by the Settings tab after saving, so open UIs update without a reload. */
|
||||
export function setTranslateConfig(patch) {
|
||||
_config = { ...(_config || {}), ...patch };
|
||||
updateTranslateButtons();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Prompt splitting
|
||||
// ------------------------------------------------------------
|
||||
|
||||
// Spans that are never translated: prompt syntax and delimiters.
|
||||
const PROTECTED_RE = /\b(?:BREAK|AND)\b|<[^<>\n]*>|__[^\s_][^\s]*?__|embedding:[^\s,()[\]{}|]+|:\s*-?\d+(?:\.\d+)?|[,\n()[\]{}|]/g;
|
||||
// A piece needs translation when it contains a non-ASCII letter (CJK, Cyrillic, accented Latin, ...).
|
||||
const NEEDS_TRANSLATION_RE = /(?![\x00-\x7F])\p{L}/u;
|
||||
|
||||
/** Split a prompt into [{ text, translate }] pieces. Joining all .text gives back the input. */
|
||||
export function splitPrompt(text) {
|
||||
const pieces = [];
|
||||
let last = 0;
|
||||
for (const m of text.matchAll(PROTECTED_RE)) {
|
||||
if (m.index > last) pieces.push(text.slice(last, m.index));
|
||||
pieces.push({ keep: m[0] });
|
||||
last = m.index + m[0].length;
|
||||
}
|
||||
if (last < text.length) pieces.push(text.slice(last));
|
||||
return pieces.map((p) => {
|
||||
if (typeof p !== "string") return { text: p.keep, translate: false };
|
||||
return { text: p, translate: NEEDS_TRANSLATION_RE.test(p) };
|
||||
});
|
||||
}
|
||||
|
||||
export function needsTranslation(text) {
|
||||
return splitPrompt(text || "").some((p) => p.translate);
|
||||
}
|
||||
|
||||
// Keep the surrounding whitespace of a piece and tidy the model output for prompt use.
|
||||
function _rebuild(source, translated) {
|
||||
const lead = source.match(/^\s*/)[0];
|
||||
const trail = source.match(/\s*$/)[0];
|
||||
const core = source.trim();
|
||||
let out = (translated || "").replace(/\s*\n+\s*/g, " ").trim();
|
||||
// A tag such as "赤いドレス" should not become "Red dress." — drop the final period
|
||||
// unless the source itself was a full sentence.
|
||||
if (!/[。..!?!?]$/.test(core)) out = out.replace(/[.。]+$/, "");
|
||||
// "少女、(" -> "girl, (" rather than "girl,(" — a Japanese comma needs no space, an English one does.
|
||||
if (!trail && /[,;:]$/.test(out)) out += " ";
|
||||
return lead + out + trail;
|
||||
}
|
||||
|
||||
/** Translate a whole prompt string. Throws on backend errors. */
|
||||
export async function translatePrompt(text) {
|
||||
const pieces = splitPrompt(text || "");
|
||||
const cores = [...new Set(pieces.filter((p) => p.translate).map((p) => p.text.trim()))];
|
||||
if (cores.length === 0) return text;
|
||||
|
||||
const res = await fetch("/api/wfm/translate", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ texts: cores }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || data.status !== "success") {
|
||||
throw new Error(data.message || `HTTP ${res.status}`);
|
||||
}
|
||||
const map = new Map(cores.map((c, i) => [c, data.translations[i] ?? c]));
|
||||
return pieces.map((p) => (p.translate ? _rebuild(p.text, map.get(p.text.trim())) : p.text)).join("");
|
||||
}
|
||||
|
||||
// Replace textarea text while keeping the browser's Ctrl+Z history where possible.
|
||||
function _setTextareaValue(ta, value) {
|
||||
ta.focus();
|
||||
ta.select();
|
||||
let ok = false;
|
||||
try { ok = document.execCommand("insertText", false, value); } catch { ok = false; }
|
||||
if (!ok || ta.value !== value) ta.value = value;
|
||||
ta.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate one textarea in place. Returns true when the text changed.
|
||||
* The original text is remembered for the restore button.
|
||||
*/
|
||||
export async function translateTextarea(ta) {
|
||||
if (!ta || ta.disabled) return false;
|
||||
const original = ta.value;
|
||||
if (!needsTranslation(original)) return false;
|
||||
const translated = await translatePrompt(original);
|
||||
if (translated === original) return false;
|
||||
_setTextareaValue(ta, translated);
|
||||
_history.set(ta, { original, translated });
|
||||
_updateRestoreButton(ta);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function restoreTextarea(ta) {
|
||||
const h = ta && _history.get(ta);
|
||||
if (!h || ta.disabled) return;
|
||||
_setTextareaValue(ta, h.original);
|
||||
_history.delete(ta);
|
||||
_updateRestoreButton(ta);
|
||||
}
|
||||
|
||||
const PROMPT_TEXTAREAS = ["wfm-prompt-pos-text", "wfm-prompt-neg-text"];
|
||||
|
||||
/**
|
||||
* GenerateUI hook: when translation and "translate before Generate" are both on,
|
||||
* translate the Positive/Negative prompt textareas. Throws if translation fails so
|
||||
* the caller can stop instead of sending untranslated text.
|
||||
*/
|
||||
export async function autoTranslateBeforeGenerate() {
|
||||
if (!(await isAutoTranslateOn("generate"))) return false;
|
||||
const targets = PROMPT_TEXTAREAS.map((id) => document.getElementById(id))
|
||||
.filter((ta) => ta && needsTranslation(ta.value));
|
||||
if (targets.length === 0) return false;
|
||||
const progress = document.getElementById("wfm-gen-progress-text");
|
||||
if (progress) progress.textContent = t("promptTranslateRunning");
|
||||
for (const ta of targets) await translateTextarea(ta);
|
||||
if (progress) progress.textContent = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Settings key for each "translate automatically when running" checkbox.
|
||||
const AUTO_KEYS = {
|
||||
generate: "prompt_translate_auto",
|
||||
lab: "prompt_translate_auto_lab",
|
||||
video: "prompt_translate_auto_video",
|
||||
};
|
||||
|
||||
/** True when translation is enabled and the auto checkbox for ``kind`` (generate/lab/video) is on. */
|
||||
export async function isAutoTranslateOn(kind) {
|
||||
await loadTranslateConfig();
|
||||
return isTranslateEnabled() && !!_config[AUTO_KEYS[kind]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Lab Run hook: translate the prompt texts inside one iteration's API-format workflow
|
||||
* copy, in place. The Lab plan's own keyframe values stay in the user's language;
|
||||
* repeated phrases come from the server-side cache, so later iterations cost ~nothing.
|
||||
* Throws on translation failure so the caller can stop the batch.
|
||||
*/
|
||||
export async function translateWorkflowPrompts(workflow, promptNodes) {
|
||||
for (const n of promptNodes || []) {
|
||||
const inputs = workflow?.[n.id]?.inputs;
|
||||
const key = n.textKey || "text";
|
||||
if (inputs && typeof inputs[key] === "string" && needsTranslation(inputs[key])) {
|
||||
inputs[key] = await translatePrompt(inputs[key]);
|
||||
}
|
||||
}
|
||||
return workflow;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Buttons in the GenerateUI Prompt tab
|
||||
// ------------------------------------------------------------
|
||||
|
||||
function _updateRestoreButton(ta) {
|
||||
const btn = document.querySelector(`.wfm-pt-restore[data-target="${ta.id}"]`);
|
||||
if (!btn) return;
|
||||
const h = _history.get(ta);
|
||||
btn.style.display = isTranslateEnabled() && h && ta.value === h.translated ? "" : "none";
|
||||
}
|
||||
|
||||
export function updateTranslateButtons() {
|
||||
const on = isTranslateEnabled();
|
||||
document.querySelectorAll(".wfm-pt-translate").forEach((b) => { b.style.display = on ? "" : "none"; });
|
||||
document.querySelectorAll(".wfm-pt-restore").forEach((b) => {
|
||||
const ta = document.getElementById(b.dataset.target);
|
||||
if (ta) _updateRestoreButton(ta);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire the 🌐 / restore buttons produced by translateButtonsHtml() inside ``root``
|
||||
* (GenerateUI prompt tab, Lab prompt modal, Video Plan block prompt, ...).
|
||||
*/
|
||||
export function attachTranslateButtons(root = document) {
|
||||
root.querySelectorAll(".wfm-pt-translate").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
const ta = document.getElementById(btn.dataset.target);
|
||||
if (!ta) return;
|
||||
if (!needsTranslation(ta.value)) {
|
||||
showToast(t("promptTranslateNothing"), "info");
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
const label = btn.textContent;
|
||||
btn.textContent = "…";
|
||||
try {
|
||||
await translateTextarea(ta);
|
||||
} catch (err) {
|
||||
showToast(t("promptTranslateError", err.message), "error");
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = label;
|
||||
}
|
||||
});
|
||||
});
|
||||
root.querySelectorAll(".wfm-pt-restore").forEach((btn) => {
|
||||
btn.addEventListener("click", () => restoreTextarea(document.getElementById(btn.dataset.target)));
|
||||
});
|
||||
root.querySelectorAll(".wfm-pt-restore").forEach((btn) => {
|
||||
const ta = document.getElementById(btn.dataset.target);
|
||||
ta?.addEventListener("input", () => _updateRestoreButton(ta));
|
||||
});
|
||||
loadTranslateConfig().then(() => {
|
||||
updateTranslateButtons();
|
||||
_warmupOnce();
|
||||
});
|
||||
}
|
||||
|
||||
// CPU-resident mode: load the model in the background once per page so the first 🌐
|
||||
// does not pay Ollama's ~4 s startup. The server ignores this unless CPU mode is on.
|
||||
let _warmedUp = false;
|
||||
function _warmupOnce() {
|
||||
if (_warmedUp || !isTranslateEnabled() || _config.prompt_translate_device !== "cpu") return;
|
||||
_warmedUp = true;
|
||||
fetch("/api/wfm/translate/warmup", { method: "POST" }).catch(() => {});
|
||||
}
|
||||
|
||||
/** HTML for the two buttons placed next to a prompt textarea's Apply button. */
|
||||
export function translateButtonsHtml(textareaId) {
|
||||
const on = isTranslateEnabled();
|
||||
return `<button class="wfm-btn wfm-btn-sm wfm-pt-translate" data-target="${textareaId}" title="${t("promptTranslateBtnTitle")}" style="${on ? "" : "display:none;"}">🌐</button>`
|
||||
+ `<button class="wfm-btn wfm-btn-sm wfm-pt-restore" data-target="${textareaId}" title="${t("promptTranslateRestoreTitle")}" style="display:none;">${t("promptTranslateRestore")}</button>`;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { t, getLang, getSummaryLang, setLang, setSummaryLang, getLanguageOptions
|
||||
import { getSettings, readJsonStorage, escapeHtml } from "./util.js";
|
||||
import { DECISION_BACKENDS, getDecisionSettings, saveDecisionSettings, testDecisionConnection, listDecisionModels } from "./decision-client.js";
|
||||
import { initVramSettings } from "./vram-prepare.js";
|
||||
import { initPromptTranslateSettings, promptTranslateSettingsHtml } from "./prompt-translate-settings.js";
|
||||
|
||||
const SETTINGS_KEY = "wfm_settings";
|
||||
|
||||
@@ -772,6 +773,9 @@ export async function initSettingsTab() {
|
||||
<button class="wfm-btn wfm-btn-primary wfm-btn-sm" id="wfm-settings-default-ckpt-save">${t("save")}</button>
|
||||
</details>
|
||||
|
||||
<!-- Prompt Translation (native language -> English, PLaMo 3 Translate via Ollama) -->
|
||||
${promptTranslateSettingsHtml(serverSettings)}
|
||||
|
||||
<!-- CivitAI Settings -->
|
||||
<details class="wfm-settings-section">
|
||||
<summary class="wfm-settings-summary">${t("civitaiApiKeySetting")}</summary>
|
||||
@@ -1228,6 +1232,9 @@ export async function initSettingsTab() {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Prompt translation ---
|
||||
initPromptTranslateSettings(serverSettings, saveServerSettings);
|
||||
|
||||
// --- Windows selector event loop (needs ComfyUI restart) ---
|
||||
document.getElementById("wfm-settings-selector-loop")?.addEventListener("change", async (e) => {
|
||||
const enabled = !!e.target.checked;
|
||||
|
||||
@@ -25,6 +25,7 @@ import { isVideoFilename, extractLastFrameBlob } from "./video-utils.js";
|
||||
import { locateVideoModelNodes, readTemplateDefaults, applyBlockToWorkflow } from "./video-workflow.js";
|
||||
import { VTEMP_GROUP } from "./gallery-tab.js";
|
||||
import { setResultPreview } from "./video-preview.js";
|
||||
import { translateButtonsHtml, attachTranslateButtons, isAutoTranslateOn, translatePrompt, needsTranslation } from "./prompt-translate.js";
|
||||
|
||||
const VIDEO_BLOCK_COLORS = ["#4caf7d", "#3b4b8a", "#8e5cd9", "#e0c341", "#4ac0d9", "#9ad14b", "#e08a3c", "#d9527a"];
|
||||
const VIDEO_PLAN_PREFIX = "ws_videoplan_";
|
||||
@@ -277,7 +278,10 @@ function _blockEditorHtml(block, isFirstBlock) {
|
||||
<span class="wfm-video-color-swatch" style="background:${escapeHtml(block.color)}"></span>
|
||||
<span>color</span>
|
||||
</div>
|
||||
<label id="wfm-video-block-prompt-label">Prompt</label>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<label id="wfm-video-block-prompt-label" style="flex:1;">Prompt</label>
|
||||
${translateButtonsHtml("wfm-video-block-prompt")}
|
||||
</div>
|
||||
<textarea id="wfm-video-block-prompt" class="wfm-textarea" rows="8">${escapeHtml(block.prompt || "")}</textarea>
|
||||
</div>
|
||||
`;
|
||||
@@ -385,6 +389,9 @@ function _renderBlockEditor() {
|
||||
document.getElementById("wfm-video-block-last-clear")?.addEventListener("click", () => _clearBlockImage("last"));
|
||||
|
||||
document.getElementById("wfm-video-block-prompt")?.addEventListener("input", (e) => { block.prompt = e.target.value; });
|
||||
// 🌐 native-language translation (Settings → Prompt Translation); translating fires
|
||||
// "input", so block.prompt follows the translated text.
|
||||
attachTranslateButtons(document.getElementById("wfm-video-block-prompt")?.closest(".wfm-video-block-prompt-col") || document);
|
||||
|
||||
// Durationの入力欄はtopbarのbasesettings行に固定配置されている(ブロックエディタの
|
||||
// innerHTMLと違って再生成されない)ため、ここでは現在選択中ブロックの値を反映するだけ。
|
||||
@@ -707,6 +714,10 @@ async function _runVideoPlanBatch() {
|
||||
const progressBar = document.getElementById("wfm-video-plan-progress-bar");
|
||||
const progressText = document.getElementById("wfm-video-plan-progress-text");
|
||||
|
||||
// Settings -> Prompt Translation -> "Video Plan Run": translate each block's prompt
|
||||
// when sending it (the plan itself keeps the original text).
|
||||
const autoTranslate = await isAutoTranslateOn("video");
|
||||
|
||||
let completed = 0, failed = 0;
|
||||
let prevGeneratedLastFrameRef = null;
|
||||
|
||||
@@ -727,9 +738,24 @@ async function _runVideoPlanBatch() {
|
||||
break;
|
||||
}
|
||||
|
||||
let prompt = block.prompt;
|
||||
if (autoTranslate && needsTranslation(prompt)) {
|
||||
try {
|
||||
if (progressText) progressText.textContent = `[${i + 1}/${total}] ${t("promptTranslateRunning")}`;
|
||||
prompt = await translatePrompt(prompt);
|
||||
} catch (err) {
|
||||
// The end-of-batch "stopped" toast replaces this one, so also keep the
|
||||
// reason in the progress line.
|
||||
showToast(t("promptTranslateError", err.message), "error");
|
||||
if (progressText) progressText.textContent = t("promptTranslateError", err.message);
|
||||
_aborted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const firstImageFilename = await _resolveFirstImage(block, i, prevGeneratedLastFrameRef);
|
||||
applyBlockToWorkflow(clone, nodes, {
|
||||
prompt: block.prompt,
|
||||
prompt,
|
||||
duration: block.duration,
|
||||
aspectRatio: _plan.base_settings.aspect_ratio,
|
||||
megapixels: _plan.base_settings.megapixels,
|
||||
|
||||
@@ -397,4 +397,27 @@ export function applyBlockToWorkflow(clone, nodes, params) {
|
||||
} else if (nodes.disconnectUnsetFrames && lastFrameSlot !== -1) {
|
||||
_disconnectInput(clone, instanceNode, lastFrameSlot);
|
||||
}
|
||||
|
||||
// Workflows saved by newer frontends also carry a name-keyed copy of a subgraph
|
||||
// instance's widget values (widgets_values_named), and convertUiToApi() prefers that
|
||||
// copy. Without mirroring the writes above into it, the template's own prompt /
|
||||
// duration / turbo values were sent instead of the Plan's (e.g. minimax_turbo.json).
|
||||
new Set([instanceNode, promptNode, durationNode]).forEach(_syncNamedWidgetValues);
|
||||
}
|
||||
|
||||
function _syncNamedWidgetValues(node) {
|
||||
const named = node?.widgets_values_named;
|
||||
const values = node?.widgets_values;
|
||||
if (!named || typeof named !== "object" || !Array.isArray(values)) return;
|
||||
const keys = Object.keys(named);
|
||||
if (keys.length === values.length) {
|
||||
// The frontend writes both from the same widget list, so the orders line up.
|
||||
keys.forEach((k, i) => { named[k] = values[i]; });
|
||||
return;
|
||||
}
|
||||
// Lengths differ: fall back to the outer widget inputs' order, the same mapping
|
||||
// convertUiToApi() uses when there is no named copy.
|
||||
(node.inputs || []).filter((inp) => inp.widget).forEach((inp, i) => {
|
||||
if (i < values.length && Object.prototype.hasOwnProperty.call(named, inp.name)) named[inp.name] = values[i];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1440,6 +1440,7 @@
|
||||
<li id="wfm-help-gen-7">Raw JSON always visible in the right column of each tab — edit directly and Apply to reload the workflow; if the loaded workflow has Bypass or Mute nodes, a note listing them appears above the search bar (Bypass nodes are excluded with their wires reconnected around them; Mute nodes are excluded with no reconnection, matching ComfyUI itself); the search bar highlights matches and scrolls to them without moving the cursor into the editor, so typing right after a search can't accidentally overwrite the JSON — click into the editor first to actually edit</li>
|
||||
<li id="wfm-help-gen-30">The <b>Save</b> button (right end of the subtab bar) is hidden while the <b>Batch</b> or <b>Lab</b> subtab is active, since those work on workflow copies / their own Plan files rather than the currently loaded workflow; it reappears on Input/Model/Settings/Feeder</li>
|
||||
<li id="wfm-help-gen-32"><b>Partner API nodes</b> (Nano Banana / Gemini image nodes, etc.): GenerateUI can run workflows that contain ComfyUI's partner API nodes. These nodes need Comfy credentials in the /prompt request — ComfyUI's own screen adds them automatically, but a separate page cannot — so add <code>COMFY_API_KEY=...</code> (a Comfy API key issued at platform.comfy.org) to the plugin's .env file and restart ComfyUI. With the key set, GenerateUI sends the request through the plugin server, which adds the key only when the workflow contains a partner API node (the key never reaches the browser); usage is billed as Comfy credits. A Gemini image node's prompt appears in the Prompt tab, and its reference images go through LoadImage nodes in the Image tab. Sample: ws_nano_banana_2_1_t2i</li>
|
||||
<li id="wfm-help-gen-33">🌐 prompt translation: when Settings → Prompt Translation is on, a 🌐 button sits next to the Positive/Negative prompts (Input tab), the Positive/Negative fields of the Lab prompt modal, and the Video Plan block prompt. It translates only the parts written in your own language into English and leaves prompt syntax as it is — commas, (word:1.2), [ ], {a|b}, <lora:...>, __wildcards__, embedding:name and BREAK; "Original" puts back the text from before translation (also Ctrl+Z) while that field stays open. Settings can also translate automatically when Generate, Lab Run or Video Plan Run is pressed</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="wfm-help-card">
|
||||
@@ -1694,6 +1695,7 @@
|
||||
<li id="wfm-help-settings-22">G'MIC-Qt Integration: path to the G'MIC-Qt executable (gmic_qt.exe) used by the Image Edit tab's G'MIC filter (Edit with G'MIC); Save stores it — see the Image Edit help for installing G'MIC-Qt</li>
|
||||
<li id="wfm-help-settings-17">Decision Model: its own section, independent of the AI TOOL / Tagger LLM/VLM backends so it can be used alongside them — backend, URL, model and a default probability threshold (answers below it are treated as suggestions to confirm). Unsloth: Laya models (laya / laya-multilingual / laya-english / laya-typed-decisions); requires Settings → API → Decision API → Serve requests in Unsloth Desktop (otherwise HTTP 404 "The Decision API is off") and either UNSLOTH_API_KEY in .env or Keyless API access → "Chat and inference". Ollama (0.35+): lists installed models whose capabilities include "decision" (tev1, tev1:0.8b, nimble — ollama pull first; ↻ refreshes the list); no key needed. Test sends one yes/no question with the unsaved form values; the first request loads the model.</li>
|
||||
<li id="wfm-help-settings-25">VRAM Management (Ollama): right before every image generation from Workflow Studio, unload the models Ollama has loaded to free VRAM (off / only as much as needed to reach a target free VRAM / everything every time); free VRAM is measured with nvidia-smi; "Unload all now" unloads them immediately; the same "Unload all now" is also in the sidepanel A tab → Settings → VRAM (Ollama). Running a workflow from the ComfyUI canvas itself is not covered</li>
|
||||
<li id="wfm-help-settings-26">Prompt Translation (native language → English): off by default. Translates prompts with a local Ollama model — PLaMo 3 Translate (0.8B / 2B / 8B / 31B; models not yet in Ollama can be fetched with the Download button) or any other Ollama model through a translation instruction. Options: source language (auto-detect or one of the 51 languages PLaMo supports), Ollama URL, where to run the model — GPU (about 0.1 s per phrase; with "Unload after translating" on, each translation first waits about 4 s for the model to reload) or CPU kept loaded (about 0.3 s per phrase, no VRAM, loaded in the background when GenerateUI opens) — and "Translate automatically when running" for GenerateUI Generate (replaces the prompt text with the translation), Lab Run and Video Plan Run (translate only what is sent; the plan keeps your text). If translation fails, nothing is generated. Test translates a sample prompt. 2B translates sentences more accurately than 0.8B. PLaMo 3 Translate is free for personal and non-commercial use (PLaMo Community License). PLaMo models also work for the AI TOOL / Prompt tab AI Assistant / sidepanel A tab translation buttons (the languages are passed in the format PLaMo expects)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="wfm-help-card">
|
||||
|
||||
@@ -4035,6 +4035,39 @@ const renderInfoTab = (container) => {
|
||||
|
||||
const AI_SETTINGS_KEY = "wfm_ai_settings";
|
||||
const AI_LANG_NAMES = { ja: "Japanese", en: "English", zh: "Chinese" };
|
||||
|
||||
// PLaMo Translate over chat — inline copy of static/js/plamo-chat.js (this file cannot import
|
||||
// from static/). PLaMo treats an instruction system prompt as context and the user text as the
|
||||
// source, so the languages go in a leading "[plamo-translate]" system message instead.
|
||||
const AI_PLAMO_LANGS = ["ar","az","be","bg","bn","ca","ceb","de","en","es","et","eu","fa","fr","gu","he","hi","hr","hu","hy","id","it","ja","jv","ka","kk","km","ko","lt","lv","ml","mr","ms","my","ne","nl","pa","pl","ru","si","sk","sl","sr","sw","ta","te","th","tl","ur","uz","vi","zh"];
|
||||
let _aiPlamoNameMap = null;
|
||||
function aiIsPlamoModel(model) { return /plamo/i.test(model || ""); }
|
||||
function aiPlamoLangCode(nameOrCode) {
|
||||
const key = (nameOrCode || "").trim().toLowerCase();
|
||||
if (!key) return "";
|
||||
if (!_aiPlamoNameMap) {
|
||||
_aiPlamoNameMap = new Map();
|
||||
for (const code of AI_PLAMO_LANGS) {
|
||||
_aiPlamoNameMap.set(code, code);
|
||||
for (const loc of ["en", "ja", "zh", navigator.language, code]) { // `code` adds the endonym
|
||||
try {
|
||||
const name = new Intl.DisplayNames([loc], { type: "language" }).of(code);
|
||||
if (name) _aiPlamoNameMap.set(name.toLowerCase(), code);
|
||||
} catch { /* Intl.DisplayNames unavailable */ }
|
||||
}
|
||||
}
|
||||
[["chinese", "zh"], ["中文", "zh"], ["中国語", "zh"], ["filipino", "tl"], ["tagalog", "tl"], ["persian", "fa"], ["farsi", "fa"]]
|
||||
.forEach(([k, v]) => _aiPlamoNameMap.set(k, v));
|
||||
}
|
||||
return _aiPlamoNameMap.get(key) || _aiPlamoNameMap.get(key.split(/[-_]/)[0]) || "";
|
||||
}
|
||||
function aiBuildPlamoMessages(text, src, dst) {
|
||||
const dstCode = aiPlamoLangCode(dst);
|
||||
if (!dstCode) throw new Error(`PLaMo Translate does not support the target language "${dst}"`);
|
||||
const srcCode = aiPlamoLangCode(src);
|
||||
const header = `[plamo-translate]\ntarget_lang=${dstCode}` + (srcCode && srcCode !== dstCode ? `\nsource_lang=${srcCode}` : "");
|
||||
return [{ role: "system", content: header }, { role: "user", content: text }];
|
||||
}
|
||||
const AI_BACKEND_DEFAULT_URLS = {
|
||||
ollama: "http://localhost:11434",
|
||||
lmstudio: "http://localhost:1234",
|
||||
@@ -4565,7 +4598,7 @@ const setupAiHandlers = (container) => {
|
||||
const systemPrompt = `You are a professional translator. Translate the text the user sends${fromPart} into ${dstName}. `
|
||||
+ `Reply with ONLY the translated text and nothing else: no explanations, no notes, no preamble like "Here is the translation:", `
|
||||
+ `no surrounding quotes, and do not repeat or echo the original text.`;
|
||||
const messages = [{ role: "system", content: systemPrompt }, { role: "user", content: text }];
|
||||
const plamo = aiIsPlamoModel(model);
|
||||
|
||||
const transBtn = container.querySelector("#wfm-nlp-ai-trans-btn");
|
||||
const statusEl = container.querySelector("#wfm-nlp-ai-status");
|
||||
@@ -4576,8 +4609,12 @@ const setupAiHandlers = (container) => {
|
||||
outputEl.value = "";
|
||||
|
||||
try {
|
||||
const result = await aiCallChat(url, backend, model, messages, c);
|
||||
const cleaned = cleanAiTranslationOutput(result);
|
||||
const messages = plamo
|
||||
? aiBuildPlamoMessages(text, srcName, dstName)
|
||||
: [{ role: "system", content: systemPrompt }, { role: "user", content: text }];
|
||||
const callCfg = plamo ? { ...c, thinkingMode: false } : c; // PLaMo has no thinking mode
|
||||
const result = await aiCallChat(url, backend, model, messages, callCfg);
|
||||
const cleaned = plamo ? result.replace(/<\|plamo:[a-z_]+\|>/g, "").trim() : cleanAiTranslationOutput(result);
|
||||
outputEl.value = cleaned;
|
||||
statusEl.textContent = "完了";
|
||||
statusEl.className = "wfm-nlp-ai-status wfm-nlp-ai-ok";
|
||||
|
||||
Reference in New Issue
Block a user