Files
ketle-manandClaude Opus 5.5 f6ce112ade release: v0.9.6 — MiniMax-H3-Image, Settings Switches column
- GenerateUI/Metadata/I tab: MiniMax H3 Image (T2I and Edit) support
- GenerateUI Settings tab: Switches column for true/false and two-choice
  on/off inputs, toggled instantly
- Fix ImageCompare failing validation (compare_view missing)
- Manual v0.9.6 (ja/en) and EPUB edition on Google Drive

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 18:28:10 +09:00

924 KiB
Raw Permalink Blame History

DEVLOG - ComfyUI-Workflow-Studio


v0.9.6(2026-11-12)

マニュアル v0.9.6版(ja/en)

  • 5-4 Settingsタブを3列(スイッチ列の説明を追加)、5-9 対応表に MiniMax H3 Image と Compare Images 修正の注記、12-3 の Apply 注意にスイッチ列の例外を追記。対象バージョンを v0.9.6 に更新し、全章MD・ZIPを manual/rebuild_repo_edition.py で再生成(v0.9.5版は削除)。

GenerateUI Settingsタブ: スイッチ列

  • KSampler・Latent Imageの右に3列目「スイッチ」を追加。APIワークフロー内のリテラルな true/false 入力(ComfySwitchNode の switch、PrimitiveBoolean の value、TextGenerateV2 の thinking など)をノード名・入力名付きで一覧し、チェックボックスで即時切り替え(Apply不要、Raw JSONへ同期)。リンクされた入力は配列なので対象外で、配線元のPrimitiveBooleanが出る。
  • 選択肢がちょうど enable/disable・enabled/disabled・on/off・true/false・yes/no の2択のCOMBO(KSamplerAdvanced add_noise、DWPreprocessor detect_* など)も対象。object_info を型別取得してから追加描画し、値はそのノード自身の綴りの文字列で書き戻す。DynamicCombo(TextGenerate の sampling_mode)は子入力が変わるため対象外。
  • 保存済みワークフロー347本(App形式5本は読込対象外)をブラウザで順に読み込み、128本・426項目でオン→オフ→元に戻すの往復と Raw JSON 同期を自動検証、失敗・コンソールエラー0。
  • MiniMax H3 Image のT2I(サブグラフ内のプロンプト強化スイッチ)とEdit(PrimitiveBoolean「Enable PE?」)でブラウザ実操作を確認。

MiniMax-H3-Image対応

  • MiniMaxH3ImageConditioning(Comfy-Org の MiniMax H3 Image T2I/Edit テンプレート)をGenerateUI/Metadata/Iタブに追加。入力は prompt/width/height/images.image_N で PH3ImageConditioning と同形のため同じ扱い(negativeなし、promptはPreviewAny→プロンプト強化スイッチ経由で配線元のユーザープロンプトをpositiveに)。width/height=0は image_1 のサイズ、画像なしなら2048。
  • 修正前はプロンプト欄が空で、Metadataタブもプロンプトを読めなかった。T2I(プロンプト強化オフ、2048x2048)とEdit(プロンプト強化オン、2720x1536)で実生成確認。

修正: ImageCompareノードを含むワークフローで検証エラー

  • ImageCompare の compare_view(表示専用の socketless ウィジェット、widgets_values に保存されない)がAPI変換で欠落し、required_input_missing になっていた。フロントエンドと同じ {"__value__": ["", ""]} を補う(_DISPLAY_WIDGET_DEFAULTS)。MiniMax H3 Image Edit テンプレートで発覚。

v0.9.5(2026-11-11)

AI TOOL Chat: スキル v2(metadata・同梱スキルの版管理・プロンプトボタン)

  • スキルの frontmatter に metadata: を追加(output prompt/svg の自動チェック+1回だけ再依頼、tools、temperature、think、context: workflow / current-prompt / workflow-detail / logs)。name/description は Agent Skills(SKILL.md)と互換のまま。
  • 同梱スキルを py/services/builtin_skills/*.md に分離し wfs-version で版管理。ai_skills/.builtin_state.json に版とハッシュを記録し、未編集なら自動更新、編集済みなら残して ↻ ボタンを表示。v0.3.79 版の未編集コピーはハッシュで判別して更新。
  • 同梱スキル6本: T2I Prompt Enhancer / Image to Prompt / Storyboard to Lab / Workflow Doctor(新規)、SVG Icon Generator v2、Skill Creator v3。
  • ワークフロー要約から「今のプロンプト」を既定で外した。「参考のみ・写すな」と書いても gemma4 e4b は 6/6 回で古いネガティブ(pink wear)を新しいプロンプトへ混ぜたため。発言が今のプロンプトに触れたときと context: [current-prompt] のときだけ渡す。

AI TOOL Chat: 生成画像のチェックと修正ループ(意思決定モデル)

  • 新規 static/js/chat-image-check.js。チャットモデルが依頼を3〜8個の yes/no 項目にし(JSON Schema)、Ollama の画像対応意思決定モデル(既定 d3:4b)が確率で判定。✔≥0.85 / ? 0.3〜0.85 / ✘<0.3。
  • 実測: 生成VLM(gemma4 uncensored e4b)は「剣を持っている」を誤って合格(7/8)。d3:4b・clef-flash・clef は引っかけ含む18/18正解。属性の取り違え(帽子が女の子なのに「男の子が帽子」0.70〜0.79)があるため合格ラインを0.85にし、expect: no の逆向き確認項目を加えた。
  • 「足りない点を直して再生成」で未達項目だけを伝えて書き直し・再生成。自動修正0〜3回。SVG も描画して部位ごとにチェック。

AI TOOL Chat: Workflow Doctor / Storyboard to Lab / フォルダ形式スキル

  • 新規 static/js/chat-context.js: 未インストールのノード、ComfyUI の一覧に無いファイル・選択肢(近い名前の候補)、切れたリンク、未接続のコア型必須入力、/history の直近エラー、ComfyUI ログの最新エラー部分を文脈に入れる。手元77ワークフローで誤検出0(ウィジェット系の必須入力チェックは1/3で誤検出したため除外)。
  • ```labplan → Lab プラン(1シーン=1イテレーション、T2I、seed 固定)。lab-tab.js に openLabPlanData()。
  • スキルの zip 取り込み(SKILL.md 必須、テキストのみ、スクリプトは展開しない、zip-slip とサイズの検査)。参照ファイルは read_skill_file ツールで必要時に読む。
  • Lab: KSampler 列が seed / sampler / scheduler だけ指定のとき「ワークフローの設定のまま」と表示していた不具合を修正。

AI TOOL: OCR・ペインの折りたたみ

  • 新規 static/js/ocr-tools.js(Comic Creator の OCR タブから汎用部分を移植)。実測(日英混在・縦書きあり): LightOnOCR-3 4b 1.1秒(位置付き7.9秒)100%、PaddleOCR-VL-manga 3.9秒100%、unlimited-ocr 14秒100%、PaddleOCR-VL 1.6 / GLM-OCR は縦書きを読み落とし94%。
  • 位置の表示(LightOnOCR / DeepSeek-OCR)、枠クリックで行選択、「翻訳へ送る」「Chatへ送る」。Translation / Settings ペインを細い帯にたためる。

修正: 非推奨の import(GitHub issue #3)

  • top_menu_extension.js の /scripts/ui/components/button.js・buttonGroup.js の import は、a6f45ef で actionBarButtons に移行して以来呼ばれていない旧メニュー用コードだった。window.comfyAPI への置き換えではなく削除。web/comfyui/*.bak も削除。

ドキュメント

  • ヘルプ(AI TOOL を12カードに再編、Lab に Storyboard)、マニュアル v0.9.5(v0.9.2〜v0.9.4 の未反映分も追記)、README の TagSpace スクリーンショット、Registry アイコン用に docs/thumb.png を 800x400 に縮小。

検証: 各機能を ComfyUI_5(:8189)で実機確認(Chrome / Playwright)。単体テスト: スキルサービス35件、JS(解析・チェック・Markdown・OCR・文脈)約50件。

未確認: サイドパネル(A タブ)はスキル本文のみ対応(metadata・ボタン・チェックは SPA のみ)。判定の閾値は画像約5枚から決めた値。OpenAI 互換バックエンドでの read_skill_file 往復。


v0.9.4(2026-11-07)

  • 新規 static/js/gallery-3d-viewer.js: three.jsのビューア。詳細パネル・ライトボックス・比較で開き、枠がDOMから外れると自分で破棄する(WebGLコンテキストを解放)。
  • 新規 static/lib/three-viewer/: three 0.187 / @pixiv/three-vrm 3.5.5 / @sparkjsdev/spark 2.4.0 のバンドル。Spark(2.8MB)は別チャンクで、スプラットを開くときだけ読む。再ビルド手順は同フォルダのREADME.txt。
  • サムネイル: サーバーに3Dレンダラーが無いため、/thumb の404を合図にブラウザが描画し /wfm/gallery/image/thumb3d へ保存する。1件ずつ処理し、128MB超は開いたときに保存する。
  • .gltf の外部ファイルは /wfm/gallery/model3d/asset で配信する(.bin/画像のみ、許可ルート内のみ)。
  • PLYは f_dc_0/scale_0 の有無でスプラットを判定する。double 属性はFloat64ArrayになりWebGLが拒否するのでFloat32へ変換する。
  • asset.extras からSaveGLBのprompt/workflowを読む(GLBはJSONチャンクのみ読む)。
  • 3DファイルはImage Edit / GenerateUI / Send to LI node / Tagger / Feederへ送れないようにした。

P-H3 Image対応

  • PH3ImageConditioning をGenerateUI/Metadata/Iタブに追加(TextEncodeFibo2 と同じ扱い、negativeなし)。_unifyPromptEnhancerText は TextGenerateV2 も対象にした。

修正: TextGenerate系の値ずれ

  • _normalizeLinkedWidgetValues が、リンク済みpromptの空枠を持つ形式を「枠なし形式」と誤判定していた。値が1つずれても、無効なDynamicComboキーがoptions[0]へフォールバックして枠数が偶然一致するため。Krea-2 / Ernie / LTX-2.5 などでも sampling_mode:2048 のようにずれていた。選択キーの有効性も判定に加えた。

検証: 3Dは実サンプルで描画・自動サムネイル・詳細・拡大・破棄・送信ガード(Image Editボタン)を確認(SPZとスプラットPLYは自作サンプル)。パストラバーサルは全て404。変換は手元の640ワークフローを修正前後で比較し、差分21件は全てTextGenerate系。うち5種類は widgets_values_named 等で正しい値と確認した。P-H3は編集(1024x1024、約60秒)とT2I(3360x2496、約40秒)をGenerateUIから実生成(RTX 4070 12GB、モデルロード済み)。

未確認: 比較表示、テーブル表示のサムネイル、128MB超のファイル、実データのスプラット、サイドパネルIタブのP-H3、Draco/Meshopt圧縮のモデル。

補足: KTX2圧縮テクスチャは未対応。.gltf を移動・削除しても外部ファイルは追従しない。GenerateUIにプロンプト強化のオン/オフ切り替えは無い。


v0.9.3(2026-11-05)

Image Edit: Drawツールのカラーパレット・ショートカット・スポイト(Comic Creator v1.67.0 から移植)

  • 新規 static/js/image-edit/DrawPalette.js: Drawツールの2段目(ie-tool-options)に色1/色2、基本色20色、カスタム色20枠を表示する。右クリックで現在の色の登録・色の編集・削除(カスタム色)・初期の色に戻す(変更済みの基本色)。保存先は localStorage ie_draw_palette_v1。
  • image-edit-tab.js: 色の変更を _setDrawColor に集約し、モードは _setDrawMode、サイズは _setDrawBrushSize / _stepDrawBrushSize に分離。
  • ショートカット(Draw中のみ): D Draw / E Erase / X 色1・色2切替 / [ ] サイズ増減(1〜10pxは1、10〜50pxは5、50〜200pxは10刻み)/ B ブラシ選択の開閉。ブラシ選択の表示中は D/E/X/[/] を無効にし、Escで閉じる。他のツールからのBはこれまでどおりDrawへの切り替え。
  • スポイト: WFSのDrawツールには無かったためCC版から追加。右パネルのColorの隣のボタンからキャンバスの色を拾う。Esc・再クリック・ツール切替・キャンバス外クリック・Closeで解除。
  • ブラシの初期色を赤から黒に変更(色1/色2 = 黒/白)。

CC版との違い

  • 色の引き継ぎは、Drawツールの表示時ではなく _initCanvases で DrawTool を作り直す時点で行う。
  • スポイトは ie-canvas-draw から色を読むため、アクティブレイヤーのみの表示なら合成し直してから読む。
  • 右クリックメニューを開いたままツールを切り替えても残らないよう、_renderToolOptions で閉じる。

検証(Playwright、ComfyUI :8189): パレットからの描画色、登録と再読み込み後の保持、重複登録の通知、右クリックメニュー、基本色の復元、Color入力との連動、各ショートカット、入力欄やCtrl/Alt同時押しで反応しないこと、サイズの刻み、ブラシ選択の開閉、ブラシ未登録時の通知、Newでの色の引き継ぎ、スポイトの取得と解除、Undo履歴が増えないこと、別レイヤーの色の取得を確認。コンソールエラーなし。

未確認: 「色を編集…」で開くブラウザ標準のカラーピッカーの表示と、日本語キーボードでの [ ] の実打鍵。

補足: 透明部分をスポイトすると黒を拾う(CCと同じ)。描いた線から拾うと値が1ずれることがある。READMEのバージョンバッジが0.8.11のままだったので0.9.3に更新した。


v0.9.2(2026-11-03)

Selectorイベントループ有効時に画像が壊れて届く不具合を修正

  • 症状: 設定「Selectorイベントループ」(windows_selector_event_loop)が有効だと、ギャラリーのプレビューと Download ボタンで取得した画像が壊れる(元ファイルと同サイズのまま、32KB 分のブロックが直後のブロックの内容に置き換わる)。出力フォルダのファイル自体は正常。
  • 原因: Selector 方式では aiohttp の FileResponse が asyncio の _sendfile_fallback を通る。これは 16KB のバッファを使い回しながら、送り切れなかった分をトランスポートにコピーせず積むため(Python 3.12)、送信が詰まると次の読み込みで上書きされる。ギャラリーは多数の画像を同時に読むので詰まりやすい。
  • 修正: prestartup_script.py で Selector に切り替えるとき AIOHTTP_NOSENDFILE=1 も設定し、aiohttp がチャンクごとに新しい bytes を読んで送る経路を使うようにした(aiohttp 読み込み済みの場合はモジュール定数も上書き)。プロセス全体に効くので ComfyUI 本体の /view なども対象。
  • 検証: 受信を遅くしたクライアントで、修正前は 5/5 回破損、修正後は 5/5 回ハッシュ一致。再起動後の実サーバー(ギャラリー配信 URL)でも 5/5 回一致。
  • 壊れた応答は Cache-Control: max-age=3600 でブラウザに残ることがあるので、更新後は再読み込み(Ctrl+F5)が必要。

v0.9.1(2026-11-03)

TagSpace: 選んだタグだけを追加 / タグのカッコをエスケープして追加

  • 選択中パネルでタグを1つ以上選んでいると、「追加」ボタンが「選択したタグを追加(N)」になり、選んだタグだけ(プロンプト中の順)を1つのチップ(名前は「rapunzel(2タグ)」)として追加する。選んでいなければ従来どおり全体(カードはプロンプト全文、Gallery点は全タグ)。3D画面のダブルクリックと一覧の+は常に全体。
  • 不具合修正: タグは分解時にエスケープを外している(rapunzel \(disney\) → rapunzel (disney))ため、タグ・Gallery点のチップをそのままプロンプトに入れると (disney) が強調指定になっていた。プロンプトに入れるときにカッコを \( \) に戻す(表示はそのまま)。カード全文の \(...\) と同じ形になるので、重複除去でも1つにまとまる。
  • 検証(Playwright): rapunzel カードで未選択の「追加」→全文、2つ選んで「選択したタグを追加(2)」→ rapunzel \(disney\), braided ponytail、一覧の+で同じタグを追加しても重複せず。
  • ヘルプ(tagspace-10 英/日/中)、README、マニュアル第11章 11-7(日英、全章まとめ・ZIP 再作成)。

TagSpace: Gallery点から「Galleryで開く」

  • 選択中パネル(Gallery点)に「Galleryで開く」ボタン。gallery-tab.js に revealInGallery(path) を新設(export): ギャラリータブ/Output サブタブへ切り替え(初回はここで出力フォルダとツリーを読み込むので state.folderTree を待つ)→ 絞り込み(検索・お気に入り・タグ・グループ・類似表示)を「Clear filters」と同じ要領で解除 → ツリーを根から目的のフォルダまで矢印クリックで展開(子は展開時に描画される遅延描画のため)→ フォルダを選んで await loadImages() → 一覧で [data-path] の要素をクリック(通常クリックと同じ選択・詳細表示)して中央へスクロール。
  • サムネイル表示は50件ずつ描くので、目的の画像まで _appendNextPage で描き足し、無限スクロールの目印を末尾へ戻す(全件描いたら外す)。パスは \ / 大文字小文字の違いを無視して比較。出力フォルダ外・一覧に無い画像は false を返し、TagSpace 側でトーストを出す。
  • 検証(Playwright): 検索語を入れた状態から TagSpace の Gallery 点で押す → 0.3秒でギャラリーに切り替わり、ツリー「2026-06-07」選択・一覧で目的の画像が選択されて画面内・検索欄クリア・詳細表示。456枚のフォルダの453枚目でも 0.6秒で表示(全件描画・目印も解除)。出力フォルダ外/存在しないパスは false。コンソールエラーなし。
  • ヘルプ(tagspace-10 英/日/中)、README、マニュアル第11章 11-7(日英、全章まとめ・ZIP 再作成)。

v0.9.0(2026-11-03)

マニュアル(画像なし配布版)を v0.9.0 に改訂

  • 日本語・英語の docs/manual/ を v0.8.9 → v0.9.0 に更新(全章まとめ・ZIP・README を作り直し、v0.8.9 のファイルは削除)。
  • 反映した変更: TagSpace タブ(第11章に 11-7 を新設、第3章のタブ一覧・付録の用語集・第12章のトラブル表)、Gallery セマンティック検索の Ollama 化(第8章 8-6・第2章・第12章。ONNX の GPU 設定の記述を Tagger 側へ移動)、Tagger の PixAI 対応と GPU 確認(第11章 11-2・第12章)、GenerateUI の Input タブ横並び・Raw JSON 折りたたみ(第5章)、Fibo 2.0 Edit/FastH3 OmniRef/Pika-H3-Flex(第5章 5-9)、Video Plan の Reference Image(第11章 11-4)、Comic Creator からの複数参照画像の実行(第11章 11-6)。
  • 前回の全章まとめは v0.8.11 の章ファイル修正(Send CC)を取り込んでいなかった。今回は章ファイルから全章まとめを作り直したので解消。
  • 英語版の Raw JSON 折りたたみボタンは、実際の表示(»)ではなく ASCII の >> と書いた。

TagSpace タブ新設(latenttag-3d の WFS 統合)

  • 目的: latenttag-3d(単独アプリは別途継続)を WFS に組み込み、WFS のデータだけでタグを視覚的に選んでプロンプトを作る。

  • 材料: カード=ImagePrompt(ws_image_prompt、.txt または ponyxlWildcardsVault の YAML から _read_sidecar_prompt でプロンプト解決、プロンプトのある951枚)/タグ=ImagePrompt・ワイルドカード(.txt 行・YAML の - 行)・Tagger DB・prompts.json の文を , \n | { } BREAK で分解し正規化(重み・括弧・Dynamic Prompts の n::・LoRA・__wildcard__・rating: を除去、_→空白、2〜40文字・5語以内)。出典の種類数→出現回数の順で上位 N(既定2000)/背景=Gallery 索引から seed 固定で間引いた画像(既定4000)。

  • ベクトル: カードは Gallery セマンティック索引の画像ベクトルを流用(未索引分は構築時に SemanticService.start_index(..., "missing") で追加)。タグは Ollama でテキスト埋め込み(task: search result | query: 付き)し tagspace_tags.npz にキャッシュ。索引が Gemini のときはエラーにする。

  • modality gap: 同じ空間でも画像とテキストは別の塊になる。実測で、そのまま PCA すると第1・第2軸が画像/テキストの分離に使われた(|Δ平均|/標準偏差 2.99・4.43)。画像とテキストを別々の平均で中心化すると 0 になり、画像→近いタグも改善(例 alley: 中心化前 anime, twintails… → 後 street, neon lights, city lights, night)。

    • 画像側の平均を「カード+背景」で取ると、数の多い背景に引っ張られてカードがタグの塊から離れた(中心間距離 5.06)。カードの平均に変えて 0.00 にした。
    • 近傍・検索も同じ中心化コサイン。同種どうしのスコアが高く出るため、カードとタグを別々に順位付けして交互に並べる。
  • 3D: three.js r187(MIT)から使う部品+OrbitControls だけを esbuild で束ねた static/lib/three/three-tagspace.min.js(559KB)を同梱し、タブを初めて開いたときに動的 import。カードは Sprite(サムネイルを6並列で順次読み込み)、タグ・背景は Points、選択/近傍/検索は重ね描き、ラベルは HTML オーバーレイ。タブが非表示の間は描画ループを止める。

  • API: /wfm/tagspace/{status,space,build,cancel,search,neighbors}(tagspace_routes.py。Gallery と同じ GalleryService/SemanticService を共有)。生成物は tagspace_space.json(表示用)・tagspace_space.npz(近傍/検索用)・tagspace_tags.npz(タグのベクトル)。

  • 検証(ComfyUI_5 / 8189): 初回構築 約2.5分(カード画像1,054枚の索引追加+タグ2,000件)、キャッシュありの再構築 7秒。英語・日本語の検索、キャンバスのホバー/クリック(タグ・カード)、チップのトグル、重複除去、GenerateUI の Positive への追記(wfs_sdxl_gallery.json)、ワークフロー未読込時のトーストを確認。コンソールエラー0。

  • 注意: Git Bash の curl で日本語の JSON を送ると文字化けして空の検索になる(API 側の問題ではない)。ヘルプ(TagSpace ページ 英/日/中)と README を追加。マニュアルは未更新。

  • 追加: Gallery 画像のプロンプト由来タグ(Tagger による補完は次の段階で検討)

    • 表示自体にタグは不要(背景点は画像ベクトルで配置)。目的は、自分の生成画像の語をタグの材料にすることと、Gallery 点をクリックしてその画像のタグを入れること。
    • 抽出: 埋め込みの API 形式グラフで、サンプラー/ガイダーの positive(無ければ Guider 系の conditioning)から上流をたどり、text / prompt / clip_l 等の文字列入力を集める。negative 系の入力はたどらない(ControlNet 等の両方を持つノード経由で negative が混ざるのを防ぐ)。UI 形式しか無い画像は対象外。
    • 実測: 2.9万枚中 64%(18,723枚)で取得、初回 約2〜3分・キャッシュ(tagspace_prompts.json、size/mtime 一致)で 0.7秒。典型的なネガティブ語が3つ以上混ざるのは5件で、いずれも利用者がプロンプト欄に画面テキストを貼っていたもの。区切りはカンマ等のみ(スペース区切りは「blue eyes」が分かれるため不採用)。
    • 出典の偏り: タグを「出典の種類数→出現数」で選ぶと、Gallery が上位2000件中1982件を占めた。出典ごとに「そのタグを含む文の数」で並べ、出典を順番に1件ずつ取る方式に変更(Gallery 682・ImagePrompt 639・ワイルドカード 476・Tagger DB 199・Prompt ライブラリ 4)。
    • 背景点に tags を持たせ、クリックでまとめて1チップ(kind: image)。プロンプトが無い画像(背景4000中 約41%)は追加不可で、詳細に「タグなし」と表示。
    • バグ修正: 背景の点(userData.items)が kind を付ける前の生データだったため、ホバー/クリックで種類と名前が undefined になっていた。同じオブジェクトに kind を付けるよう修正。
    • バグ修正: セマンティック索引に削除済み画像が残っており、背景のサムネイルが 404 になった。背景は多めに取って実在するファイルだけに絞り、プロンプト読み取りでも削除済みは除外。
    • 既知: 画像編集モデルの指示文(例 do not use the colors)も5語以内ならタグとして残る。
    • 検証(ComfyUI_5 再起動後): 構築148秒(プロンプト読み取り111秒)→キャッシュ有りで15〜16秒。背景4000点で存在しないファイル0件・サムネイル404は40件抽出で0件。Gallery 点のクリック(タグありは1チップで追加、タグなしは追加不可)と、ホバー331回でコンソールエラー0を確認。
  • 追加: Tagger 補完(プロンプトを取り出せない Gallery 画像、2.9万枚中 10,652枚)

    • ツールバー「Tagger補完: [OFF/モデル] [Gallery点のみ/プロンプトの無い全画像]」。モデルは Tagger タブの ONNX モデル(.onnx の無い wd-vit-tagger-v3 は出ない)。TaggerService.tag_image() を追加し、しきい値は既定値(WD 0.35/0.85、PixAI は推奨値 0.17/0.27)、評価ラベル(general/sensitive/questionable/explicit)は除外。
    • 背景点の選択をタグ集めより前に移し、「Gallery点のみ」はその中のプロンプト由来タグが無い画像だけを処理。結果は tagspace_tagger.json(path→size/mtime/model/tags)にキャッシュし、100件ごとと終了時に保存(中止しても残る)。補完のために読み込んだモデルは終了後に Tagger のキャッシュから外す(Tagger タブで読み込み済みなら残す)。
    • 新出典 tagger_auto(「Tagger補完」、黄緑)。背景点は tag_src: prompt|tagger を持ち、Tagger 由来はツールチップ/詳細に「(Tagger補完)」。進み具合に実行先(CUDA/CPU)を表示。
    • 環境の問題(再発): ComfyUI_5 の venv で onnxruntime 1.31.0(CPU)と onnxruntime-gpu 1.30.0 が共存し CUDA が無効。CPU では wd-v1-4-convnext 465ms/枚・wd-v1-4-vit 641ms/枚(全画像なら約1.5時間)。修復は ComfyUI 停止→fix_onnxruntime_gpu.bat(外側リポジトリ、v0.8.10 と同じ)。今回は未実施。
    • 検証(CPU): Gallery点600・Gallery点のみ・convnext で 288枚を142秒(0.49秒/枚)、全600点にタグ(プロンプト312・Tagger 288・なし0)。tagger_auto を含むタグ571件(うち補完でしか出ないもの180件: 1boy, multiple girls, scenery 等)。キャッシュで再構築17秒。UI(選択欄の有効/無効、凡例、「(Tagger補完)」表示、クリックで1チップ)とコンソールエラー0を確認。確認後は Gallery点4000・補完OFFで作り直し済み。
    • GPU 修復後(利用者が fix_onnxruntime_gpu.bat を実行、onnxruntime-gpu 1.30.0 のみ・CUDA/TensorRT 有効): Gallery点4000・Gallery点のみ・convnext で 1,918枚を約136秒(約0.07秒/枚、画像読み込み込み。CPU の約7倍)、全4000点にタグ(プロンプト2,082・Tagger 1,918)。全画像(10,652枚)なら約12〜13分の見込み。
  • 追加: Gallery 点の近傍

    • 以前は Gallery 点を選んでも select() が近傍を問い合わせずに抜け、直前の一覧が残っていた(ファイル名とは無関係)。近傍用 tagspace_space.npz にも背景のベクトルが無かった。
    • 背景のベクトル(bg_ids / bg_vecs、4000×768 で約12MB)を保存し、カード/タグ/画像の中心化行列を読み込み時にキャッシュ。Gallery 点を基準にしたときだけ「画像→タグ→カード」を交互に並べる。カード・タグ・検索では画像を混ぜない(利用者の指定)。一覧の Gallery 画像は丸いサムネイル、タグの無い画像は「+」なし。
    • 検証: ComfyUI_00510_.png → 似た画像(0.69〜0.71)・pink body suits 等のタグ・カードが交互、57ms。タグ/カード/検索の一覧は card・tag のみ。画面でも Tagger 補完の Gallery 点で確認。
    • 未解明: 再起動直後の最初の構築が146秒かかり tagspace_tagger.json が書き換えられた(2回目以降は13秒・書き換えなし、ログに警告なし)。再現したら段階ごとの時間を記録して調べる。
  • 追加: 表示と操作の改善

    • 発光: 放射グラデーションのテクスチャ(明るい芯+にじみ)を加算合成(blending: 2。同梱サブセットに AdditiveBlending が無いため数値で指定)、depthWrite: false、点サイズ×2.4。背景は夜空色 #05070d。Gallery点2万点だと重なりで中心が白く飛んだので、発光時は Gallery点の不透明度を 0.55→0.3。
    • またたき: PointsMaterial.onBeforeCompile で頂点シェーダーに twinkle(位相・速さ。添字から決まる疑似乱数で再構築しても同じ)と時刻 uniform を差し込み、vColor を 0.15〜1.25 倍(pow(0.5+0.5 sin, 2) で暗い時間を長く・ピークを鋭く)。タグ・カード・Gallery点のみで、選択・近傍の強調は瞬かない。検証: 0.9秒で変化した画素 約2.6万(オフ時0)。
    • 拡大表示: 「選択中」の画像と「近く」の一覧の画像に data-ts-zoom。マウスを乗せるとサイド列の左に 360px で表示(読み込み済みの小画像を先に出し 720px に差し替え、pointer-events: none)。
    • 追加/削除ボタンを「選択中」の名前の行(position: sticky)へ移動。本文をスクロールしても押せる。
    • クリックは選択のみ(e.detail > 1 は無視)、ダブルクリックで追加/削除。「追加」ボタンと一覧の+は従来どおり。ヒント・空欄の案内・ヘルプ(英/日/中)・README を更新し、ヘルプに表示の項目(tagspace-8)を追加。
  • 追加: ワイルドカード YAML をパーサーで読む

    • 従来は「- で始まる行」を1項目として読んでいたため、- >- 等のブロックスカラー(本文が次行以降)は >- だけになり本文を取りこぼしていた(billions_of_all_in_one.yaml、corn-flakes-doll.yaml)。二重引用符内の ’ もエスケープのまま残っていた。
    • PyYAML(ComfyUI 本体の依存)の CSafeLoader(無ければ SafeLoader)で読み、文字列の葉だけを取る(キー=カテゴリ名、yes/no・数値に変換された葉は除く)。YAML として読めないファイルは従来の行読みにフォールバック。4.2万行で 0.19 秒(純 Python の safe_load は 1.86 秒)。
    • 実測(ワイルドカード7ファイル): 異なるタグ 28,861 → 30,130(+1,269。female / wings / dress / lace 等)。他5ファイルは不変。
    • 実機(ComfyUI_5 再起動後、2000タグ・背景20000で再構築): ワイルドカード由来 748 → 762、入れ替わり22件(male / benevolent / pointed ears 等が入り、出典を順番に取る方式なので他出典の下位が抜ける)。female の出現数 164 → 208 等。
  • 追加: 全タグ・最低出現回数・ワイルドカードのオン/オフ

    • 背景: 「全部の点を表示したい」。タグの種類は全出典で約4.8万、うち約3.7万がワイルドカードにしか無い語(1回だけの語が大半)。ワイルドカードを除くと約1.1万。
    • ツールバーに「全タグ」(max_tags=0、上限 ALL_TAGS_LIMIT=20,000)、「最低出現回数」(1/2/3/5。全出典合計の出現数で候補から外す)、「ワイルドカード」(材料に含めるか)。meta に max_tags / min_count / wildcard / tags_available を記録し、一部だけ使ったときは状態表示に「(候補 N)」。既定値(ワイルドカードあり・最低1回)は従来と同じ結果(2000タグの集合・出現数・出典が完全一致を確認)。
    • 正規化の追加: 全タグにすると、MOSS-Image のレイアウト指定({"bbox":[...],"label":"text"})や文字入れ指定の JSON 断片が Gallery プロンプトから大量に入り、bbox":-8 等 234件が空間の外に孤立した塊を作った。"key": を含む断片とモデルのファイル名(.safetensors 等)を除外し、片側だけ残った " は取り除く。括弧を外した後の embedding: も除外。ワイルドカードなし全タグ 11,406 → 10,435(2回以上 7,383 → 6,843)。
    • Ollama の一時ポート枯渇: 9,056件を連続で埋め込んだところ 6,880件目で HTTP 400 ... dial tcp 127.0.0.1:NNNNN: connectex: Only one usage of each socket address で失敗。Ollama はテキストごとにランナーへ新しい接続を張るため、ループバックの TIME_WAIT が 14,228 / 16,384 に達していた。このエラーを一時的扱いにし、30秒待って再試行(最大5回、Windows の TIME_WAIT は約2分で解放)。単体テスト(2回失敗→3回目で回復、失敗が続けば6回で諦める)で確認。実構築では以後枯渇せず(新規5,203件で TIME_WAIT 最大 11,968)、本番経路での再試行は未発生。
    • 実測(ComfyUI_5、Gallery点20,000・Tagger補完あり): ワイルドカードなし全タグ 79秒(新規埋め込み 2,656件)、最低2回 22秒、ワイルドカードあり全タグ・最低2回(14,188件)127秒(新規 5,203件)。空間データ 9.4MB(2000タグ)→ 10.8MB(11,406タグ)。ブラウザで約3.2万点を表示し、毎フレームのホバー判定込みで 60fps・JSヒープ 35MB。PCA 寄与率は 10.5% → 7.9%(タグの種類が増えて軸が分散)。
  • 追加: Gallery点のタグを選んで空間検索

    • 選択中パネルで Gallery 画像のプロンプト(またはTagger補完)のタグを、文ではなくタグごとのボタンで表示。クリックで選択/解除(複数可、state.picked に画像IDと Set で保持し、別の要素を選ぶとリセット)。「選択したタグで空間検索(N)」で、選んだタグをプロンプト中の順に , でつないだ文を既存の runSearch() に渡す(検索欄にも入れる)。
    • タグが多いとパネル(最大260px)からボタンがはみ出したため、ボタン行を position: sticky; bottom: 0 で下端に固定。
    • 検証(Playwright): 33タグの画像で2つ選択→ボタン有効化→「brown hair, east asian」で検索し、east asian 0.67 / brown hair 0.67 / dark brown hair 0.62 等が並ぶ。検索後も選択を保持。15タグの画像でパネルがスクロールする状態でもボタンが見えることを確認。
  • 追加: カードのタグも選んで空間検索

    • カードはプロンプトを文で持つだけだったので、構築時に split_tags(Gallery点と同じ正規化)で tags を付ける。詳細パネルの表示処理を tagPicker() に共通化し、カードはタグのボタン+「プロンプト全文」(<details>)+カテゴリ。タグが無いカード・作り直し前の空間(tags 無し)は従来どおり全文表示。
    • 検証(ComfyUI_5 再起動・再構築後): 951枚中948枚にタグ(平均3.4個)。無いのは zero two \(darling in the franxx\) のように1語が5語を超えるもの等3枚。「resentful」カードで angry / evil smile を選んで検索 → evil smile 0.89、evil grin 0.84、arrogant smile 0.64。
  • 修正: ズーム時に広い範囲で同じ点が反応する(ホバー・クリック)

    • 原因: Raycaster.params.Points.threshold が3D空間の固定値 0.12 で、しかも結果はカメラに近い順に並ぶのに先頭を採用していた。ズームして点群に入ると、手前の1点が画面の広い範囲で「当たり幅の内側」になり続ける。
    • 修正(pick()): 候補は広めに取り(注視点の3倍の奥行きで 8px になる幅と、描かれた点の3D上の半径の大きい方)、各点を画面へ投影してカーソルとの距離(px)が最小のものを選ぶ。点はカーソルから 8px 以内、拡大で大きく描かれた点は見た目の半径(発光時は芯)まで。カードのサムネイル(Sprite)は従来どおり当たれば候補。
    • 検証(Playwright、ホイールで大きくズーム、10×10 の格子でホバー): 修正前 100か所すべてで反応し2種類(boke 55回)→ 修正後(適度なズーム)73か所で反応し72種類、点の無い場所は無反応。4か所のクリックもそれぞれ別の要素を選択。カメラがカードの中に入るほどズームするとサムネイルが画面全体を覆うので、そのカードが反応するのは正しい。

v0.8.14(2026-11-03)

Fibo 2.0 Edit / FastH3 OmniRef / Pika-H3-Flex 対応(GenerateUI・Metadata・Videoタブ)

  • GenerateUI解析(comfyui-workflow.js): TextEncodeFibo2 を TextEncodeQwenImage21 と同じ固定ロール扱いに(prompt は PreviewAny → プロンプト強化スイッチ経由でリンクされるので配線元へ positive を伝播、negative_prompt は VGL JSON のままプロンプト欄に出す、width/height/batch_size を設定タブへ。0 は自動サイズ)。動画オールインワンノードの判定を VIDEO_ALL_IN_ONE_RE(ImageToVideo|TextToVideo|ReferenceToVideo)に集約し、MiniMaxH3ReferenceToVideo の prompt が別ノード(PrimitiveStringMultiline)から配線されていれば配線元を positive にする。
  • Metadata / サイドパネルIタブ: 動画ノードの prompt がリンクなら配線元テキストを解決。Fibo2 は MageFlow/Boogu と同じく1ノードで positive/negative を分けて取り出す。
  • Videoタブ(Plan): 既存の MiniMax H3 / LTX-2.5 / Wan2.2 はサブグラフの公開入力1ノードに書く方式だが、FastH3 系はサブグラフ無しのフラット構成。新ファミリー fasth3 として、各入力の配線元ノードを書き込み先にした(prompt → PrimitiveStringMultiline、duration → PrimitiveFloat(length が未配線なら自ノードの length へ 24fps・17k+5 グリッドのフレーム数で書く)、解像度 → ResolutionSelector)。First Image / Last Image 欄を Reference Image / Reference Image 2(ref_images.ref_image_0 / ref_image_1)として流用し、Asset タブのボタン表記・トーストも切り替える。
  • 未指定の参照画像は接続を外す: FastH3 系のみ、None / Clear の参照画像はテンプレートの LoadImage が繋がっていても切断し、使われなくなった LoadImage はノードごと除く(外さないと UI 上は未指定なのにテンプレートの画像で生成される)。他ファミリーは従来通り。
  • chain_previous: Reference Image 2 は「前ブロックの最終フレーム」として扱わず、常に生成された最終フレームを抽出して次ブロックの1枚目の参照画像にする。
  • 罠: FastH3 テンプレートの PrimitiveString/Float・ResolutionSelector・LoadImage は inputs[] に widget 定義を持たない形式で保存されており、widgetIndex が -1 を返して何も書けなかった。ノード型ごとの既定の並び順にフォールバックするようにした。
  • ノード側の仕様: MiniMaxH3ReferenceToVideo は ref_images.values() を順に使うだけなので、1枚目が欠けて2枚目だけでも動く(その画像が <Picture 1> になる)。
  • 検証: Node 上で実コード+ ComfyUI_5(8189)の object_info を使い、3種のワークフローの変換・解析・抽出と、参照画像の差し替え/注入/切断を確認(既存 minimax / ltx25 テンプレートは結果不変)。Playwright で Video タブを実操作(ラベル・既定値・Clear・+Block・ドロップ・Asset 表記)。Pika-H3-Flex を Plan から実生成(2ブロック×2秒・0.2MP → 352×608・56フレーム・音声付き、約160秒)し、chain_previous でブロック1の最終フレームがブロック2の参照画像になって自然に繋がることを確認。
  • 未対応: 参照画像の3枚目以降・参照動画・参照音声はワークフロー側の設定のまま。Pika は ManualSigmas のため GenerateUI の Steps は効かない(linked 表示)。
  • 背景: latenttag-3d(Ollama の embeddinggemma-2 でタグを埋め込む)と Gallery 索引を共通の空間で使いたい。ONNX 版(semantic_local.py、q4)は onnxruntime-gpu の入れ替え問題も抱えていた。
  • 互換性の実測: 同じ入力で ONNX 版と Ollama 版(Q8_0)を比較。テキスト cos 0.985〜0.993、画像(12枚)平均 0.967(最小 0.923)。既存索引に Ollama の検索文を当てた上位10件の一致は 8〜9/10。→ npz の model 名は embeddinggemma-2 のまま、既存索引(2.9万枚)をそのまま使う。
  • 740m / 440m: 740m=テキスト+画像+音声、440m=テキスト+画像。画像・テキストとも cos 1.0(同じベクトル)なので、切り替えても再作成不要。:latest は 740m と同じ(parent_model 740m-q8_0)で、740m 未取得時の代わりに使う。270m / 570m は画像非対応のため選択肢に入れない。
  • Ollama の画像埋め込みの罠: /api/embed に images フィールドを付けてもエラーにならず無視され、テキストだけの埋め込みが返る。正しくは input: [{"image": "<base64>"}]。[{"images": ...}] は 400、壊れた画像は 500 "Failed to load image or audio file"、未取得モデルは 404。input: [] + keep_alive: 0 でアンロードできる。
  • 実装: semantic_ollama.py を新設し semantic_local.py を削除。画像は4枚ずつ送信(1枚ずつより約1.8倍速い)し、バッチが失敗したら1枚ずつ送り直して原因の画像だけを失敗扱いにする。Ollama 内部の接続切断(400 "forcibly closed" 等)と 5xx は最大5回再試行。接続不可・モデル未取得はジョブ開始前に検出し、作成中に起きたら中止。作成後は VRAM から外す。URL は url_guard で検査。設定は semantic_settings.json(url / tag)、保存用に /wfm/gallery/semantic/options を追加。
  • UI: モデル設定タブのデバイス/CPUスレッド/画像精度を廃止し、Ollama URL・モデルサイズ(未取得表示つき)に置き換え。ヘルプ(helpGallery33/35/36・helpTrouble10/11 英/日/中)、README、requirements.txt を更新。マニュアル(v0.8.9版)は未更新。
  • 実機確認(ComfyUI_5 / 8189、再起動 13:17 > 反映 13:13 をプロセス起動時刻で確認): 既存索引 29,356件をそのまま読み込み。テキスト検索 0.9秒、類似画像 約0.6秒(4件)。URL ガードで 169.254.169.254 を拒否。モデル設定の 740m⇔440m 切替が保存され状態欄に反映。ponyxl/scenes/city(未索引9枚)を「新規・変更のみ」で作成→約1.7秒・失敗0(4+4+1枚のバッチ)、作成後 /api/ps が空(アンロード済み)、追加した alley.jpeg の類似上位は同フォルダの画像(0.88〜0.93)。コンソールエラーなし。
    • 確認中に Python の urllib から /similar が1回120秒タイムアウト、/folders が1回 WinError 10054 になった。いずれも再試行や curl では毎回0.6秒前後で正常で、変更していない /folders でも起きたため今回の変更とは別の現象と判断(このPCで以前から出ている接続切断と同種の可能性)。
  • 残作業: 旧 ONNX モデル(user/default/Workflow-Studio/models/embeddinggemma-2、302MB)は削除可能。onnxruntime は Tagger で使うため依存に残す。

v0.8.13(2026-10-27)

Edit in Mask Editor One: 送信画像をノードへ永続化(Mask Editor One v0.1.12 対応)

  • 背景: Mask Editor One v0.1.12(PR #4)が、背景画像を input/mask_editor/ へアップロードして node.properties.bg_image に保存し、ワークフロー切り替え/リロード時の onConfigure で復元するようになった。一方 window.wfmOpenMaskEditorForNode(web/comfyui/node_sets_menu.js)は node._bgDataUrl / node._bgImg を直接差し替えるだけで properties を更新しないため、そのままだとリロード時に以前の画像が復元され、/mask_editor/store_image のサーバーキャッシュを上書きする(WFSが送った画像が消える)。
  • 修正: 画像送信後に node._persistBgDataUrl(imageDataUrl, "wfs_image.png") を呼んで properties.bg_image を同期(Mask Editor One 側が公開する関数。旧バージョンでは typeof チェックで無視)。
  • layer_data: 既存マスクを引き継ぐ際の layer_data に、送信画像の width / height を付与。ノードのマスクプレビュー復元はこのサイズでレイヤーを作る(無い場合は復元済みの背景画像サイズ、それも無ければスキップ)。付与順は「_bgImg ロード後」に変更。
  • How to apply: 外部から MaskEditorOne ノードの画像を差し替えるコードは、node._bgDataUrl を書き換えたら必ず node._persistBgDataUrl() で参照も同期すること。applyImageAndMaskToMaskEditorOneNode(API形式ワークフローへ書き込む経路)はグラフ上のノードを触らないので影響なし。
  • 検証: ComfyUI_5(8189)で、画像A保存→WFS相当でB送信→シリアライズ/復元で、背景がB(300×600)・マスクプレビューも300×600になること、width/height なしの layer_data でも背景サイズで復元されることを確認。
  • ヘルプ(helpImageEditMask14 英/日/中)とREADMEを更新。マニュアル(v0.8.9版)は未更新。

v0.8.12(2026-10-27)

Comic Creator向け複数画像I2Iブリッジ + GenerateUI Inputタブ: Prompt/Image統合・Raw JSON折りたたみ

  • 背景: Comic Creator v1.65.0 の Nanobanana+ タブ(ローカルモード)が、Flux.3 Image Klein / Qwen Image 2.1 等の Image Edit 系ワークフローを複数の参照画像つきで実行したい、という要望。既存の _wfmReceiveI2IRunRequest は画像1枚(slot 0固定)だった。
  • 複数画像ブリッジ: gallery-tab.js に window._wfmReceiveMultiImageI2IRunRequest(imageBlobs, params, workflowData, workflowFilename) を追加(ワークフロー読込→image-edit-tab.js の runI2IExternalMulti へ委譲)。InpaintI2IActions.js の _runI2IWithImages が imageBlobs[i] を i 番目のLoadImageへ applyImageToSlot(file, i, ...) で割り当て(ノード数超過分は無視)。戻り値は { ok, url, slotCount, usedImages }。
  • 生成パラメータ: width/height/steps/cfg/denoise/sampler_name/scheduler は既存の comfyEditor.setGenerationParams を再利用(指定時のみ書き込み。未指定=ワークフローの値のまま)。シードは generate(workflow, { seedValue }) で固定。
  • How to apply(限界): setGenerationParams はKSampler系・解析済みlatentノードにしか書けない。出力サイズを入力画像から決める構成(例: Flux.3 Image Klein の ReferenceMultiLatent)では EmptyFlux2LatentImage が解析対象外になり、width/height は反映されない(Steps/CFG/シード/プロンプトは反映)。
  • Inputタブ(GenerateUI): Prompt / Image の内部タブを廃止し、同一画面に左右(Prompt:Image = 3:2)で並べた。親カラム(.wfm-gen-params-col--input)をCSSコンテナにして、幅が720px以下になるとImageがPromptの下へ折り返す(@container)。JSのタブ切替ハンドラは削除(gallery-tab.js の [data-input-tab="image"] クリックは ?. のため無害)。
  • Raw JSONの折りたたみ(Inputのみ): 右カラムの上端に »/« ボタン。折りたたむと幅30pxの縦書き「Raw JSON」の帯だけが右端に残り、Prompt/Imageが広がる。状態は localStorage(wfm_gen_rawjson_collapsed)。Model/Settings の Raw JSON は従来どおり。
    • 落とし穴: .wfm-gen-rawjson-col(幅540px)より前に書いた --collapsed { width:30px } は詳細度が同じで負ける。.wfm-gen-rawjson-col.wfm-gen-rawjson-col--collapsed と2クラスで指定する。
  • 検証: Comic Creator内のiframe(ComfyUI_5、8189)で、横並び(757px : 514px)・折りたたみ(540→30px)・狭幅での折り返しと、Comic Creator経由の実生成(Qwen2.1 1枚/2枚入力、Flux.3 Klein)を確認。
  • ヘルプ(helpGen4 英/日/中・index.html)とREADMEを更新。マニュアル(v0.8.9版)は未更新。

v0.8.11(2026-10-26)

  • 背景: これまで「Send CC」はレイアウトタブ(選択コマ/オーバーレイ)専用で、Imageタブへ送れるのはComic Creatorの「I2Iへ送る」を押した直後(window.parent._ccI2ITargetMode === 'image')の戻り経路だけだった。送り先を自分で選べるようにしたい、という要望。
  • UI(templates/index.html): wfm-gallery-send-cc-btn を「Send CC Layout」に改名し、隣に wfm-gallery-send-cc-image-btn「Send CC Image」を追加。どちらもComic Creator内にiframe埋め込みされたときだけ表示(従来どおり)。
  • Send CC Layout(gallery-tab.js): 従来の経路(画像は insertImageFromUrl、MP4は insertVideoFromUrl)のまま。_ccI2ITargetMode による分岐は撤去(Imageタブへは新ボタンで送る)。
  • Send CC Image: Comic Creator側の新設ブリッジ window.parent.sendImageToImageTab(url, name) を呼ぶ。ブリッジは Imageタブへ switchTab してから _ccImageTab.loadFromUrl で読み込む(作業中のレイヤーがあれば新規レイヤー、無ければ新規キャンバス)。動画はImageタブ未対応のため従来どおりトーストで弾く。ブリッジが無い(古いComic Creator)/失敗時は「Comic Creator内から開いてください」のトースト。
  • 連携の前提: Comic Creator側の window.sendImageToImageTab(14-integrations.js、feature/ocr-text-insert ブランチ)が必要。
  • 検証: Comic Creator内のGalleryで両ボタンの表示と、画像選択→Send CC Image→Imageタブへの読み込み(トースト表示)を実機(ComfyUI_5)で確認。Send CC Layoutの実クリックは未検証(処理は従来のまま)。
  • ヘルプ(helpGallery37、日英中)、README、マニュアル第8章・第11章(日英)を更新。
  • 教訓: ComfyUI_5への同期は変更したファイルだけでなく、事前に実行側とHEADが同一であることを diff --strip-trailing-cr で確認してからコピーする。

v0.8.10(2026-10-23)

Tagger: PixAI Tagger v1.0 対応

  • 対象: コミュニティ変換の Mexes/pixai-tagger-v1.0-onnx-fp32-fp16-int8(FP32/FP16/INT8、sigmoid込みの確率出力、selected_tags.csvにcategory列)。モデルフォルダにpreprocess.jsonとselected_tags.csvがあり、行数が出力数(30,877)と一致すればPixAI形式とみなす(TaggerService._load_pixai_categories)。list_modelsは該当モデルにpixai: trueを付ける。
  • 前処理(_predict_pixai): 透過は白合成 → 長辺1008にBICUBICで縮小 → 黒で1008角にセンタリング → RGBのまま(x/255-0.5)/0.5 → NCHW。WD系(白パディング/BGR/0-255)とは別経路。
  • タグ抽出: general(0)/style(1)/copyright(3)/character(4)のみ採用(meta/ratingは除外)、確率の高い順。推奨しきい値は general 0.17 / style 0.15 / copyright 0.24 / character 0.27。UIのThreshold(general,style)とChar Thr.(copyright,character)は推奨値を基準にした相対指定(スライダー=0.17/0.27で推奨値そのもの)。
  • UI(tagger-tab.js): PixAIモデル選択でスライダーが0.17/0.27へ、WD系へ戻すと0.35/0.85へ切替(Single/Batch両方)。手動で変えた値は上書きしない。
  • 性能: CUDA(onnxruntime-gpu)で約0.4秒/枚(FP16・1008px)、CPUでは約21秒/枚。
  • 教訓: onnxruntimeとonnxruntime-gpuが同じvenvに共存すると、後から入ったCPU版が同じonnxruntime/フォルダを上書きしてCUDAが無言で無効になる(get_available_providers()にCUDAが出ない)。insightface/rtmlib/qwen-tts/onnxruntime-genaiがonnxruntimeを要求するため再発しやすい。直し方は両方uninstallしてonnxruntime-gpuのみ入れ直し。
  • ヘルプ(helpTagger1/2/4、日英中)を更新。

v0.8.9(2026-10-20)

Video: アセット登録ボタン / Editの音声変換 / ソースパネルのレイアウト

  • アセット登録: 動画ソースパネルに「アセット登録」ボタンを追加(video-tab.js)。ローカルファイルはcomfyUI.uploadImageでinputへ上げ、バックエンドPOST /api/wfm/video/asset/register(VideoService.register_asset)でoutput/videoへコピーしてから__Video Assets__グループへ追加する。Galleryはoutputフォルダしか一覧しないためコピーが必要。すでにoutput配下のソース(Asset選択・生成結果)はコピーせずタグ付けのみ。同名はアンダースコア+連番で回避。登録後はAssetタブを更新。
  • Editの音声変換: クロップ行のボタンをクロップ関連(編集/キャンセル/リセット)左寄せ→|区切り→「音声変換...」の並びに変更。Galleryの変換モーダルをopenAudioConvertDialog()として共通化・export(convertOneコールバック方式。allowDelete/allowCopyMetaで項目の出し分け)。Gallery側の挙動は不変。EditのクリップはinputフォルダにあるためPOST /api/wfm/video/edit/convert-audio(VideoService.convert_audio_to_output、PyAVをasyncio.to_threadで実行)がoutput/videoへ保存する。音声付き動画クリップのみ対象、読み込み中のクリップはトーストで案内。
  • ソースパネルのレイアウト: パネル幅を220→320pxに拡大(左右プレビューはflex:1で自動的に縮む)、ファイル名を専用の1行(省略表示)にし、ボタン3つを折り返さず横一列に。
  • ヘルプ(helpVideo5/helpVideo11、日英中)を更新。
  • 教訓: i18n.jsの英語文字列内に"を入れると全体が構文エラーになる(node --checkは.mjsコピーで確認)。

v0.8.8(2026-10-19)

ユーザー提供の5本(公式テンプレートのBase/SFT × T2I/バウンディングボックス4本と、公式ではない自作のmanga 8step LoRA構成1本)で、convertUiToApi→analyzeWorkflowを実測して直した。

  • サブグラフの外側ウィジェット値のズレ: 新フロントエンドは外側ノードにwidgets_values_named(名前キーの値)も保存し、inputs[]にはseed/steps/cfg/sampler/schedulerが載らない一方widgets_valuesは全ポートを持つ。従来の「inputs[]内の位置」参照だとunet_nameに25、vae_nameにer_sdeが入っていた。_flattenSubgraphs()でwidgets_values_namedを優先(無い場合は従来どおり)。Iタブ用の複製(node_sets_menu.jsの_wfmFlattenSubgraphs)にも同じ修正。
  • CreateBoundingBoxesの変換: bboxesの型BOUNDING_BOX,ARRAY,STRINGが共用体の途中にSTRINGを含むため入力欄と誤判定され、本来の入力欄editor_state/last_incoming(型BOUNDING_BOXES)が読まれていなかった。共用体型は先頭セグメントだけで判定し、BOUNDING_BOXESを入力欄型に追加。配列値はAPIでリンク扱いされるため{"__value__": [...]}で送る(バックエンドが展開)。Flux.3 Klein 4種にも同ノードがあり、変換前は検証エラーになる出力だったものが直った。
  • プロンプト検出: MOSSImageLayoutPromptのpromptをpositiveとして検出(textKey: prompt)。リンクされたPrimitiveStringMultiline/PrimitiveStringはロールを上流へ伝播し、リンクのままの値は編集対象にしない(従来は配列がtextに入っていた)。
  • Gallery Promptタブ: APIのpromptチャンクが無くサブグラフ入りのUI形式のみの画像は、変換してから抽出(Metadataタブと同じ扱い)。通常の生成画像は従来どおりAPI形式を使う。
  • 実機(8189)で公式bboxes版と自作manga版の生成成功を確認。ワークフロー357件で変換結果を新旧比較し、差分はFlux.3 Klein 4件のCreateBoundingBoxesのみ。

変換チェック(GenerateUI Check)の初回待ち時間を短縮

初回に/object_info全件(この環境で約26MB・約43秒、毎回)を取得していたのが原因。ワークフロー詳細を開いた時点で全件の先読みも走らせており、サーバーを長時間塞いでいた。

  • _loadObjectInfoFor(types): 使用ノード型(サブグラフ内も含む)だけを/object_info/<型>で取得(1型約20ms、16並列、型ごとにキャッシュ)。転送失敗時や、既知の型が1つも無いときは全件にフォールバック。全件キャッシュがあればそれを優先。invalidateObjectInfoCache()は型別キャッシュも消す。
  • 変換(_convertUiToApiImpl)と互換性チェックで使用。GenerateUIの初回変換も速くなる。先読みは削除。
  • MOSSで初回変換 約43秒→約250ms、2回目以降は約2ms。362ワークフローで全件版との変換結果が完全一致。

v0.8.7(2026-10-16)

Qwen Image 2.1 Turbo(ComfyUI公式テンプレート T2I / I2I)対応

テンプレート(SamplerCustomAdvanced+ManualSigmas+CFGGuider、プロンプトエンハンサー=TextGenerate+ComfySwitchNode構成)をGenerateUIで使えるようにした。

  • Positiveプロンプトが空になる問題: プロンプトがPreviewAny → ComfySwitchNode(on_false=文字列リテラル)経由でTextEncodeQwenImage21.promptに入るため検出できなかった。comfyui-workflow.jsのPass 1でTextEncodeQwenImage21のリンクされたprompt/negative_promptへロールを伝播し、Pass 2で文字列リテラルを持つComfySwitchNodeをtextKey: "on_false"のプロンプトノードとして登録。
  • サブグラフ展開のファンアウト: _flattenSubgraphs()は外部リンクをポートの最初の接続先にしか張らなかったため、images.image_1がTextEncodeとプロンプトエンハンサー用BatchImagesNodeの2か所へ分岐するI2Iで、後者が画像未接続になっていた。2番目以降の接続先にも同じ外部リンクを複製するよう修正。
  • 実機(8189)でT2I/I2Iとも生成成功を確認。
  • プロンプトエンハンサー対応: TextGenerate.promptとスイッチon_falseが展開後に別々のリテラルになる問題を、_unifyPromptEnhancerText()で共有のPrimitiveStringMultiline(<switchId>:wfs_prompt)へ集約して解消。GenerateUIの入力欄の編集がエンハンサー入力にも反映される。I2Iでエンハンサーをオンにして実機確認(入力プロンプトと参照画像の両方がエンハンサーに届く)。

Flux.3 Image Klein(ComfyUI公式テンプレート 4種)対応

Klein(cfg 3.5/25step)とKlein SD(蒸留、cfg 1/16step)それぞれのT2I(プロンプトエンハンサー付きサブグラフ)とImage Edit(ReferenceMultiLatent)。CLIPLoaderのtype=flux3は既存の変換でそのまま通る。

  • ReferenceMultiLatentのロール伝播: positive/negative入力が別々の出力に出るノードなので、InstructPixToPixConditioningと同様にロールを各入力へ個別に伝播。従来はEdit版のPositive/Negativeが両方とも"Positive Prompt"というタイトルだったため、Negative側もpositiveと誤判定されていた。
  • 未使用の空Latent: ReferenceMultiLatentは参照画像サイズのempty_latentを自前で出力するため、テンプレートに残っている未接続のEmptyFlux2LatentImageはSettingsのLatent Imageに出さない(ReferenceMultiLatentを含むワークフローに限定)。
  • T2I側はQwen Image 2.1 Turboと同様、エンハンサーのTextGenerate.promptが入力用PrimitiveStringMultilineに直結しているため、そのまま編集が両方に反映される。SD版T2I(SD_4)はテンプレート既定でエンハンサーがオン。
  • COMBO値の誤置換: テンプレートはunet_nameをフォルダ接頭辞なし(flux3_image_klein_int8_convrot.safetensors)で保存しているが、実機の選択肢はdiffusion_models\flux3_...。従来の「選択肢に無い値はchoices[0]へ無警告で差し替え」により、無関係なモデルに差し替わって実行されていた。選択肢に無い場合はまずファイル名(区切り文字\//の後ろ)が一致する選択肢が1つだけあればそれを採用してから、従来のフォールバックに回すよう変更(_convertUiToApiImplのCOMBO検証)。
  • 実機で4種とも生成成功(エンハンサーはオフ)。Edit版は参照画像の編集が効くことを画像で確認。

v0.8.6(2026-10-14)

GenerateUI: パートナーAPIノード(Nano Banana 等)の実行に対応

v0.8.5 で見送った課題。パートナーAPIノードは /prompt の extra_data に Comfy の認証情報(auth_token_comfy_org = ブラウザのログイン、または api_key_comfy_org = Comfy APIキー)が必要。ComfyUI 本体の画面は自動で付けるが、WFS の queuePrompt(fetch('/prompt') 直叩き)は付けないため、GenerateUI からは Unauthorized: Please login first to use this node. で失敗していた。

  • py/routes/partner_routes.py(新規): GET /api/wfm/partner/status(キー設定の有無のみ)、POST /api/wfm/partner/prompt(/prompt の中継)。.env の COMFY_API_KEY(prestartup_script.py が python-dotenv で読み込み)が設定されているとき、プロンプトに API ノード(/object_info の api_node: true、クラスごとにキャッシュ)が含まれる場合だけ extra_data.api_key_comfy_org を付与して自 ComfyUI の /prompt へ中継し、応答はそのまま返す。クライアントが既に認証情報を付けている場合は上書きしない。キーはブラウザへ出さない。.env.example に追記。
  • comfyui-client.js: queuePrompt は、キー設定あり(isPartnerKeyConfigured()、60秒キャッシュ)かつ接続先が同一オリジンのときだけ中継を使う(別サーバーへキーを送らない)。execution_error が Unauthorized / login first のときは、設定方法を案内する文言(partnerAuthHint、3言語)を付ける。
  • comfyui-workflow.js: Gemini(Image|NanoBanana)* ノードの prompt 入力を prompt_nodes(positive、textKey: "prompt")に追加し、GenerateUI の Prompt タブで編集できるようにした。参考画像は従来どおり Image タブの LoadImage 経由。
  • ヘルプ(helpGen32、index.html / i18n.js 3言語 / app.js の helpIdMap)、README、サンプルワークフロー workflows/ws_nano_banana_2_1_t2i.json を追加。

UI形式→API形式の変換: DynamicCombo ノードの widgets_values ずれを修正

Nano Banana 2.1 の UI 形式ワークフローを変換すると値がずれていた(seed が "IMAGE"、システムプロンプトが response_modalities に入る等)。原因は2つで、どちらも修正した。

  1. _getDynamicComboSubNames が、選択肢の子入力のうちソケット(images = Autogrow、files = GEMINI_INPUT_FILES)まで widgets_values の1枠として数えていた。ウィジェット型の子入力だけを数えるようにした(_resolveWidgetType を再利用)。
  2. seed 等の control_after_generate 付きウィジェットの直後に入るフロントエンド専用の "randomize" 等の1枠を、次のウィジェットが数値型のときしか読み飛ばせなかった(Nano Banana 2.1 は seed の次が COMBO の response_modalities)。object_info の control_after_generate フラグで判定する _hasControlAfterGenerate を追加。
  • 検証: ComfyUI 本体のフロントエンドで作ったワークフローの graphToPrompt() 出力と、変換結果が完全に一致。実機の保存済み UI 形式ワークフロー301件を修正前後で比較し、300件は変換結果が同一、変化は今回の Nano Banana サンプルのみ。

Comic Creator v1.55.0 で、半自動マンガがネイティブノード経由で生成した画像が cc_nanobanana に保存され、PNG に prompt / workflow が埋め込まれるようになるため。gallery-tab.js: 埋め込みワークフローがある画像は isNanobananaFolderImage による Prompt タブの非表示を解除する(埋め込みの無い Gemini API 直叩きの画像だけ従来どおり非表示)。

実機確認(ComfyUI_5 / ポート8189、v0.40.0)

  • .env に COMFY_API_KEY を設定し、GenerateUI でサンプル(Nano Banana 2.1)を読み込み、Prompt タブで編集して Apply → Generate を実クリック: リクエストは /api/wfm/partner/prompt 経由、16.4秒で成功、編集どおりの絵(青いティーポット)が output/nano_banana_00001_.png に出力された。
  • キー未設定時の案内文(partnerAuthHint)が表示用文言として解決されること、ヘルプ(helpGen32)が表示されることを確認。

今後の作業候補(2026-10-14 時点・未着手。v0.8.4〜v0.8.6 の続き)

ネイティブ LoRA スタックノード(LoadLoraModel / LoadLoraTextEncoder、v0.8.4)

  • UI形式への対応: ComfyUI フロントエンド 1.55.17 は DynamicGroup を実装していない(行を追加できず widgets_values も空)。フロントエンドが対応したら、実物の UI 形式 JSON で widgets_values の並びを確認し、analyzeWorkflow / Metadata / I タブの UI 形式分岐を追加する。
  • 画面操作の確認: GenerateUI の Stack タブ(Stack グループを複数行として書き込む処理)は、関数の直接呼び出しでのみ確認済み。Models タブで Stack グループを登録した状態での画面確認が未実施。

Gemini / Nano Banana 系ノード(v0.8.5 / v0.8.6)

  • Metadata / I タブの表示拡充: プロンプトは Positive / Negative に分けず全文を1件で表示している(送信全文に NEGATIVE_PROMPT: が含まれる)。NEGATIVE_PROMPT: で分割して Positive / Negative に振り分ける、aspect_ratio / resolution / thinking_level を設定欄に出す、UI 形式の widgets_values(並びは [prompt, model, aspect_ratio, resolution, thinking_level, seed, control_after_generate, response_modalities, system_prompt, temperature, top_p])に対応する、が候補。
  • 他のパートナーAPIノードの Prompt タブ対応: 現状は Gemini(Image|NanoBanana)* のみを prompt_nodes に登録している。/object_info の api_node: true かつ prompt 入力を持つノード(Seedream / Kling 等)へ一般化できないか検討する。
  • 費用ガード: Batch / Lab / カタログ作成などの繰り返し実行でパートナーAPIノードを使うと、回数ぶんのクレジットが連続して消費される。/object_info の price_badge から概算を出して、実行前に表示・確認する案。
  • Settings タブに COMFY_API_KEY の設定状態を表示: 現状は .env 編集のみ。GET /api/wfm/partner/status(設定の有無のみ返す)を使って、設定済み/未設定と設定手順を Settings タブに出す。キー自体は表示・入力させない方針を維持する。
  • サンプルの追加: 参考画像付き(I2I、LoadImage → model.images.image_1)の Nano Banana ワークフロー、複数参考画像のもの。Nano Banana Pro(GeminiImage2Node)は Comic Creator 側のネイティブ対応と合わせて。

運用・開発環境

  • 同期スクリプトの整備: 開発リポジトリから実機の custom_nodes への反映は、今回も手作業(追跡ファイルの cmp 比較 + cp、workflows/ / data/ / .env は除外)だった。CLAUDE.md の「同期スクリプトでパッケージ全体をデプロイ」に沿って、tools/ に同期スクリプト(除外リスト付き、ハッシュ比較・__pycache__ 削除まで)を用意する。部分同期が残ると古い版が混在する(今回、実機は v0.8.2 開発途中の版のまま止まっていた)。
  • comfy-cli / comfy-mcp の既定ワークスペースが ComfyUI_6(プラグイン 0.3.78)で、実際に使っている ComfyUI_5(8189)と一致しない。server_info の running / freshness は ComfyUI_6 の情報になるため、MCP で実機を扱うときは注意するか、既定ワークスペースを揃える。
  • 英語・中国語UIでの表示確認: 今回追加したヘルプ(helpGen32 ほか)と partnerAuthHint は日本語UIでのみ画面確認した。

v0.8.5(2026-10-14)

Metadata / I タブ: Gemini 系ノード(Nano Banana)の画像を読めるように

Comic Creator v1.54.0 で、Nano Banana を ComfyUI ネイティブノード(GeminiNanoBanana2V2)経由で生成できるようにした(併用)。この経路の画像は SaveImage により prompt / workflow が PNG に埋め込まれるため、Workflow Studio 側で通常の ComfyUI 画像として読めるようにした。

  • metadata-tab.js / web/comfyui/node_sets_menu.js: 自ノードの prompt 入力を持つ Gemini(Image|NanoBanana)* ノードを、動画ノード(*ImageToVideo / *TextToVideo)と同じ扱いでプロンプト候補(texts)へ追加。NEGATIVE_PROMPT: を含む送信全文がそのまま1件として出る(Positive / Negative の分割はしない)。
  • extractDiffusionModels(API形式): Gemini 系ノードの model 入力(例: Gemini Nano Banana 2.1)をモデル名として表示。
  • extractSamplerSettings: Gemini 系ノードの seed を補助ノードとして拾い、設定欄に表示。
  • 対象は API 形式のみ(DynamicCombo のため UI 形式の widgets_values の並びは未確認)。GenerateUI のプロンプト編集・実行は対象外。
  • cc_nanobanana フォルダ用の特別扱い(history.jsonl 参照・Promptタブ非表示)は、従来の Gemini API 直叩きの画像のため変更なし。ネイティブ経路の出力先は別フォルダ(cc_nanobanana_native)なので、この特別扱いに該当せず、埋め込みメタデータが使われる。

実機確認(ComfyUI_5 / ポート8189、v0.40.0)

  • Comic Creator のネイティブ経路で実際に生成した画像(cc_nanobanana_native/NB_00001_.png、848×1264)を extractAllMetadata に通し、モデル名(Gemini Nano Banana 2.1)・シード・プロンプト(NEGATIVE_PROMPT: 付き全文)が取れることを確認。Gallery の一覧・画像メタ取得でも通常の画像として扱われる(nanobanana エントリは null)。

ネイティブ API ノードの認証について(今回の調査で判明)

  • API ノードは /prompt の extra_data に auth_token_comfy_org(ブラウザのログイン)または api_key_comfy_org(Comfy APIキー)が必要。ComfyUI 本体の画面は自動で付けるが、Workflow Studio の queuePrompt(fetch('/prompt') 直叩き)は付けないため、GenerateUI からパートナーAPIノードを実行すると認証エラーになる。今回は対応せず(Comic Creator はサーバー側で COMFY_API_KEY を渡す方式で対応)。必要になれば Workflow Studio 側にも同様のサーバー側プロキシが必要。

v0.8.4(2026-10-14)

ComfyUI v0.40.0 対応: ネイティブ LoRA スタックノード(LoadLoraModel / LoadLoraTextEncoder)

ComfyUI v0.40.0 の変更点を確認し、影響のあった LoRA 検出・編集を対応させた(コミット 5078dd7)。

背景・調査結果

  • 新ノードは comfy_extras/nodes_lora_stack.py。1ノードに DynamicGroup loras(1〜20行)を持ち、API形式では loras.<行>.lora_name|strength|enabled のドット結合キーで渡す。Model用とText Encoder用で別ノード。
  • Workflow Studio の LoRA 検出は固定リスト(LoraLoader / LoraLoaderModelOnly / Power Lora / LoraManager / ImageMetadataLoRALoader)だったため新ノードは未検出。また既存の編集コードが inputs.lora_name を書き込むため、そのまま使うと DynamicGroup の検証エラーになる状態だった。
  • ComfyUI フロントエンド 1.55.17 には DynamicGroup の実装が無い(型名ラベルのみ)。GUI のグラフエディタではこのノードに行を追加できず、UI形式の widgets_values も空になる。そのため対応は API形式のみ。UI形式の対応はフロントエンドの対応後に実物のJSONで再検証が必要。

実装

  • comfyui-workflow.js: analyzeWorkflow が2ノードを is_lora_stack として lora_nodes に追加(1ノード1エントリ、代表は有効な最初の行。無効行は飛ばし、行番号順に解釈)。
  • comfyui-editor.js: 共通の書き込み処理 applyLoraStack(comfyEditor から公開)を追加。ワークフロー内の全スタックノードを同期して書き込む(Modelノード=Str M、Text Encoderノード=Str C)。Single / Stack Apply(Stackは全モデルを行として書く、最大20行)、バイパスボタン(全行のstrengthを0、解除で行ごと復元)、LoRAペインの現在値表示に対応。
  • lab-tab.js / models-tab.js / generate-tab.js: Lab、Models タブの Apply、LoRA バッチも同じ処理経由に。Models Apply は既存の強度を保持する。
  • metadata-tab.js / web/comfyui/node_sets_menu.js: Metadata タブ・サイドパネル I タブの LoRA 抽出(API形式)に対応。Model/Text Encoder 両ノードの同名LoRAは1件にまとめ、両方の強度を表示する。
  • ヘルプ(helpGen20 / helpGen21 / helpGen29 / helpSidepanel15、en/ja/zh)と index.html を更新。README に項目を追加。

実機確認(ComfyUI_5 / ポート8189、v0.40.0)

  • テスト用 API 形式ワークフロー(Checkpoint → Load LoRA 2ノード×2行 → SDXL)を /prompt へ投入: HTTP 200・node_errors 空・1枚生成成功(18秒)。
  • 生成時ログの lora key not loaded: ...(約5千行)は、Model/Text Encoder で別ノードに分かれる設計上、各ノードが対象外キーを報告する無害なノイズ(従来 LoraLoader 単独では0件、件数も2LoRA×キー数と一致)。
  • GenerateUI 画面で、ID:2/ID:3 の選択、バイパスボタンの往復(0→元の値)、Single Apply(RAW JSON・LORA SYNTAX・トリガーワード更新)を実クリックで確認。
  • 書き込み処理は実ファイルから切り出して Node で単体試験(強度維持・行入替・両ノード同期・バイパス復元・ワークフロー引数指定)。

影響なしと確認した項目

  • WebSocket のメタデータ付与: メタデータは workflow_metadata の送信または機能フラグ supports_preview_metadata の宣言時のみ付く。本プラグインのクライアントはどちらも行わず、メッセージも既知フィールドだけを try/catch 付きで読むため影響なし。
  • 3D 出力・カメラ系ノード: 結果取得は images と saved_svg のみを見るため無視されるだけ。
  • Comic Creator(v1.53.2): LoRA / WebSocket / /prompt を使わず、/upload/image の応答形式も互換。/ccc と /wfm の iframe 連携も正常。

その他(運用上の発見)

  • ランタイム(ComfyUI_5 の custom_nodes)が v0.8.2 開発途中の版のまま古いファイルが残っていた(部分同期)。リポジトリの追跡ファイルを同期し直した(workflows/・data/・.env は上書きしない)。
  • comfy-cli の既定ワークスペースは ComfyUI_6(プラグイン 0.3.78)。comfy-mcp の server_info の running / freshness は 8189 の ComfyUI_5 とは別物。

v0.8.3(2026-10-13)

GenerateUI: シードモード「Workflow」を追加(改善候補メモ 2-A)

生成パネルのSeed欄が常に優先され、ギャラリーから読み込んだ画像を元のシードで再現しにくかった問題への対応。

  • templates/index.html: #wfm-gen-seed-mode に workflow を追加(Random / Fixed / Increment / Decrement / Workflow)。
  • comfyui-client.js: getWorkflowSeed()(API形式ワークフローの最初のリテラルな seed / noise_seed を返す。リンク入力・負値は無視)を追加し、generate() が seedMode === "workflow" のときシードを上書きせずワークフロー値を使うようにした。数値のシード入力が無いワークフローではパネル値(-1ならランダム)にフォールバック。生成後はSeed欄に実際に使った値が入る。
  • 副次的に見つけた不具合の修正: 呼び出し側が渡す { ...workflow } は浅いコピーでノードオブジェクトを currentWorkflow と共有していたため、Random生成のシード上書きが読み込み済みワークフローに漏れ、その後のWorkflowモードが元のシードではなく直前のランダム値を使っていた。generate() 冒頭で structuredClone(workflow) するようにした。
  • 実機確認(ComfyUI_5 / chat_t2i_sdxl): Random→Workflowの順でも元のシード(1097716508243681)で生成され、本体のシードは変化しないことを確認。同じシード・同じ設定の連続生成はComfyUIのキャッシュで新規画像が増えない(仕様)。
  • ヘルプ(helpGen8、en/ja/zh)とindex.htmlのヘルプ行にWorkflowモードを追記。
  • 追加: workflows/ws_sdxl_gallery_feeder_i2i.json(+ .png)。既存の ws_sdxl_image_feeder_i2i(SDXL I2I)の ImageFeeder を WFS_GalleryFeeder(group_name=__Feeder__ / sort_mode=filename_asc)に差し替えたもの。リンク・ノードIDは元サンプルと同じ構成で整合。サムネイルPNGは既存サンプルのものを流用(要差し替え)。

ドキュメント: 初心者向けマニュアル(日本語・英語)

  • docs/manual/(日本語)と docs/manual/en/(英語)に、v0.8.3対応・画像なしのテキスト版マニュアルを追加(章ごとのMarkdown、全章まとめMarkdown、配布用ZIP)。Kindle版(画像入りEPUB)は作者のリポジトリ外原稿から別途作成。
  • 内容はv0.7.13版から更新: Workflowシードモード、Gallery Feederサンプル、セマンティック検索(8-6)、音声ファイルの扱いと変換(8-7)、メタデータタブのレイアウト変更、WS Library NタブのAlt+クリックなど。Kindleでの文字化け対策として、U+2015等の記号を代替表記に置換。
  • 配布版の再生成スクリプト(make_repo_version.py / make_repo_version_en.py)はリポジトリ外のマニュアル作業フォルダにある。

v0.8.2(2026-10-12)

画像を文章の意味で探す検索と、選択画像に類似した画像の一覧表示を追加した。

UI

  • 検索ボックス横に「S」ボタン。ONで検索ボックスが意味検索になる(Enterで実行、スコア順、サムネイルにスコアバッジ)。OFFは従来のファイル名/タグ/プロンプト検索。
  • Sを入れるとツールバーに2行目が現れ、Similar(トグル)・↻(更新)・⚙(設定)・検索結果件数を左詰めで表示する。Similarは、類似一覧の中で別の画像を選んでも一覧はそのままで、↻でその画像の類似を再表示、Similar再押下で通常表示に戻る。フォルダ切替・フィルタクリア・S OFFでも解除。Similar/↻は当初詳細パネルに置いていたが2行目へ移設(Taggerの紫と区別するためティール)。
  • ⚙のモーダルを3タブ化: インデックス(対象フォルダ・サブフォルダ・新規のみ/全件再作成・進捗・停止・全削除)/ モデル設定(モデルのラジオボタン・次元・ローカルのデバイス/CPUスレッド/画像精度・接続テスト)/ 検索設定(類似画像と文章検索の最小スコア、最大表示件数)。「?」でヘルプの該当項目へジャンプ。
  • 最小スコア(例: 0.6以上)はモデルごとに保存(スコアの水準がモデルで違うため。少数画像の実測では、類似画像はEmbeddingGemma 2で約0.6〜0.8、Geminiで約0.6、文章検索はそれぞれ約0.6〜0.7・約0.3)。
  • フォルダツリーの枚数バッジの右に緑のドット = そのフォルダ直下の画像が全件インデックス済み(GET /wfm/gallery/semantic/folders)。インデックスは全フォルダ共通の1つで、フォルダ単位で少しずつ作れる。
  • ヘルプページの幅を760→1076px(上部タブバーのAI TOOLタブの右端に揃える)。

バックエンド

  • py/services/semantic_service.py: ベクトルを semantic_index.npz に保存(パス→正規化ベクトル、サイズ/mtimeで鮮度判定、モデル/次元/画像精度をヘッダに保持)。インデックス作成はバックグラウンドスレッド(進捗ポーリング・停止可)、検索はコサイン類似度(mat @ q)、未インデックスの基準画像はその場で1枚だけ埋め込む。動画/PSDはサムネイルから、音声は対象外。
  • Gemini: gemini-embedding-2(x-goog-api-keyヘッダ、接続先固定、429/5xxは指数バックオフで再試行)。APIキーは .env の GEMINI_API_KEY(UNSLOTHと同じく settings.json・フロントには出さない)。
  • EmbeddingGemma 2: py/services/semantic_local.py。onnx-community/embeddinggemma-2-ONNX を onnxruntime + tokenizers + huggingface_hub だけで実行(transformers の対応を待たない)。Transformers.jsの Gemma4ImageProcessor / EmbeddingGemma2Processor を移植(アスペクト比維持リサイズ → 16pxパッチ化 → vision_encoder → <|image|> 位置へ image_features を差し込み → sentence_embedding)。クエリは task: search result | query: プレフィックス。MRL(128/256/512/768)で切り詰め+再正規化。
  • 実行デバイス: 自動(CUDAがあればGPU)/ CPU / GPU(CUDA)。CPUスレッド数の既定はコアの半分。設定は semantic_settings.json に保存。画像精度はソフトトークン数 280/140/70(高精度/標準/軽量、CPUで約2.5倍/約5倍速)でインデックスに記録し、変えたら全件再作成が必要。インデックス作成後はセッションを解放しVRAM/RAMを画像生成に譲る。

実測(RTX 4070 / 32コア)

  • 768×576の画像1枚あたり: CUDA 0.13秒(標準 0.07秒)、CPU 4スレッド 5.8秒(標準 2.3秒、軽量 1.1秒)。CPUとCUDAのベクトルは一致(cos 1.0)。実機で122枚を約20秒で作成。
  • DirectMLはvision_encoderのReshapeノードで実行時エラーとなり非対応(テキストモデルのみ動作)。WindowsMLは未検証。
  • 環境メモ: ComfyUI_5のvenvには onnxruntime と onnxruntime-gpu(ともに1.30.0)が同居し、CPU版が勝っていたため、両方を削除して onnxruntime-gpu==1.30.0 のみを入れ直した(pip check は onnxruntime 名での依存を警告するが動作に影響なし)。

安全性(セキュリティレビュー指摘への対応)

  • 数値パラメータ(dim / soft / threads / limit / min_score)を共通関数で範囲内に丸め、数値でない値は500ではなく400(bad_param)で返す。スレッド数はコア数の2倍まで。
  • ローカルモデルへの入力を制限: テキスト1024トークン、検索文2000文字、画像は長辺2048pxを超えたら先に縮小。

その他

  • .env.example に GEMINI_API_KEY、requirements.txt にGPU用の手順コメントを追加。ヘルプ(3言語)・READMEを更新。
  • Gemini Embedding 2には無料枠があるが、無料枠で送った内容はGoogleの製品改善に使われる場合がある(2026年10月時点の公式料金: 画像1枚あたり約$0.00012)。

v0.8.1(2026-10-11)

v0.8.0 のコードレビュー指摘(#1〜#10)への対応。

バグ修正

  • Nodes: 検索結果が0件のとき renderToken を進めておらず、描画途中のカードが「No nodes found」の下に追加され続けていた。
  • Metadata: convertUiToApi() に { readOnly: true } を追加。表示用の変換ではCOMBO値の無警告差し替え(サンプラー名の誤表示)を行わず、Workflowタブの互換性チェックが参照する _last* 記録も更新しない。
  • Metadata: 画像を連続で開いたとき、先に始めた処理の結果が後から描画されて混ざる問題を、リクエストID(_loadSeq)で防止。
  • Metadata: サンプラー設定の抽出を改善。Flux/SD3系(RandomNoise / BasicScheduler / KSamplerSelect / CFGGuider)の設定を統合し、複数KSamplerでは設定が最も多いノード(同数なら後ろ)を採用。
  • Gallery: history.jsonl に dict でない行・filenames が list でない行があっても、フォルダ内の全画像でメタ取得が失敗しないようにした。
  • Gallery: 同じファイルを同時に変換すると出力名が衝突して上書きされる問題を、出力名の決定を _convert_lock 内に移して解消。
  • Gallery: PNGチャンクの読み取りを先勝ちに統一(png_extractor と同じ)。ワークフロー読み込みとの食い違いを防止。

パフォーマンス・保守性

  • Gallery: 一括選択の変換対象判定を O(選択数×画像数) から O(選択数) に(パス→画像Mapのキャッシュ + some で打ち切り)。12,780枚の全選択で約33ms。
  • Gallery: history.jsonl を (mtime_ns, size) キーの索引としてキャッシュ(OrderedDict、上限16件)。
  • 音声・動画の拡張子判定を isAudioFile() / isVideoFile() に統一し、波形サムネイルの版数を WAVE_THUMB_VERSION 定数に集約(JS/Python)。

v0.8.0(2026-10-11)

ComfyUIの音楽生成の増加に合わせ、Galleryで音声ファイルを画像・動画と同じ一覧で扱えるようにした。

  • gallery_service.py: AUDIO_EXTENSIONS を追加し IMAGE_EXTENSIONS に合流。再生時間は_read_media_dimensions()(PyAV)、埋め込みタグは_read_audio_metadata()で取得(ストリーム側タグも統合し、prompt/workflowは小文字キーでも引ける)。_extract_embedded_workflow()も音声に対応し、Prompt/JSONタブが動く。
  • サムネイルは_render_audio_waveform()でPyAVデコード→8kHzモノラル→列ごとの最大振幅を描画してJPEGキャッシュ。拡張子ごとに波形色を変える(AUDIO_WAVE_COLORS)。キャッシュキーに版数(wave2)、サムネイルURLにも&v=wave2を付けブラウザの24hキャッシュを回避。
  • gallery_routes.py: Windowsで音声MIMEが返らない/不統一なためmimetypes.add_typeで明示登録。FileResponseのRange対応でシークも可能。
  • gallery-tab.js: isAudioFile()、♪バッジ、詳細パネル/ライトボックス/Compareで「波形+<audio controls>」を表示(音量は動画と共通設定)。詳細パネルは音声のとき固定高さをやめ、再生メニューが見切れないようにした。PSD書き出し・Edit as Layersでは音声を除外。

Video: Assetサブタブで音声を素材として利用

  • 先に音声を一覧から除外していたのを改め、種別フィルタに「Audio Only」を追加。音声はプレイヤー付きで、「BGMに設定」「サウンドとして追加(Audioトラックの選択クリップ先頭に配置)」ボタンが使える。video-edit-tab.jsにaddSoundFromFile()を追加。Editに送る/オーバーレイ/First・Last Imageと、Frame/GIFツールへのSourceプレビュー投入は音声では出さない。
  • 新規py/services/convert_service.py(PyAVのみ、システムffmpeg不要)。出力はFLAC / MP3 / Opus / M4A / WAV(Vorbisエンコーダが無いため.oggは入力のみ)。ビットレート・サンプルレート・チャンネル指定可。
  • GalleryService.convert_file() + POST /wfm/gallery/convert(1ファイルずつ呼ぶ。run_in_executor+同時1本のロック)。同じフォルダにname.ext(既存ならname_converted.ext、以降連番)で保存し上書きしない。
  • メタデータ: prompt/workflowと曲情報タグを、タグを持てる形式(FLAC/MP3/Opus/M4A)へ引き継ぐ。WAVは対象外(通知)。お気に入り/タグ/メモ/グループの引き継ぎ、変換成功後の元ファイル削除(確認ダイアログ付き)を選択可。
  • UI: 一括バーに「変換...」ボタン(音声/動画を含む選択時のみ表示)とダイアログ。i18n(en/ja/zh)対応。
  • A1: PNGの読み取りがIENDで止まっていたため、ComfyUI-Custom-ScriptsがIENDの後ろに追記するworkflowチャンクを読めなかった(_read_png_metadata() / extract_png_workflow())。
  • A2: WebPのEXIFからworkflow:/prompt:を取り出す_read_webp_metadata()を追加。
  • A3: ワークフローが無い画像(A1111/Forge/Fooocus)はparametersからPromptタブを表示(extractPromptsFromParameters())。
  • A4: API形式のImageMetadataLoRALoader(lora_N/strength_model_N/strength_clip_N)をMetadataタブとサイドパネルIタブで検出。

Library/Nodes: 表示速度の改善とAlt+クリックお気に入り

  • LibraryのNタブ: Alt+クリックでお気に入りをON/OFF。/object_info(数MB)の取得を待たずに一覧表示し、Category/PackageはLiteGraph.registered_node_types[..].nodeDataから取得(無い環境のみ裏で取得)。
  • SPAのNodesタブ: 数千件を一括でDOM生成していたのを分割描画に変更し、検索をデバウンス+検索文字列の事前計算で軽量化。さらに/api/wfm/nodes/light(INPUT_TYPESを呼ばず約6ms)で一覧を先に表示し、入力定義を含む/object_info(実測で約14秒)は裏で取得して補完する方式にした。
  • フォルダ名/パスの部分一致(空白区切りAND)で絞り込み、Enterで先頭候補へ移動。音声/動画も移動できるため、文言を「N件移動しました」に変更(movedNImages、moveToFolderTitle)。

その他

  • ヘルプ(en/ja/zh)とREADMEを更新。READMEのバージョンバッジが0.7.13のままだったのを修正。

v0.7.14(2026-10-09)

Comic Creator連携: nanobananaタブの生成履歴をGallery/Metadataタブに表示

Comic Creator(eagle_comic_creator_spa)のnanobananaタブがcc_nanobananaフォルダに書く生成履歴history.jsonl(1行=1生成リクエスト、filenames配列で画像ファイルと対応、prompt/negative_prompt/engine/model/image_size/seed/timestampを記録)を、WFS側のGallery/Metadataタブから閲覧できるようにした。

  • gallery_service.pyに_read_nanobanana_entry()を追加。画像の親フォルダ名がcc_nanobananaの場合のみ同フォルダのhistory.jsonlを新しい行から探索し、自分のファイル名を含むエントリを返す(既存のget_image_metadata()レスポンスにnanobananaフィールドとして追加、新規エンドポイントは不要)。
  • Gallery詳細パネルに「Nanobanana」タブを新設。cc_nanobananaフォルダの画像のみタブボタンを表示し、engine/model/size/seed/timestamp/prompt/negative promptを表示。
  • Metadataタブ右ペインにも同内容の「Nanobanana」セクションを追加。Galleryの「Metadata」ボタンから開いた場合は画像パスも渡されるようになり(loadFileIntoMetadataTab(file, path))、サーバーの/wfm/gallery/image/metaから画像サイズとnanobanana履歴を補完する。nanobanana画像はComfyUIワークフローを埋め込んでいないため、従来は「メタデータなし」エラーになっていたが、nanobanana履歴があればエラーにせずNanobananaセクションのみ表示するようにした。
  • cc_nanobananaフォルダの画像はComfyUIワークフローを埋め込んでおらずPromptタブに表示する情報が無いため、Gallery詳細パネルのPromptタブ自体を非表示化(フォルダ名で判定、history.jsonlに対応エントリが無い古い画像でも同様に隠れる)。

Metadataタブ: レイアウト再編(LoRA/Promptを中央へ、右ペインをSettings+Nanobananaに)

  • 中央ペイン(Col2): Checkpoint / VAE / Diffusion Model / Text Encoder に加えて LoRA / Prompt もここへ移動。
  • 右ペイン(Col3): 「Settings」(画像サイズ、Seed、Steps、CFG、Sampler、Scheduler、Denoise)と、上記「Nanobanana」セクション。
  • KSampler系ノードの設定値を抽出するextractSamplerSettings()を新規実装。API形式ワークフローの名前付きinputs(seed/noise_seed/steps/cfg/sampler_name/scheduler/denoise)から読む。UI形式ワークフローはconvertUiToApi()を経由して同じ抽出を通す(サブグラフの有無に関わらず常に変換を試みるよう変更。既存のCheckpoint/LoRA/Prompt抽出ロジックには影響なし、変換結果はSettings抽出にのみ使う)。
  • 画像サイズは、Galleryから開いた場合はサーバー側の実測値(/wfm/gallery/image/metaのwidth/height)、ドラッグ&ドロップされた単体ファイルの場合はプレビュー画像のnaturalWidth/naturalHeightを使う。
  • i18n(en/ja/zh)・ヘルプカードを更新。

バグ修正: LiveChatStreamなど複数出力テキストノードがAPI形式プロンプト抽出で解決できない不具合

ComfyUI-LiveChatStreamのresponse(LLM応答全文)出力をCLIPTextEncodeに直結した場合にGallery/Metadataタブでプロンプトが表示されない、という報告を調査。

  • 原因: PNGのprompt(API形式)埋め込みからのプロンプト抽出はpromptチャンクを優先して使うが(Metadataタブ・Gallery Promptタブとも)、API形式のresolveLinkedText()は接続元ノードの値を決め打ちのキー名リスト(text_positive/text/prompt/value等)でしか探しておらず、LiveChatStreamの実際のウィジェット名(prompt_text/negative_text/response_text/chat_p_text/chat_n_text/thinking_text、nodes.pyのINPUT_TYPESで確認)がどれとも一致しないため、responseだけでなくpositive/negativeも含め、API形式経由では本来解決できていなかった(UI形式のworkflow埋め込みはwidgets_valuesを出力スロット順でそのまま読むため、たまたま正しく解決できていた=どちらの埋め込みがPNGにあるかで体感が変わっていたと推測)。
  • metadata-tab.jsのresolveLinkedText()、およびサイドパネルIタブ(node_sets_menu.js)の複製ロジック_resolveLinkedText()の両方に、class_type === "LiveChatStream"の個別対応を追加(LIVE_CHAT_STREAM_OUTPUT_KEYSで出力スロット→ウィジェット名を対応付け)。
  • 実機(稼働中のComfyUI_5)の/wfm_static/js/metadata-tab.jsに対し、約19,000〜40,000文字の長文応答を模したAPI形式ワークフローで検証し、全文正しく(0.4ms程度で)抽出されることを確認。文字数そのものは無関係だった。

既存の「Send to LI node」(選択画像をComfyUIキャンバス上の選択中ノードのimageウィジェットへ送る)の動画版。

  • node_sets_menu.jsに_wfmFindVideoWidget()/window.wfmSendVideoToSelectedNode()を追加。ネイティブのLoadVideoノード(comfy_extras/nodes_video.py、fileウィジェット)に絞って探す(imageと違いfileという名前は他ノードとも衝突しうるためnode.type === "LoadVideo"で限定)。
  • Galleryツールバーに「Send to LV node」ボタンを追加。動画以外が選択されている場合はエラートースト。アップロードは既存の/upload/imageエンドポイント(拡張子を問わず任意バイナリをinputフォルダへ保存できる汎用エンドポイント)をそのまま流用。
  • 実機でLoadVideoノードを作成→選択→送信し、fileウィジェットの値が正しく書き換わることを確認(ComfyUI_00013_.mp4 → test_video_12345.webm)。
  • gallery_service.pyの.mp4限定だった5箇所(埋め込みメタデータ読み込み・寸法/再生時間取得・サムネイル抽出・PSDレイヤー除外判定・一覧フィルタ)をVIDEO_EXTENSIONS = {".mp4", ".webm"}ベースに統一。いずれもPyAV(av)経由でコンテナ形式を問わず同じコードパスのため、webm固有の実装追加は不要だった。
  • gallery-tab.jsのisVideoFile()をwebm対応に拡張。一覧表示・プレビュー・ライトボックス・Comic Creator連携・一括レイヤー編集の動画除外フィルタなど、これを参照する全箇所が自動的にwebm対応。
  • ヘルプカード(Gallery/3言語)を更新。

意思決定モデルクライアントにvision対応を追加(移植、消費する機能はまだ無い)

Comic Creatorのdecision-client.jsが2026-10に追加したvision対応(Ollamaのclef等、画像入力可能な意思決定モデル向け)を、WFS本家のstatic/js/decision-client.jsへ移植した。

  • decide(state, questions, opts, images)に第4引数images(base64配列、data:プレフィックス無し)を追加。
  • imageToBase64(src)(Blob/data URL/画像URL→base64)、supportsDecisionVision(opts)(設定中のモデルが画像入力に対応しているかをOllamaの/api/showで判定。capabilitiesにvisionが無いビルドではprojector_infoの有無でも判定するフォールバック込み)を新規追加。
  • DECISION_BACKENDS.ollama.modelsに"clef"を追加(27B・vision対応、Ollama 0.35.1以降が必要。clef-flashは2026-10時点でOllama側の不安定挙動があるため含めていない)。
  • 実機(稼働中のOllama、clefモデル)でsupportsDecisionVision→true、imageToBase64→decide(images=[...])の一連の流れを実画像(canvas生成の赤い正方形)で検証し、red: 0.99 / blue: 0.009と正しく判定されることを確認。
  • 既存の呼び出し箇所(settings-tab.js、vram-prepare.js)はimages引数を使わないため後方互換。消費する機能(Gallery自動分類等)はまだWFS側に無く、クライアントライブラリの同期のみ。

v0.7.13(2026-10-06)

VRAM調整(Ollama)— Comic Creatorから移植

画像生成の直前にOllamaでロード中のモデルをアンロードし、ComfyUIの空きVRAMを確保する機能をComfyUI Comic Creator(/api/ccc/vram/prepare)から移植。

  • 設定タブに「VRAM調整(Ollama)」セクションを新設。生成前の動作は「何もしない」(既定)/「空きが目標未満のときだけ必要な分をアンロード」/「毎回すべてアンロード」、目標の空き(GB)、「今すぐすべてアンロード」ボタン。設定はブラウザの wfm_vram_settings。
  • フックは comfyUI.queuePrompt() の直前(static/js/vram-prepare.js の prepareVramForGeneration())。GenerateUI・Lab・バッチ・Video・AI TOOLのChat生成など、Workflow Studio経由の生成すべてに効く。失敗しても生成は止めず、アンロード時・目標未達・失敗時のみトーストで通知(連続生成で同じ目標未達の警告は繰り返さない)。ComfyUIのキャンバスから直接実行する生成は対象外。
  • サーバー: 新規 py/routes/vram_routes.py(POST /api/wfm/vram/prepare、Content-Typeは application/json のみ=他サイトからの単純リクエストでモデルを外されないため)。空きVRAMは nvidia-smiの実測+ComfyUI自身のtorch未使用キャッシュ(ComfyUIの get_free_memory はOllamaのロード/アンロードに反応しないことがあるため)。取れなければComfyUIの値。autoは size_vram が実際に空く量より小さいため、大きいモデルから1台ずつアンロードしてそのつど実測で止める。アンロードは keep_alive:0 の /api/generate、受け付けない意思決定モデルは /api/chat で再試行し、最後に /api/ps で確認。同時実行はロックで409。
  • 対象のOllama: 既定の 127.0.0.1:11434(常に追加)+OllamaのときのAI TOOL・Prompt・TaggerのAI設定と意思決定モデル。接続先は v0.7.12 の url_guard(loopback+WFS_ALLOWED_BACKEND_HOSTS)を通し、許可外のURLはリクエスト全体を拒否せずそのURLだけ飛ばす。
  • 「モデルをアンロード」ボタン(AI TOOLタブ、Prompt/Taggerの⚙設定モーダル)は、Ollamaのとき意思決定モデルを含む全モデルをアンロードするよう変更(同じサーバー処理)。サイドパネルAタブの Settings にも「VRAM (Ollama) → Unload all now」を追加(サイドパネルからはSPAの vram-prepare.js を import できないため、同じlocalStorageキーから接続先を集める小関数を node_sets_menu.js に内蔵)。
  • 実機(ComfyUI_5、ポート8189、RTX 12GB)で確認: qwen3.5:9b+tev1:0.8bをロードし空き1.5GBの状態から、auto(目標4GB)はqwen3.5:9bのみアンロードして空き1.7→9.1GB・tev1は残る、allはtev1も外す(各約1.1秒)。415/400の入力検証、許可外URLのスキップ、設定タブUI、queuePrompt() 経由のフック(/prompt 送信のみ差し止めて確認)、各アンロードボタンを確認。

Workflowタブ: GenerateUIチェック(変換・操作性バッジ)

ワークフローをGenerateUIに読み込む前に、動くかどうかを確認できるようにした。

  • 詳細モーダル(ダブルクリック)のタイトル下に、形式バッジ(UI/API/App形式)・「GenerateUIチェック」ボタン・✅⚠❌ の結果バッジ2つ。サイドの詳細ペインにも名前の下に形式バッジと結果バッジ(選択時に自動チェック、結果のみ)。バッジにマウスを乗せると理由をCSSチップで表示(詳細ペインはペイン幅いっぱいに表示)。
  • 変換: GenerateUIと同じ convertUiToApi() で変換。未対応形式・変換エラー・このComfyUIに無いノード型は ❌、この環境に無いCOMBO値(モデル・LoRA・画像など)は ⚠。UI形式は変換時に先頭の選択肢へ無警告で差し替わる(全COMBOの差し替えを記録する getLastComboSubstitutions() を追加。GenerateUIの挙動は不変)。API形式は変換しないため値を直接照合し、「そのまま送るとComfyUIの検証エラー(Value not in list)」と案内。
  • 操作性: 設定を持つノードのうち、analyzeWorkflow() でGenerateUIに表示されるノードの割合(✅80%以上/⚠50%以上/❌それ未満)。チップにプロンプト欄・サンプラー数と、ノードグラフで変更が必要なノードを列挙。
  • 当初は意思決定モデル(Unsloth laya)のscoreで操作性を評価する設計だったが、実測で区別できなかった(全ノード非表示の状態2.54 > 全表示2.35、状態を単純化しても2.24対1.87、yes確率0.41対0.44、choiceはどちらも同じ選択肢)ためルール判定に変更。
  • 結果は保存せずセッション中のメモリのみ。チェックは約20ms。/object_info の初回取得(約10秒)を避けるため、モーダルを開いた時点で先読み。

ヘルプ

設定タブ(VRAM調整)、Workflowタブ(サイドパネル・GenerateUIチェック)、Prompt/Taggerタブ(アンロード)、AI TOOLタブ、サイドパネルAタブの記述を3言語で更新。


v0.7.12(2026-10-04)

セキュリティ: バックエンド接続先をブラウザから任意指定できた問題(SSRF)を修正

ComfyUI-Manager登録PR(LiveChatStream、Comfy-Org/ComfyUI-Manager#3340)のレビューで指摘された「リクエストから接続先URLを受け取り、サーバーが接続する」問題が、WFSにも同じ構造で存在していたため修正。

  • 対象: POST /api/wfm/ollama/chat(bodyの url。従来は検証なしで、任意ホスト・クラウドメタデータ・file:// に接続できた)、Tagger VLM(/wfm/tagger/vlm/models・vlm/predict・batch/start の api_url。Unslothのみ制限されていた)、Eagle(/api/wfm/eagle/add・/test の eagleUrl)、Unslothプロキシ(baseUrl)。
  • 新規 py/services/url_guard.py に共通化。許可は loopback(localhost / 127.0.0.1 / ::1、ポート自由) と、運用者が環境変数 WFS_ALLOWED_BACKEND_HOSTS(host または host:port のカンマ区切り)で指定したホストのみ。http(s) 以外(file:// 等)、URL内の認証情報、localhost.evil.com のような偽装ホストは拒否。リダイレクトは追従しない(302は接続せずエラー)。
  • 挙動変更: LAN上の別マシンのOllama/Eagle等を使っている場合は WFS_ALLOWED_BACKEND_HOSTS の設定が必要(拒否時のエラーメッセージに案内を表示)。
  • 実機(ComfyUI_5、ポート8189)で、メタデータIP・file://・公開IPの拒否、loopbackのOllama chat/Tagger一覧/Eagle test/Unsloth Decision APIの正常動作を確認。
  • 注意: ComfyUIを --listen で公開すると、到達できる相手は引き続きこれらのルートを使える(接続先を変えられないだけ)。信頼できるネットワークでのみ使うこと。

v0.7.11(2026-10-02)

セキュリティ: CivitAI情報の説明文のHTML無害化(保存型XSS対策)

  • Models詳細パネルのCivitAIタブは、説明文(description)をエスケープせずHTMLとして挿入していた。CivitAI APIの応答は従来から同じ扱いだったが、v0.7.10で追加した .cm-info.json(Stability Matrix)取り込みにより、ローカルのサイドカーファイルからも同じ経路で入るようになった。細工されたファイルがモデルと一緒に配布された場合、詳細パネルを開いただけでスクリプトが実行され得る(ComfyUIと同一オリジンのため、ComfyUIのAPIを操作できる)。
  • static/js/util.js に許可リスト方式の sanitizeHtml() と safeHttpUrl() を追加し、説明文・モデルURL・サンプル画像URLに適用。script/style/iframe/svg/form 等の要素、on* 属性、style 属性、javascript:/data: URL、相対URL(ComfyUI自身のURLへリクエストを送らせる経路)を除去する。許可するのは段落・見出し・リスト・強調・表・code・a/img(絶対http(s) URLのみ)など。
  • Playwright(実ブラウザ)で攻撃文字列16種を検証し、スクリプトが一度も実行されないことを確認。

セキュリティ: サイドカー経由のSSRF/ローカルファイル読み取りと、Hostヘッダ依存の接続先

取り込み経路の再点検で見つかった2件を修正。

  • プレビュー画像のダウンロード(download_image)のURLが無検証だった。URLは .metadata.json(Lora Manager形式のローカルサイドカー)の civitai.images から来るため、細工されたサイドカーがあると、サーバーが任意のURLへ接続する(SSRF)。file:// を指定すればローカルファイルを読んでプレビュー画像として保存でき、プレビューAPI経由で取り出せた。https かつ CivitAI系ホスト(civitai.com/civitai.red/civitai.green とそのサブドメイン)のみ許可し、リダイレクト先も同じ条件で検証、サイズ上限50MBを設定。正規のCivitAI画像の取得は実環境で確認済み。
  • Lora Manager連携(lora_manager_bridge)の接続先を、リクエストのHostヘッダから組み立てていた。Hostの偽装(DNSリバインディング等)で任意のホストへ接続させられるため、ループバック(127.0.0.1)+実際に待ち受けているポートに固定。
  • サンプル画像・フォールバック画像のURLにも safeHttpUrl()(絶対http(s)のみ)を適用。

.cm-info.json / .metadata.json 取り込みの堅牢化

  • ModelId/VersionId は正の整数のみ受け付ける(URLに文字列を埋め込まない)。不正なら情報は採用せず、sha256のみ利用。
  • タグ・トリガーワードは文字列/数値のみ・件数上限あり。JSONのトップレベルがオブジェクトでない場合は無視。
  • サイドカーJSONのサイズ上限を2MBに設定(巨大ファイルでメモリを使い切らない)。

v0.7.10(2026-10-02)

Windows: ComfyUIが固まる問題の原因特定と対策(Selectorイベントループ)

v0.7.9で「原因未特定」としていたComfyUIの無応答は、ほぼ素の環境(ComfyUI 0.34 / Workflow Studio 0.7.2〜0.7.9、ComfyUI Desktop・Stability Matrix両方)でも再現し、/system_stats すら応答しなかった。py-spy(dump --native)でメインスレッド(イベントループ)が asyncio proactor _call_connection_lost → socket.close → closesocket → NtClose で停止していることを確認。ハッシュ計算・モデルファイル・CivitAI取得・Windows更新(26H2を戻しても再現)・ComfyUI/WSL/Windows Firewall Controlのいずれも原因ではなかった。

  • きっかけは「プレビュー画像の多い一覧を開き、続けて他の一覧へ切り替える」操作。ブラウザが読み込み中の画像リクエストを一斉に中止し、短時間に多数の接続が開閉する。
  • 対策: prestartup_script.py でWindowsのasyncioループをProactorからSelectorへ切り替えるスイッチを追加(既定オフ)。設定画面の「Windowsの安定性(イベントループ)」のチェック、または環境変数 WFS_SELECTOR_LOOP=1 で有効化(ComfyUI再起動後に反映)。有効化すると同じ操作で再現しなくなった。
  • トレードオフ: asyncioのサブプロセス機能が使えない/同時接続数の上限が約512。ComfyUI-Managerの閲覧・CivitAI取得は問題なし。
  • 固まったプロセスは taskkill しても終了せずポートを握り続けるため、再起動が必要になる。固まったときは py-spy dump --native --pid <PID> でメインスレッドの位置を確認する。

Models: プレビュー画像の404削減

  • プレビューが無いモデルにも画像リクエストを出しており、一覧の切り替えごとに大量の404と接続の開閉が発生していた。
  • 新API GET /api/wfm/models/preview-keys?type= がフォルダを1回走査して「プレビューを持つモデル」の一覧を返し、画面側は無いモデルへ画像リクエストを出さない(取得失敗時は従来動作)。サムネ変更・CivitAI取得でのプレビュー保存・一括取得後は一覧を更新。
  • 無効化(.disabled)されたモデルのプレビューが表示されなかった問題を修正(find_preview_image が <名前>.disabled を見ていなかった)。

Models: モデルフォルダ設定(Stability Matrix対応)

  • 設定に「モデルフォルダ」を追加。追加のモデルルート(例: Stability Matrixの Models、; 区切りで複数可)直下の StableDiffusion/Lora/VAE/ControlNet/TextEncoders/DiffusionModels/Embeddings/HyperNetworks(またはComfyUI名 checkpoints/loras …)をModelsタブの一覧とフォルダ解決に追加する。
  • Modelsタブの一覧はComfyUIの /object_info 由来で追加フォルダを含まないため、/api/wfm/models/files?extra_only=1 の結果を合流させている。

CivitAI取得: ハッシュ計算を行わない運用へ

  • 一括取得はハッシュ計算をしない。保存済みsha256、.metadata.json(Lora Manager)、.cm-info.json(Stability Matrix)、Lora Managerの /api/lm/{loras,checkpoints,embeddings}/list のsha256から解決できる分だけを取得し、未確定のモデルは「ハッシュ未計算」として件数を表示する(大きなモデルを多数ハッシュするとディスクが飽和するため)。
  • .cm-info.json 取り込み: Hashes.SHA256 とCivitAI情報を読む。ファイルサイズが無いため ImportedAt より後にモデルが更新されていたら不採用。画像一覧を持たないので source: "cm-info" を付け、個別の取得/更新でAPIの完全な情報に置き換える。
  • 詳細パネルのCivitAIタブで、ハッシュ未計算のモデルは赤字「未計算」を表示。取得/更新ボタンでハッシュ計算して取得する(ファイルサイズ/更新日時も保存)。
  • クライアント切断(再読み込み・タブ遷移)で一括取得を中断し、計算済みのハッシュだけ保存して終了。ハッシュ計算を3→2並列、読み込み1MB→512KB、プレビュー保存4→2並列に抑制。
  • 詳細パネルの「ファイルパス」表示を削除。

ヘルプ・i18n

  • 設定のヘルプに「モデルフォルダ」「Windowsの安定性」を追加(英日中)。

v0.7.9(2026-10-02)

Models: CivitAIベースモデルからの自動バッジ作成

バッジ管理モーダルに「CivitAIから自動バッジ作成」を追加(static/js/models/auto-badge.js)。現在のタブのモデルのCivitAI baseModel からバッジを作る。意思決定モデルは使わず、ルール表で決定的に決める("SDXL 1.0"/"SDXL Turbo"等はSDXLにまとめ、Pony/Illustrious/NoobAI/Flux.1/Flux.2/Qwen/Wan/Z-Image/HiDream等は別バッジ。表に無い値は文字列そのまま、"Other"と空は対象外)。

  • モデル側は既存バッジを残したまま追加(同名が既にあれば何もしない)。パレットは同名バッジの色を上書き可(チェックで切替)。「すでにバッジが付いているモデルはスキップ」オプションあり(スキップ分のバッジ名はパレットにも作らない)。
  • プレビューで件数・新規/既存を確認してから適用。

CivitAI一括取得の高速化と安定化

参考にしたcivitai-updater/tiny-model-managerはどちらも単発GETの逐次実行で、当プラグインの一括POST(100件/回)のほうが進んでいた。遅さの原因は取得ではなくハッシュ計算側だった。

  • 保存済みsha256の再利用: 一括取得が保存済みハッシュを無視して毎回全件再計算していた。sha256Size/sha256Mtimeを記録してファイル差し替えを検知(記録の無い旧データは信頼)。
  • サイドカー取り込み: <名前>.metadata.json のsha256とCivitAI情報を、サイズ一致かつfiles[].hashes.SHA256一致の場合だけ採用(ハッシュ計算・通信不要)。実データでサイドカー87件中74件が利用可能、58件にCivitAI情報あり。
  • 並列ハッシュ(3並列)+ 1MB読み、確定ごとに5秒間隔でメタデータへ保存(途中で止めても計算済み分が残る。以前は最後にまとめて保存していた)。
  • プレビュー保存を4並列・別スレッド化(以前はasyncハンドラ内で逐次ダウンロードしイベントループを塞いでいた)。進捗に「プレビュー保存中」を追加。
  • 未登録スキップ: CivitAIに存在しなかったモデルに civitaiNotFound を記録し、ツールバーの「未登録はスキップ」で一括取得の対象外にできる(既定オフ)。通信エラーで失敗した分は記録しない。見つかれば記録は自動解除。

フォルダのシンボリックリンク/ジャンクション対応

  • モデル走査を rglob から自前の _iter_files に変更。リンクは辿りつつ、祖先フォルダへ戻る循環だけを検出して入らない(警告ログ)。
  • 一括移動の移動先チェックを resolve() 比較から論理パス比較に変更し、リンク先フォルダへの移動が「Destination is outside model root」で拒否される問題を修正(..・絶対パスでの脱出は従来通り拒否)。
  • 注意: リンク配下のモデルを削除すると、リンク先の実ファイルが消える。

実機で分かったこと

  • TextEncoderタブでの一括取得中にComfyUIが応答しなくなる事象があった。py-spyのダンプでは、イベントループがWindowsのclosesocket内で停止しており(作業スレッドは全て待機中でプラグインのコードはスタックに無い)、本変更が直接原因とは断定できなかった。PC再起動と動作に影響しうる他のカスタムノードの削除後は正常動作を確認。
  • この環境(管理者権限なし)ではシンボリックリンクを作れず、ジャンクションのみでテストした。

v0.7.8(2026-10-01)

Video Edit: マルチトラック化(ビデオ/オーバーレイ/テキスト/オーディオ)

タイムライン上部のボタンでトラック表示を切り替える(ビデオ=青 #4a9eff・オーバーレイ=青・テキスト=緑 #3fb950・オーディオ=赤 #f85149)。下のツールメニューも連動して切り替わる(ビデオ=トリム+クロップ、オーバーレイ=オーバーレイクリップ、テキスト=テキストオーバーレイ、オーディオ=オーディオ設定)。Save/Load/書き出しは右に固定。

  • テキストをタイムライン直付けに変更: 時間をクリップ相対でなくタイムライン絶対秒で持つ(_s.texts)。テキスト帯のドラッグ移動(クリップをまたげる)と端ドラッグでのリサイズに対応。旧形式プロジェクト(クリップ内texts)は読込時に絶対秒へ自動変換する(変換処理は実機未検証)。クリップ複製ではテキストは複製されなくなった。
  • 追加サウンド: オーディオトラックに「+ サウンド...」で効果音・ナレーションを何個でも配置(開始/長さ/音量dB、帯のドラッグで移動・端ドラッグでトリム)。書き出しはLoadAudio→TrimAudioDuration→AudioAdjustVolume→AudioConcat(無音パディング)→AudioMergeでBGMの上にミックス。実際のComfyUIで実行成功を確認(音の位置自体は耳では未確認)。
  • オーバーレイ(PinP): 動画/画像をメイン動画の上に重ねる。時間・位置(9方向プリセット/X/Y)・サイズ・不透明度を個別指定。追加位置は4隅→中央の順にずらす。プレビューには点線枠+サムネイルを表示。Assetタブの詳細パネルに「オーバーレイに追加(PinP)」ボタンを追加。
  • 合成方式: ComfyUIグラフには手を入れず、既存のテキスト焼き込みと同じPyAV 1パス(/api/wfm/video/edit/overlay-textにpipsを追加)で、オーバーレイ→テキストの順に描画。動画オーバーレイは終端フレームを保持。オーバーレイ自身の音声はミックスしない。実際の書き出し出力のフレームを目視確認。
  • Undo/Redo・プロジェクト保存/読込・ヘルプ(3言語)に対応。

フルバックアップ: 追加サウンド/オーバーレイの素材漏れを修正

「Video Editプロジェクトの素材を含める」が、クリップとBGMしか集めていなかったため、追加サウンドとオーバーレイクリップの素材がZIPに入らなかった(_iter_video_edit_media_refsがclipsとBGMだけを走査)。sounds/pipsも走査するよう修正。単体テストと実APIでの出力ZIPの中身確認済み(パストラバーサル・出力フォルダ参照・旧形式・壊れたJSONの扱いも確認)。復元側は無変更。

実機で分かったこと

  • ファイル選択ダイアログ: Overlay追加ボタン(label+非表示input、BGMと同方式)が、ユーザーの通常ブラウザタブではダイアログを出さず、Shift+クリックの別ウィンドウでは出た。コードの欠陥ではなく通常タブ環境側の可能性が高い(未特定)。念のためOverlay/Soundボタンは透明な本物の<input type=file>をボタンに重ねる方式に変更。Playwrightはダイアログを横取りするため、この種の不具合は自動テストでは検出できない。
  • 検証用ブラウザに待機中のファイル選択が溜まると、ページが古い状態のままになりうる(サーバーはCache-Control: no-store)。

v0.7.7(2026-09-30)

意思決定モデル: Ollama 0.35+ バックエンド追加

Ollama v0.35.0(2026-09-28)がPOST /v1/systemoneで意思決定モデルに対応した(質問・回答の形式はUnsloth/ollayaと同じ)。モデルはtev1(Together AI、4B・4.5GB)/ tev1:0.8b(812MB)/ nimble(Bespoke Labs、9B・9.5GB)で、ollama pullで取得する。

  • decision-client.js: バックエンドollamaを追加。APIキー不要でCORSも許可されているため、Unslothのようなサーバー中継は通さずブラウザから直接呼ぶ(既存のOllama LLMバックエンドと同じ)。Ollamaのエラー{"error": ...}(例: model "tev1" not found, try pulling it first)はそのまま表示。
  • listDecisionModels(): /api/tagsのcapabilitiesに"decision"を持つインストール済みモデルだけを返す(通常のLLMは出ない)。
  • Settings「意思決定モデル」: バックエンド切替でURL(11434)とモデル一覧を更新、↻ボタン、意思決定モデルが無い場合はollama pull tev1:0.8b等を案内。Laya専用でなくなったためセクション名を「意思決定モデル」に変更。保存済みモデル名が別バックエンドのもの(Ollamaなのにlaya等)なら既定モデルへ戻す。
  • 実測: Layaが外した画像プロンプト(「masterpiece, 1girl, silver hair, school uniform…」)の判定を、tev1:0.8bは主題person 0.98〜0.99・NSFW「none」0.97〜0.98と全問正解。2回目以降0.2〜0.6秒。ユーザー実機のテストボタンで「接続成功 ✓ 280 ms、yes=0.914」。
  • 作業中の不具合: Pythonスクリプト経由で書いた正規表現の\bがバックスペース制御文字(0x08)に化け、モデル名補正が効かなかった(構文エラーにならず単体テストで発覚)。修正後、リポジトリ全体に制御文字が無いことを確認。

Unsloth: APIキー未設定時はキー無しで送信(Keyless API access対応)

Unsloth Desktopの Settings → API → Keyless API access →「Chat and inference」をオンにするとキー無しで使えるため、UNSLOTH_API_KEY未設定を許容した。

  • 対象は中継(py/routes/unsloth_routes.py: AI TOOL・サイドパネル・意思決定API)とTaggerのUnsloth VLM経路(py/services/tagger_service.py)の2か所。キー未設定なら事前エラーにせずAuthorization無しで送る。localhost限定(SSRF対策)は維持。
  • キー無しでUnslothが401を返した場合は「.envにキーを設定するか、Keyless API access →『Chat and inference』をオンに」という案内を返す。
  • 偽Unslothサーバー(キー必須/不要)で、キー無し成功・案内メッセージ・キー付与・非localhost拒否を検証(5回連続合格)、ユーザー実機でも確認。
  • ヘルプ・README・.env.exampleに、この設定は同じLAN内にもAPIを開放すること、「Allow tools」(キー無しの接続元にPython/ターミナル実行を許す)はオフのままにすることを明記。
  • 実機で発生した不具合: 英語ヘルプ文に"Chat and inference"をエスケープせずに入れ、i18n.jsが構文エラーになりSPA全体が「Loading workflows...」で停止した。この環境ではnode --check x.jsがESモジュールの構文エラーを見逃す(終了コード0)ことが原因で、以後は.mjsにコピーしてnode --checkする方式で確認している。

ヘルプ(英日中)の点検と修正

Settings・Video(Edit)ページを3言語で点検。言語間の内容差は無かったが、3言語共通の記載漏れ・不正確な点を修正した。

  • Video Edit: Saveで保存される内容に、クロップ・テキストオーバーレイ・音声/BGM設定を追記(従来は順序・トリム・参照ファイルのみ記載)。本文に出ていた内部キー名「(see helpVideo9)」を「Projectタブの項目を参照」に。素材はinput内ファイルへの参照のみであること、フルバックアップの素材オプションで保存できることを追記。
  • Settings: 未記載だった5セクション(Video Volume、Gallery出力フォルダ、デフォルトCheckpoint、CivitAI APIキー、G'MIC-Qtパス)をhelpSettings18〜22として追加。
  • フォント数「Google Fonts 16種」→「15種+システム既定」に訂正。
  • テンプレート(index.html)の英語初期表示のうち古くなっていた項目を、Settings・Video全項目についてi18nの英語と同期。

v0.7.6(2026-09-30)

Settings: 意思決定モデル (Laya) の専用設定

v0.7.5で入れたdecision-client.jsは接続先を「AI TOOLのバックエンドがUnslothならそのURL、それ以外は8888」とAI TOOL設定から借りていたため、LLM/VLMと併用するとURL変更が連動する・モデルやしきい値を選べない問題があった。Settingsタブに独立したセクションを新設し、wfm_decision_settings(backend / baseUrl / model / threshold)としてAI TOOL・Tagger設定とは別に保存する。

  • backendは現状Unslothのみだが、同じTypeSafe互換APIを話すollaya(:11435、キー不要)等を後から足せるようフィールドとして持つ。
  • モデル: laya(Unsloth側で選択中)/ laya-multilingual / laya-english / laya-typed-decisions。
  • 既定しきい値(0.5〜0.99、既定0.8): isYes/pickChoice/pickScoreでしきい値省略時に使う。
  • テストボタン: 保存前のフォーム値でnoul1問を送り、応答時間と回答を表示(testDecisionConnection())。
  • 実機確認: 初回はUnsloth側で Settings → API → Decision API → Serve requests がオフのため HTTP 404 The Decision API is off(中継・APIキーは正常に通っている)。オン後は「接続成功 ✓ 10430 ms、テスト回答 yes=0.945」(初回はモデル読み込み込み)。

Settings: フルバックアップのVideo Edit対応

Video Editのプロジェクト(video_edit_project/*.json)はデータフォルダ内なので元からZIPに入っていたが、中身はComfyUIのinput/にアップロードしたクリップ・BGMへの参照(serverRef)だけで、素材そのものは含まれていなかった。別環境への復元やinput/整理後はプロジェクトを開いてもクリップが読み込めない。説明文にもVideo Editの記載がなかった。

  • 3つ目のオプション「Video Editプロジェクトの素材を含める」(include_video_media=1): 全プロジェクトのclips[].serverRefとaudio.bgm.serverRef(type=input)を_video_edit_media/<subfolder>/<filename>に同梱。重複・欠損は除外し、input/の外を指す参照(..等)は含めない。
  • 復元時、_video_edit_media/はデータフォルダではなくinput/へ戻す。同名ファイルが既にあれば上書きしない(input/は他のワークフローと共有のため)。結果にmedia_restored/media_existingを返し「Video Edit素材: ○件復元、○件は既存」と表示。エクスポート専用の_external/とは扱いが異なる。
  • 説明文(Settings注記・ヘルプ・README)のバックアップ対象にVideo Editプロジェクトを追加。

実機で判明した既存問題(Request Entity Too Large): aiohttp 3.14のBodyPartReader.read()はComfyUIの--max-upload-size(既定100MB)を強制する。workflows/wildcardオプション込みのバックアップは約125MB(_external/だけで118MB)で超過し復元できなかった(今回の追加以前から起こり得た)。handle_import_fullをread_chunk()で1MBずつ一時ファイルへ流し込む方式に変更し、上限に関係なく復元できるようにした(ZIP全体をメモリに載せることもなくなった)。

検証: 一時フォルダでの単体テスト(素材収集・重複/欠損除外・パストラバーサル遮断・既存非上書き)、上限1MBのaiohttpテストサーバー(ComfyUIと同じ3.14.3)への5MBアップロード、ユーザー実機で「インポート完了: 45件 / Video Edit素材: 1件復元、3件は既存」を確認。

ヘルプ

Settingsページの「Connections」カードに意思決定モデルの項目(helpSettings17、英日中)を追加。フルバックアップの項目(helpSettings15/16)にVideo Editプロジェクトと素材オプションを追記し、テンプレート側の英語初期表示もi18nと同期(15はVideo Plansが抜けたままになっていた)。


v0.7.5(2026-09-30)

Ming Image ワークフロー対応(GenerateUI / Gallery・Metadata / サイドパネルIタブ)

ユーザー提供のming_image_test1〜5.json(T2I・参照画像Edit・レイヤー分解×2・標準KSampler T2I)に対応。

  • comfyui-workflow.js: TextEncodeMingImageEdit(prompt単一ウィジェット+images.image_N最大8枚のAutogrow参照画像)をTextEncodeQwenImageEditと同じ扱いで検出。ロールはsampler/guider配線から決まるので、レイヤー分解ワークフローでKSamplerのnegativeに繋がる空プロンプトの2つ目のインスタンスはNegativeとして出る。linked promptへのロール伝播も追加。EmptyQwenImageLayeredLatentImageをLatentパネル対象に追加。
  • SamplerCustomAdvancedのSAMPLER入力がsampler_nameを持たないノード(SamplerLCM)の場合はsamplerNodeId=nullとし、SettingsパネルのSampler欄をN/A無効化(従来は無関係なsampler名が選択表示され、Applyで存在しない入力に書き込んでいた)。
  • metadata-tab.js / node_sets_menu.js: BasicGuiderのconditioning入力をpositive扱い(従来は判別不能テキスト扱い)。UI形式でCLIPTextEncodeのtextがリンクされている場合、widgets_values[0]に残る古い入力値よりリンク先(PrimitiveStringMultiline等)を優先。

汎用バグ修正(top-levelノードのwidgets_valuesズレ): ウィジェットをリンク入力化しても現行フロントエンドは値をwidgets_valuesに残す(legacy full形式)。サブグラフ内部ノードは_stripLegacyLinkedWidgetValuesで正規化していたが、top-levelノードは未対応で、リンクされたウィジェットより後ろの値が1つずつズレていた(EmptyLatentImageのwidth/heightをGetImageSize/PrimitiveIntに繋ぐとbatch_size=1024、EmptyQwenImageLayeredLatentImageのlayers=1024等)。判定ロジックを_normalizeLinkedWidgetValuesに切り出し、convertUiToApiの本ループ前にも適用。

検証: Node上で稼働中ComfyUIの/object_infoを使い3系統(analyzeWorkflow / metadata-tab / node_sets_menu、UI形式・API形式両方)を5ワークフローで確認。回帰確認としてuser/default/workflows配下366件を修正前後のconvertUiToApiで比較し、差分35件はすべてズレていた値が正しくなったもの(KSampler steps=397027830130951→10、cfg=25→8等)。変換エラー1件は修正前から発生する無関係なもの。ユーザー実機で生成まで確認済み。

Unsloth Decision API(Laya決定モデル)クライアントの土台

Unsloth DesktopがTypeSafe互換の意思決定API(POST /v1/systemone、Laya)を提供するようになったため、活用機能の試行に先立って呼び出しの土台のみ実装(UIからの利用は未実装)。

  • py/routes/unsloth_routes.py: 既存プロキシ(.envのAPIキー付与+localhost限定)の_ALLOWED_PATHSに/v1/systemoneを追加。HTTPエラー時はUnsloth側エラー本文の先頭300字をmessageに含める(質問スキーマ誤りをフロントから診断できるように)。
  • static/js/decision-client.js(新規、SPA用): decide(state, questions, {model, baseUrl})・decideMany(同時実行数制限、個別失敗は{error})、ビルダーnoul/choice/score(Unslothのinstructions+criteria形式)、上限チェック(64問/255選択肢/10段階)、isYes/pickChoice/pickScore。ドキュメントの推奨どおりconfidenceではなくprobabilitiesでしきい値判定し、しきい値未満はconfident:false(自動適用せず提案表示する用途向け)。接続先はAI TOOL設定がUnslothならそのURL、それ以外はhttp://localhost:8888。
  • Layaはテキスト専用(画像入力なし)。画像の判定にはTaggerタグやVLMキャプションを経由させる必要がある。
  • 検証はプロキシをモックしたNode単体テストのみ(作業時にUnslothのDecision APIが待ち受けておらず実APIは未検証)。

v0.7.4(2026-09-29)

Video Edit: BGM/音声トラック合成(Phase 4)— 複数クリップ結合時の音声消失も解消

VIDEO_EDIT_TAB_PLAN.mdのPhase 4。着手前に既存の動画編集系ノードを調査し、ComfyUI Core 0.37.0の音声ノード群(LoadAudio/TrimAudioDuration/AudioConcat/AudioMerge/AudioAdjustVolume/EmptyAudio)とConcatenateVideoのcomplete_audio入力(結合動画全体の音声を上書き)だけで、書き出しグラフ内で完結できると判明したため、計画にあった独自mix-audio APIは作らなかった。音声トラックの組み方はH3Studioのreel書き出し(クリップ音声を尺にconform→連結→musicを重ねる)を参考にした。

グラフ構成: クリップごとにEmptyAudio(クリップ尺)+AudioMerge(自クリップの音声)で尺ぴったりのセグメントを作り(AudioMergeはaudio2をaudio1の長さにpad/trimし、音声なしクリップ=Noneならaudio1をそのまま返す)、AudioConcatで連結→(元音量≠0dBならAudioAdjustVolume)→BGM(LoadAudio→TrimAudioDuration(開始位置, 総尺)→AudioAdjustVolume)をAudioMergeで重ねてcomplete_audioへ渡す。単一の動画クリップで元音声そのまま・BGMなしの場合のみ従来通り再エンコードなしでパススルー。

UI: 書き出しパネルにオーディオ欄(元の音声を残す+音量dB、BGMファイル、BGM音量、BGM開始位置)。BGMは「BGM選択...」ボタン、オーディオ欄への音声/動画ファイルのドラッグ&ドロップ、AssetサブタブのVideoに追加した「BGMに設定(音声のみ)」(動画の音声トラックを使う。Galleryは音声ファイルを扱わないため)で指定できる。▶プレビュー中はBGMも<audio>で同時再生。クリアボタンでオーディオ欄も初期状態に戻る(BGMのみ設定されている状態でもクリア可能)。

実機で判明した注意点: 生のVideo Slice出力と画像由来のCreateVideoクリップをConcatenateVideoへ直接混ぜるとVideo chunk N could not be encoded compatibly(extradata/color space不一致)で失敗する——complete_audioの有無に関係なく。既存の「複数クリップ時は全クリップをGetVideoComponents→CreateVideoで再構成する」処理は音声対策だけでなくこのためにも必要だったので維持した。

検証: 動画2本(片方トリム)+静止画+BGM(-6dB)をUIから書き出し、尺6.625秒・音声同尺、区間ごとのRMSで元音声+BGM/BGMのみを確認。音声なし動画(audio=None)+BGM、mp4をBGMにした書き出しも確認。

Video Edit: テキストオーバーレイ(Phase 3)

クリップ単位のテキスト(文字・クリップ内の開始/終了秒・サイズ(フレーム高さ%)・色・9方向アンカー・縁取り・背景)。クリップ相対時刻で保持するので並べ替えに追従し、書き出し時に絶対時刻へ変換する。

Core TextOverlayは上/下固定・区間指定なし・既定フォントがCJK非対応、既存のテキスト系カスタムノード(advanced-textoverlay等)も静止画向けか独立パイプラインで、かつ全フレームをIMAGEテンソル化するとメモリが厳しい(720p×720フレームで約8GB)ため、新設のPOST /api/wfm/video/edit/overlay-text(VideoService.overlay_text_on_video()、asyncio.to_thread)でPyAVストリーム処理にした: 1フレームずつデコード→該当区間のテキストレイヤー(事前に1回だけ描画)をPillowで合成→libx264でエンコード、音声パケットは再エンコードせずリマックス。書き出しはグラフ出力をwfm_edit_pre中間ファイルに保存→焼き込み→中間ファイル削除(wfm_edit_pre接頭辞かつoutput内のみ削除対象)の2段構成。フォントはmeiryo→YuGothM→NotoSansJP→msgothic→arialの順に探索。プレビューはSource/Result枠上にCSSで近似表示(video-preview.jsにgetPreviewPaneElements()を追加)。

検証: ComfyUI再起動後、動画+画像クリップに英日テキスト2件を付けて書き出し、各時刻のフレームで表示/非表示・位置・日本語描画・背景、音声長の一致、中間ファイル削除を確認。

Video Edit: クロップ(Phase 2)

動画クリップごとに正規化座標(0..1)のクロップ矩形を持ち(ComfyUI-LoadVideoCropと同じ方式)、Sourceプレビュー上で移動+四隅リサイズ(アスペクト比 自由/16:9/9:16/1:1/4:3/3:4固定可、外側は暗転)。「完了」で確定、「キャンセル」で編集前のクロップに戻す。Plan/Assetサブタブ・他タブへの移動、別クリップ選択時は「完了」扱いで編集モードを自動終了。書き出しはCore VideoCrop(0.37.0でもexperimental)をVideo Sliceの直後に挿入——crop入力は計画どおり二重ネスト{"crop":{"x","y","width","height"}}で、偶数ピクセルに丸める(H.264 4:2:0制約)。出力サイズは先頭動画クリップのクロップ後サイズで、サイズの異なる他クリップはImageScale(crop=center)でフィット。解像度不一致ガードはクロップなしの動画クリップ同士だけ比較。ImageCropは0.37でdeprecatedのため不採用。

検証: 608x352→300x200のクロップ出力が元フレームの同領域と一致(平均画素差3.1=コーデック誤差程度)。UIで1:1クロップしたクリップを先頭に書き出し、280x280・字幕がクロップ後の枠内・2本目が中央フィットされることを確認。

Video Edit: Undo/Redo(Phase 5残り)

編集状態(クリップ順・トリム・保持時間・クロップ・テキスト・オーディオ設定)のJSONスナップショット履歴(上限100)。各編集経路に個別に仕込むのではなく、全編集が必ず通る既存の再描画3関数(_renderTimeline/_refreshTextPreview/_syncAudioPanel)から300msデバウンスで記録し、状態が変わったときだけpushする(選択・プレビュー・リサイズでは積まれず、連続入力やドラッグは1ステップにまとまる)。削除クリップはFile参照を持つ_clipRegistryから再アップロードなしで復元。↶/↷ボタンとCtrl+Z / Ctrl+Y / Ctrl+Shift+Z(入力欄内はブラウザ標準のundoを優先)。プロジェクト読込時は履歴リセット。

その他

  • プロジェクト保存にクロップ・テキスト・オーディオ設定を追加(旧プロジェクトは既定値で読込)。
  • BGM選択・Loadボタンが実ブラウザでダイアログを開かない不具合を修正: hidden <input type=file>をボタンのclickハンドラからinput.click()で開く方式が、ユーザー環境ではエラーも出さずに無反応だった(同タブのVideo Sourceは<label>で包む方式で正常動作)。両ボタンを<label>方式に変更。Playwrightはファイル選択を横取りするため自動テストでは再現できなかった。
  • トリム値入力直後に「+ テキスト追加」のクリックが効かない不具合を修正(テキスト欄実装時に混入): blur時のchangeでテキスト欄全体を再構築し、押そうとしたボタンが置き換わっていた。上限値だけ更新する方式に変更。

How to apply: ComfyUI Coreの新しい動画/音声ノードは、execution_successで判断せず出力をprobe・画素比較まで実測すること(VideoTrim/VideoCropの二重ネストのように、形式を誤ってもエラーにならず無視されるノードがある)。ファイル選択UIは<label>で包むネイティブ方式で作り、動作確認はユーザー実機で行うこと。


v0.7.3(2026-09-24)

GenerateUIタブにQwen Image 2.1(TextEncodeQwenImage21)対応を追加

Comfy-Orgのnightly workflowテンプレートqwen_image2.1_t2i.json系(TextEncodeQwenImage21ノードでprompt/negative_prompt/resolutionとimage_1〜image_10参照画像を一手に扱う、T2IとEdit兼用のsubgraph構成)をGenerateUIタブで使えるようにした。

変更内容: comfyui-workflow.jsのanalyzeWorkflow()にTextEncodeQwenImage21の分岐を追加。TextEncodeBooguEditと同じ「1ノードにprompt/negative_prompt両方を持ち、ロールは固定(KSampler側の配線をたどらない)」パターンでprompt_nodesへ登録し、Input タブのPositive/Negative Prompt欄に反映されるようにした。あわせて_getWidgetMapping()にTextEncodeQwenImage21: ["prompt", "negative_prompt", "resolution"]を追加し、Settings欄からの書き戻しにも対応。

調査で判明した既存アーキテクチャ: GenerateUIのModel/Diffusion Model/Text Encoder/VAE/KSampler/EmptyLatentImage検出は元々ノードタイプ非依存の汎用ロジックなので、UNETLoader+CLIPLoader+VAELoaderの3分割ロード構成(Flux/Qwen Image系で共通)は無改修でそのまま動作した。また画像アップロードUI(Input タブのImageサブパネル)はgenerate-tab.jsではなくcomfyui-editor.jsが担当しており、analysis.load_image_nodesに載る全LoadImageノード(1個に限らず)に対して個別カードを描画する汎用実装だったため、Qwen Image 2.1の複数参照画像編集(image_1・image_2をそれぞれ別のLoadImageで接続したワークフロー)もコード変更なしでそのまま動作した——ユーザー提供のI2Iテストワークフローで実機確認済み。

How to apply: 新しい「1ノードでprompt+negative_prompt+複数画像入力」型の text-encoder ノードをGenerateUIに対応させる際は、まずanalyzeWorkflow()のprompt_nodes検出だけ確認すればよい場合が多い——モデルロード・サンプラー・LoadImageカードは大抵ノードタイプに依存しない汎用ロジックが既にカバーしている。画像アップロードUIを探すときはgenerate-tab.jsではなくcomfyui-editor.jsを見ること(両者は別ファイル)。

関連: project_v071_unsloth_ssrf_fix_and_outer_gitignore


v0.7.2(2026-09-18)

Workflowタブにソート機能追加(Date/Name 昇順降順)

Workflowタブのグループフィルター右隣に、Galleryタブと同じパターンのソートドロップダウン(Date Newest/Oldest、Name A-Z/Z-A)を追加した。wf.mtime(バックエンドが既に返していたファイル更新時刻)を使ったクライアント側ソートで、選択状態はlocalStorage(wfm_workflow_sort)に永続化する。

変更ファイル: templates/index.html(#wfm-sort select追加)、static/js/workflow-tab.js(sortWorkflows()追加、renderGrid()でfilterWorkflows()の結果に適用)

Videoタブ左ペイン幅調整・Editトリムパネルのバグ修正とUI調整

左ペイン幅: PlanサブタブのフォームパネルをGenerateUI寄りの380px固定から253px(約2/3)に縮小し、中央プレビュー/タイムラインを広げた(.wfm-video-form-panelのwidthのみの変更、flex:1の中央パネルが自動で広がる)。

「現在位置を使用」ボタンでトリムスクラバーの見た目が更新されないバグを修正: トリムのStart/End数値入力とスクラバー(ドラッグハンドル・ハイライト範囲)は同じcommit()関数を経由する設計だが、commit()はスクラバー自身のupdateRange()(ハンドル位置・ハイライト帯の再描画)を呼んでいなかった。ハンドルをドラッグした場合のみ呼び出し元が直接updateRange()を叩いていたため気付かれておらず、数値入力や「現在位置を使用」ボタンではclipのtrimStart/trimEndは正しく更新されるのにスクラバーの表示だけ古いままになっていた。_wireTrimScrubber()がupdateRange関数自体を返すようにし、_renderTrimPanel()側でcommit()から必ずそれを呼べるようにして解消。

トリムスクラバーのバッジ重なりを解消: 開始/終了/再生位置の時刻バッジ(ハンドルから浮き上がるラベル)がクリップ名やルーラー目盛りと重なる、また再生位置がトリム開始/終了に近いとStart/EndバッジとPlayheadバッジ同士が重なって読めなくなる不具合をユーザーが発見。クリップ名とスクラバーの間隔を拡大、スクラバー上部にバッジ専用の余白(padding-top)を確保、トラック高さを36px→52pxに拡大した上で、PlayheadバッジのみtopをStart/Endバッジより高い位置(2段目)にずらして重なりを回避した。

変更ファイル: static/css/video-tab.css(.wfm-video-form-panel, .wfm-video-trim-scrubber/-track/-badge/-badge-playhead)、static/js/video-edit-tab.js(_wireTrimScrubber()がupdateRangeを返すよう変更、_renderTrimPanel()のcommit()から呼び出し、クリップ名/トリム行の余白調整)

How to apply: 同じ状態変更を複数の入力経路(数値入力・ボタン・ドラッグ)から行える場合、各経路が同じ「見た目を再描画する関数」を実際に呼んでいるかを確認すること——1経路(ドラッグ)だけがたまたま直接呼んでいたために、他の経路のバグが長く気付かれずに残っていた。

Video Assetタブに動画/画像フィルタドロップダウン追加

Asset切替ドロップダウン(__Video Assets__/__Video Temp__/All Video Assets)の右隣に、幅を約半分にした動画/画像タイプフィルタ(All Types / Video Only / Image Only)を追加。既存のisVideoFile()判定を再利用してクライアント側でリストを絞り込む。

変更ファイル: templates/index.html(#wfm-video-asset-kind select追加)、static/js/video-asset-tab.js(_s.kindFilter追加、_filteredImages()で適用)

Video Edit プロジェクト永続化(Phase 5)とサイドバーProjectのPlan/Editモード切替

VIDEO_EDIT_TAB_PLAN.mdのPhase 5(永続化)を実装。他の未着手フェーズ(クロップ/テキストオーバーレイ/BGM合成)と比べ、video_plan_service.pyと同型のCRUDパターンをほぼそのまま流用できるため最軽量(見積もり1〜1.5日相当)と判断し、最初に着手した。

バックエンド: video_plan_service.pyと同じ形(パス安全性チェック付きlist/get/save/delete、updated_at自動付与)のVideoEditProjectServiceを新設し、video_edit_project/ディレクトリにws_videoeditproj_*.jsonとして保存する。video_plan_service.pyとの共通base化は行わず独立クラスのまま(両者のスキーマは無関係なので、無理に共通化しても行数が減るだけで得るものがないという既存のVideoPlanServiceのドキュメント方針を踏襲)。

保存対象: タイムラインのクリップ順序・トリム区間・各クリップが参照するアップロード済みサーバーファイル(serverRef)のみ。アップロード未完了のクリップは保存対象から除外する。

読込方式: 保存されたクリップをComfyUIの/viewエンドポイントからBlobとして再取得し、File化した上で既存の「クリップ追加」経路(addClipFromFile()→アップロード→probe)にそのまま通してから、保存済みのトリム値を上書き適用する方式にした。serverRefとメタデータを信頼してプレビュー生成ロジックを別に書く(再アップロードを省く)選択肢もあったが、既存の十分テスト済みの1経路だけを通ることを優先し、実装量とリスクを抑えた。

サイドバーProjectタブの切替: 中央のPlan/Editどちらのサブタブがアクティブかをvideo-tab.jsが_centerSubtabModeとして記憶し、サイドバーのProjectパネルを開く/切り替えるたびにそのモードを渡すようにした。video-project-tab.jsは元々Video Plan専用だったリスト表示・削除ロジックを、モードごとの設定テーブル(取得/削除エンドポイント、開く関数、件数ラベル等)に切り出して汎用化。Editモードでのアイテムクリックは新設したvideo-edit-tab.jsのopenSavedVideoEditProject()(video-plan-tab.jsのopenSavedVideoPlan()と同型)に委譲する。

検証: 全JS/Pythonファイルをnode --check/py_compileで構文確認、開発元リポジトリと実行時custom_nodesフォルダ間でファイルのdiffが空になることを確認して同期。Python側(新規サービス・ルート追加)はComfyUI完全再起動が必須(インポートは起動時一度きりのため)である旨をユーザーに案内済み。

変更ファイル: py/services/video_edit_project_service.py(新規)、py/config.py(VIDEO_EDIT_PROJECT_DIR)、py/routes/video_routes.py(/api/wfm/video/edit/projects系4エンドポイント)、static/js/video-edit-tab.js(_buildProjectData()/_saveProject()/_restoreClipFromSaved()/_loadProjectData()/openSavedVideoEditProject()、Save/Save As/Loadボタン配線)、static/js/video-project-tab.js(_MODES設定テーブルによるPlan/Edit汎用化)、static/js/video-tab.js(_centerSubtabMode追跡)、templates/index.html(Save/Save As/Load UI)、static/js/i18n.js(新規メッセージキー・ヘルプ本文更新、英/日/中3言語)

How to apply: 新機能の永続化を設計する際、対象データを直接シリアライズしてサーバー参照込みで復元しようとすると(プレビュー生成・probe結果の整合性など)別経路のロジックを新たに書く必要が出やすい。既存の「新規追加」経路がテスト済みで十分軽ければ、保存データを最小限(サーバー参照+差分パラメータ)に絞り、読込時はその経路をそのまま再利用する方が実装・保守コストが低い。

関連: project_video_edit_tab_feature_branch


v0.7.1(2026-09-17)

セキュリティ修正: Tagger VLM の Unsloth 経路でAPIキーがSSRFにより外部送信され得る不備を修正

セキュリティレビューで、Unsloth Desktop連携の2つの経路のうちAI TOOLタブ用プロキシ(unsloth_routes.py)にはリクエスト先ホストをlocalhost/127.0.0.1/::1に制限するallowlistが入っていたが、Taggerタブ VLM経路(tagger_service.pyのvlm_models/vlm_predict、backend=="unsloth"時)には同等の検証が無く、クライアントがリクエストごとに指定するapi_urlへそのままAuthorization: Bearer <UNSLOTH_API_KEY>を送信してしまう実装になっていた。ComfyUIを--listen 0.0.0.0等で外部公開している環境では、設定変更なしにこの経路経由でAPIキーが任意の外部ホストへ流出し得る。unsloth_routes.pyと同じallowlistロジックをtagger_service.py側にも追加し、loopback以外のapi_urlはキーを付与せずエラーを返すようにした。

副次的に、G'MIC-Qt起動(gmic_routes.py)のgmic_qt_path検証も、存在チェックのみだったのを絶対パス・.exe拡張子・実ファイルであることの検証に強化した(shell=Trueは使用しておらずコマンドインジェクションではないが、外部公開環境で設定変更と組み合わさった際のローカル実行リスクを下げる防御的対応)。

How to apply: 同一サービス(Unslothなど認証キーを要する外部API)へ複数の呼び出し経路がある場合、ホスト制限などのセキュリティ検証はルート/サービス単位ではなく呼び出し経路ごとに漏れなく入れる必要がある。片方だけ実装して安心しないこと。

v0.7.0(2026-09-17)

プレビュー枠の高さ固定・AssetサブタブをPlan/Editと並ぶ中央下部へ移設・トリムパネルにClipchamp風スクラバー追加・Frame保存のVideo Assets登録とAsset→Plan画像連携

Edit サブタブMVP実装後、実際にユーザーが使う中で見つかった調整要望をまとめて対応した。

プレビュー枠の高さ固定: 中央ペインのSource/Resultプレビュー枠がmin-height指定のみだったため、動画ソースのネイティブサイズ(特に縦長)によって枠が伸縮し、下のPlan/Editサブタブの表示位置がソースを選ぶたびに動いてしまっていた。枠をheight:260px固定にし、動画/画像側はwidth/height:auto; max-width/max-height:100%(ネイティブサイズが枠より大きい場合のみ縮小、小さい場合は拡大しない)に変更して解消。

AssetサブタブをPlan/Editと並ぶ中央下部の第3サブタブへ移設: 左サイドバーにあったAsset一覧・詳細パネルを中央ペイン下部(Plan/Editと同じ場所)へ移設し、左=一覧(サムネイル/テーブル切替可)・右=選択ファイルの詳細という2カラムレイアウトに再構成。一覧上部にグループ切替・ファイル名検索・表示切替(▦グリッド/☰テーブル)ボタンを追加した。左サイドバーはProjectのみのシンプルなパネルになった。

トリムパネルにClipchamp風ビジュアルスクラバーを追加: 動画クリップのトリム編集がテキスト入力(開始/終了秒)のみだったのを、時間目盛り(ルーラー)・SourceプレビューのcurrentTimeと同期するドラッグ可能な再生ヘッド・時刻バッジ付きのドラッグ可能なイン/アウトハンドルを備えたビジュアルなスクラバーに拡張した。全位置をclip.duration基準の%指定にすることでpx/秒換算の管理を持たずに済ませている。既存のStart/End数値入力・「現在位置を使用」ボタンとは同じcommit関数を通して連動する。ハンドルドラッグ中は_renderTimeline()の再描画を避け、pointerup時のみ確定してパフォーマンスに配慮。playheadとトリムハンドルが同じ位置に重なってもハンドル操作を優先できるようz-indexを調整した。

Frame保存のVideo Assets登録とAsset→Plan画像連携: 右ペインFrameタブの「Save to Output」に「Add to Video Assets group」チェックボックスを追加し、チェック時は保存したフレームをGalleryの__Video Assets__グループへ自動登録できるようにした。またAssetタブの画像アセット詳細に「Set as First Image」「Set as Last Image」ボタンを追加し、PlanタブのFirst/Last Imageへ直接画像をアップロードできるようにした(動画クリップでは非表示、選択中のPlanブロックへ反映後Planサブタブへ自動切り替え)。

検証: いずれもKaptureで実機ComfyUI_5(ポート8189)に接続し、正方形/縦長動画でのプレビュー枠固定確認、Asset新レイアウトでのグリッド/テーブル切替・検索・選択、トリムスクラバーのplayhead同期・トラッククリックシーク・ハンドルドラッグ(playhead重なり時含む)・複数クリップ切替時のリスナー管理、Frame保存後のGalleryグループ登録をAPI経由で確認、Asset画像からFirst/Last Imageへの反映まで一通り実機確認済み。

How to apply: プレビュー枠のようにコンテンツ依存でサイズが変わる要素が、隣接するUIの表示位置に影響する場合は、コンテンツ側ではなく枠側を固定サイズにしてコンテンツを内側で縮小表示する方が安全(project_video_edit_tab_feature_branch)。ドラッグ可能なUI要素同士が同じ座標に重なる可能性がある場合は、操作頻度の高い方(今回はトリムハンドル)のz-indexを優先し、もう一方の当たり判定に埋もれないようにする。

関連: project_video_edit_tab_feature_branch

Video タブ Edit サブタブ: 簡易動画編集機能(クリップタイムライン・トリム・結合・書き出し)を新規実装

これまで「Editing tools coming soon」のプレースホルダーのみだったVideoタブのEditサブタブに、Adobe Premiereライクな簡易動画編集機能を実装した。詳細設計・調査結果はVIDEO_EDIT_TAB_PLAN.mdにまとめてある。

MVP機能: クリップのタイムライン配置(左詰め、実秒数×固定px/秒スケール)、トリム(開始/終了秒)、複数クリップの結合書き出し、タイムライン全体の疑似連続プレビュー、静止画/アニメーション画像クリップ(表示時間指定)、Clear/複製/削除/並べ替え(◀▶ボタンおよびネイティブドラッグ&ドロップ)。新規ファイルstatic/js/video-edit-tab.jsが中心実装。

Phase 0実機検証での重要な発見(当初の設計を訂正):

  • ComfyUI Core 0.35〜0.36にはVideoSliceという名前のノードは存在せず、正しいclass_typeは"Video Slice"(スペース込み)。VideoTrim(experimental)のtrim入力はフラット値{"start_time":..}を渡すとエラーにならず黙って何もトリムしない(.get("trim")で一段余計にネストを剥がす実装のため)。実際に出力duration をprobeして初めて発覚し、「execution_successだけでは検証不十分」という教訓を得た。
  • ConcatenateVideoのAutogrow入力videosは、ネストしたJSONオブジェクトではなく"videos.video0"のようなドット結合フラットキーがinputsのトップレベルに必要(ComfyUI Core Autogrow.TemplatePrefixのドット結合ロジックに由来)。
  • ConcatenateVideoは解像度不一致で実行時ハードエラーになるため、クライアント側で事前チェックし、動画クリップ同士の不一致は書き出しをブロック、画像クリップはImageScale(crop=center)で自動フィットして回避。
  • 動画クリップ(音声あり)と画像由来クリップ(無音)を結合するとConcatenateVideoが音声レイアウト不一致でエラーになる。複数クリップ結合時のみ、全クリップをGetVideoComponents→CreateVideoで無音統一する対策を実装(単一クリップ書き出しは音声を保持。複数クリップ結合時は音声が失われるのが既知の限界)。

静止画/アニメーション画像クリップ対応: 既存のAsset一覧(__Video Assets__/__Video Temp__グループ)には元々ビデオ専用フィルタが無く画像も一覧されていたが、プレビューが常に<video>要素前提で画像では何も表示されなかった。video-preview.jsのSource/Resultプレビュー枠に<img>コンパニオン要素を追加し、mediaType引数で切り替えるよう拡張。画像クリップはLoadImage→(必要時)ImageScale→RepeatImageBatch(amount=表示秒数×24fps)→CreateVideoで正確な長さの動画セグメントに変換して書き出しグラフに組み込む。

UI設計はPlanサブタブに合わせて再設計: 当初は縦リスト+各行ボタンだったが、ユーザー指示によりPlanタブの横トラック+共有ツールバー方式(◀▶/複製/削除/クリアが選択中クリップに対して働く)に変更。タイムライン自体はPlanのブロックのようにflex-growで幅一杯に引き伸ばさず、実秒数の固定幅で左詰め(空きトラックはそのまま空く)。この過程で、Edit実装以前の「Editing tools coming soon」プレースホルダー時代の重複CSSルール(align-items:center等)が残存しており、実装後もタイムライン全体が中央に縮んで表示される不具合を発見・修正した。

タイムライン連続プレビュー: 新規<video>要素を追加せず、Export結果が最終的に書き出される「生成された動画」(Result)プレビュー枠を共有し、全クリップ(トリム区間反映)を順番に連続再生する機能を追加(「▶ プレビュー / ■ 停止」トグル)。ユーザー要望「このプレビューの動画=書き出しされる動画」に対応。

検証: 全JS/Pythonファイルをnode --check/構文チェックで確認。開発元リポジトリと実行時custom_nodesは別実体のため、変更のたびに両フォルダへ同期しmd5ハッシュ比較で完全一致を確認。Kapture(ブラウザDevTools連携)で実機ComfyUI_5(ポート8189)に接続し、クリップ追加・トリム・並べ替え(ドラッグ含む)・複製・削除・クリア・連続プレビュー・単一/複数クリップ書き出し・静止画クリップの書き出し・動画+画像混在結合を一通り実機確認済み。

How to apply: ComfyUI Coreの新しいノード(特にexperimental扱いのもの)をAPIプロンプト形式で直接呼び出す際は、/object_infoのクラス名・入力スキーマを鵜呑みにせず、実際に/promptで実行して出力を実測するまで検証を終えたとみなさない——execution_successは型検証を通っただけで、パラメータが意図通り効いたかは別問題(今回VideoTrimのトリムが黙って無視される形で顕在化)。Autogrow(COMFY_AUTOGROW_V3)型の動的入力は、フラットなドット結合キー("prefix{name}"を.区切りでinputsトップレベルに並べる)で渡す。

関連: project_dev_deploy_sync

v0.6.3

Send CCボタンでMP4動画をComic Creatorの新設「動画ツール」へ送れるように対応

Comic Creator(eagle_comic_creator_spa/comfyui-comic-creator、別リポジトリ)側にレイアウトタブの新機能「動画ツール」(MP4をコマに配置して再生・停止・音量/ミュート調整・指定フレームでの静止画キャプチャができる実験的機能)が追加されたのに伴い、Galleryタブの「Send CC」ボタンをこれに対応させた。

問題: 既存のsendCcBtnクリックハンドラ(gallery-tab.js)は、選択中ファイルのURLをwindow.parent.insertImageFromUrl()(レイアウトタブ宛て)またはwindow.parent._ccImageTab.loadFromUrl()(Imageタブ宛て)へ渡すだけで、.mp4を判別する分岐が無かった。Comic Creator側の受け口は画像専用ロジックしか持たないため、動画を選んでSend CCを押しても正しく処理されなかった。

対応: gallery-tab.jsに既にあったisVideoFile()ヘルパー(拡張子判定、Video Assetsグループのバッジ表示等で使用中)で選択中ファイルが動画かどうかを判定し、レイアウトタブ宛て(デフォルト経路)で動画の場合はComic Creator側が今回新設した受け口window.parent.insertVideoFromUrl(url, filename)を呼ぶよう分岐した。Imageタブ宛て(_ccI2ITargetMode === 'image')で動画を選んだ場合は、Comic Creator側が今回Imageタブの動画編集には対応していないため、トースト(新規i18nキーgalleryVideoNotSupportedInImageTab、英/日/中3言語)で案内して送信を中断するようにした。

検証: 双方の変更点を構文チェック(node --check)のみで確認。ComfyUI実機でのSend CC往復動作(Gallery側でmp4選択→送信→Comic Creator側でのオーバーレイ表示・再生確認)はユーザー側での実機確認待ち。

How to apply: Comic Creator側とWorkflow Studio側は別リポジトリだが、Send CC・Send to LI nodeのようなwindow.parent/window.opener経由のクロスウィンドウ連携APIは、どちらか一方だけを変更すると壊れる。片方(今回はComic Creator側)に新しい受け口(insertVideoFromUrl)を追加したら、呼び出し元であるこちらのGalleryタブ側も同時に更新する必要がある。

v0.6.2

Image Editタブにペンタブレット筆圧対応とFillツール(バケツ塗りつぶし)を追加

ユーザーから「Comic CreatorのImageタブ同様にこちらのImage Editタブをペンタブレット対応させたい」という依頼を起点に、Draw/Maskツールの筆圧対応、Fillツール(バケツ塗りつぶし)の移植、そして実機フィードバックを受けた追加のペンタブレット不具合修正まで一連の作業を行った。

Draw/Maskツールの筆圧対応: image-edit-tab.jsのキャンバスイベントをmousedown/mousemove/mouseup/mouseleaveからpointerdown/pointermove/pointerup/pointerleaveに変更し、両canvasにtouch-action: noneを設定。PointerEvent.pressure(ペン=実際の筆圧、マウス=ボタン押下中は常に0.5)をDrawTool/MaskToolのonMouseDown/onMouseMoveに伝播させ、両ツールにpressureEnabled(既定ON)を追加——筆圧に応じてブラシサイズ(0.3〜1.7倍)とDraw側は不透明度(0.4〜1.6倍)を可変化する。pressure=0.5(マウス)では従来と同じ1.0倍になるため、マウス操作の挙動は変わらない。実装はComic Creator(eagle_comic_creator_spa)のImageタブで先行実装済みだった筆圧対応パターンをそのまま移植した。

Fillツール(バケツ塗りつぶし)を新規追加: Comic CreatorのImageタブにあったFillTool.js(スキャンライン方式のフラッドフィル、Solid/Gradient(線形・放射状)両対応、particle_engine.js由来のグラデーション評価ロジック)をそのまま移植し、image-edit-tab.jsにツール切り替え・アクティブ化・クリック処理・プロパティパネル(Mode/Color/Ramp/Direction dial/Tolerance/Opacity)を追加、ショートカットGを割り当てた。ツールバーのボタンはtemplates/index.htmlに静的HTMLとして定義されていたため個別追加が必要だった(TOOL_DEFS配列だけでは表示されない)。

追加のペンタブレット不具合修正(ユーザー実機フィードバック): Fillツールのグラデーション設定UI——色ストップを並べた「Ramp」(ドラッグでストップ位置を変更)と方向・強度を指定する「Direction」ミニダイヤル——がペンタブレットでスライド操作できないという報告を受けた。原因はこの2つのミニウィジェットのドラッグ処理だけがmousedown/mousemove/mouseupのまま残っていたこと(touch-action未指定でブラウザのパン/スクロールジェスチャに奪われやすく、documentに張ったmousemoveはポインタキャプチャが無いため速いペンの動きを取りこぼしやすい)。_setupFillGradientRamp()/_setupFillGradientDir()をpointerdown/pointermove/pointerup/pointercancelに変更し、canvas.style.touchAction = "none"とcanvas.setPointerCapture(e.pointerId)を追加、ドラッグ中のリスナーもdocumentではなくcanvas自身に張るよう変更した。

Comic Creator側も同じ不具合を横展開で修正: Fillツールの移植元であるComic Creator(image-tab.js)にも同一パターンの_setupFillGradientRamp()/_setupFillGradientDir()に加え、Shapeツールの塗りグラデーションランプ_setupShapeGradRamp()(同一コードパターンの複製)が存在し、同じ理由でペンタブレット操作ができない状態だった。3箇所とも同じPointer Events化を適用して修正した。

検証: Kaptureで実ブラウザ(ComfyUI_5上のWorkflow Studio /wfm、Comic Creator /ccc)を操作し、Draw/Mask/Fillそれぞれで新規キャンバス作成→クリック描画/塗りつぶし→コンソールエラーなし・レイヤーサムネイル即時更新を確認。Ramp/Directionダイヤルもクリックでのストップ選択・ハイライト表示が正しく動作することを確認した(ペン特有の筆圧・ドラッグ挙動そのものは自動化ツールでは検証できないため、筆圧の効き具合とドラッグ操作は実機ユーザー確認による)。Comic Creator側は開発フォルダがComfyUI custom_nodes配下にシンボリックリンクされているため、コピー不要で編集がそのまま反映される構成だった(Workflow Studio側は開発リポジトリと実行時custom_nodesが別実体のため、変更のたびに手動コピーが必要)。

How to apply: キャンバスベースの描画・ドラッグ操作を実装する際は、最初からmousedown/mousemove/mouseupではなくpointerdown/pointermove/pointerup/pointercancel(Pointer Events)を使い、touch-action: noneを設定する——マウス専用に見えるミニウィジェット(カラーランプ、方向ダイヤル等)であっても、後からペンタブレットで使われた際に同じ不具合が起きる。ドラッグ中のmove/upリスナーはdocumentではなく対象のcanvas自身に張り、setPointerCapture()を使うと要素境界を越えた高速な動きでも取りこぼさない。同一パターンのコードが複数箇所(ファイル間・同一ファイル内)に複製されている場合、1箇所の不具合報告を機に全箇所を横展開で確認する。

関連: project_v062_fill_tool_pentablet, feedback_realtime_debug_logs_and_kapture, project_comic_creator_deploy_symlink

v0.6.1

Galleryタブの検索が「読み込み中」のまま止まりクリアでも中断できない不具合、および50枚程度のフォルダでも数秒〜十数秒かかる不具合を修正

ユーザーから「ギャラリータブで検索を行うと読み込み中のままでクリアを行っても中断できない。50枚程度のフォルダで検索をかけても時間がかかっている」という報告を受け、原因調査から修正まで対応した。

フロントエンドの2つの不具合(gallery-tab.js): 検索欄はキー入力のたびに即座に(debounceなしで)loadImages()を呼んでおり、しかも直前のfetchをキャンセルする仕組み(AbortController)が無かった。そのため「クリア」を押した直後でも、その前に打っていた検索文字列の(まだ処理中の)リクエストが後から返ってきて、クリア後の空検索の結果を上書きしてしまい、「読み込み中のまま/検索結果のまま」に見えていた。検索入力に300msのdebounceを追加し、loadImages()にAbortController管理を追加(新しいリクエスト発行時に前回の未完了リクエストを中断)、「クリア」「Clear filters」ボタンはdebounceタイマーもクリアして即座に反映するようにした。

バックエンドのロック競合(gallery_metadata.py, gallery_service.py): フォルダを開くたびに起動するバックグラウンドインデックス処理が、gallery_metadata.json(画像数が多い環境では1MBを超える)全体をthreading.RLock保持したままJSONシリアライズ+ディスク書き込みしており、その間ずっと検索リクエストのget()呼び出しがロック待ちでブロックされていた。さらに10枚処理するごとに1件ずつsave()を呼んでいたため、実質10回分の全体書き込みが発生していた。_save_to_disk()をロック内ではデータのdeepcopyのみに留め、実際のファイルI/Oは別の書き込み専用ロック(_write_lock)でロック外に出すよう変更し、バックグラウンドインデックス側もまとめて1回で保存するsave_batch()を新設して使うようにした。また検索時は(該当時に全メタデータのJSON全体書き込みを伴う)cleanup_stale_images()をスキップするようにした(通常の検索なしロード時に実行されれば十分なため)。

根本原因: ponyxlWildcardsVaultフォールバック探索でのVaultフォルダ誤検出(gallery_service.py): 上記2件を直しても実機で53枚のフォルダへの検索が約3秒かかっており、一時的な計測ログを実機に仕込んで原因を特定した。出力ルート直下にComfyUI/StabilityMatrixが作る空のthumbnailsフォルダがたまたま存在しており、これが検索対象画像の.txtサイドカーが無い場合のフォールバックである_find_vault_category_root()(ponyxlWildcardsVault形式——「直下にthumbnails(_option2)フォルダを持つ祖先ディレクトリ」というゆるい条件で判定)によって誤ってVaultのcategory_rootと認識されていた。誤検出されたcategory_root(1万枚超のファイルを含む出力ルート)に対し、_load_vault_leaves()がキャッシュの妥当性確認のため画像1枚ごとにglob("*.yaml")+glob("*.yml")を実行しており、1回あたり約53msかかるglobを53回(検索対象の画像数分)実行して合計2.8秒——実測ベンチマークで再現・確認した数値と実機ログの数値がほぼ一致した。_load_vault_leaves()をTTL(60秒)ベースのキャッシュに変更し、TTL内はglobを省略するようにして解消した。

検証: Kaptureで実ブラウザ(ComfyUI_5上のWorkflow Studio)を操作し、ネットワークリクエストのタイミングを実測しながら段階的に検証した。Python変更後の再起動が確実に反映されているかはプロセス起動時刻とファイル更新時刻を比較して都度確認した。最終的に53枚のフォルダへの検索は3327ms→74msまで短縮し、検索・クリアとも実機で即座に反映されることを確認した。デバッグ用の計測ログは検証完了後に削除済み。

How to apply: サブフォルダを持つ画像出力ルートで「Vault風フォルダ名(thumbnails等)の存在だけで判定するフォールバック検出」を書くと、無関係な巨大フォルダを誤検出してglob/scandir系の操作が本来の対象より何桁も遅くなることがある——検出ロジック自体だけでなく、誤検出時に何度も高コストな操作(特に大量ファイルを含むディレクトリへのglob)が繰り返し呼ばれないか、呼び出し頻度とキャッシュの妥当性も含めて確認する。原因不明のパフォーマンス低下は仮説で止めず、実機に一時的な計測ログ(perf_counterで区間ごとの時間をlogger.warningに出力)を仕込んで実測すると、コードレビューだけでは気づけない誤検出のような盲点が見つかることがある。

関連: feedback_asyncio_blocking_in_aiohttp_routes, feedback_realtime_debug_logs_and_kapture, feedback_verify_restart_via_process_creation_time

v0.6.0

Gallery/Image Edit両タブにPSD(Photoshop)対応を追加 — 複数画像の一括PSD書き出し、レイヤー保存/読み込み、Gallery表示、Image Editのレイヤーパネル改善

ユーザーから「ギャラリーで複数画像を選択してPSDファイルを作成したい」という依頼を起点に、段階的に機能を積み上げてPSD対応を一式実装した。

ライブラリ選定の紆余曲折: 当初はPythonでPSDを書き込めるライブラリとしてpytoshopを候補に選定したが、実際にインストールを試すとWindows用のプリビルドwheelが存在せず、packbitsというC拡張のビルドにcl.exe(Visual Studio Build Tools)を要求することが発覚した。StabilityMatrix/ComfyUI Portable環境のユーザーは通常Cコンパイラを持たないため、この依存はほぼ確実にインストール失敗を招く。代わりにpsd-tools>=1.10を採用した——1.10以降は読み取り専用ではなくPSDImage.frompil()/PixelLayer.frompil()による書き込みにも対応しており、依存(attrs, numpy, Pillow, typing-extensions)は全てプリビルドwheelを持ちCコンパイラ不要。実機と同等の条件(venv構築→pip install→実際にPSD作成→再読み込みで往復検証)でレイヤー構成・重なり順・opacity・blend_mode・visibleが正しく機能することを確認してから実装に着手した。

Gallery: 複数選択→PSD一括書き出し — Galleryの一括操作バーに「PSD作成」ボタンを追加。選択した複数画像を、既存のZIPエクスポート機能と同じ選択・送信パターンを踏襲して1つのPSDファイル(各画像=1レイヤー、レイヤー名=ファイル名)にまとめてダウンロードする。画像サイズが異なる場合は最大サイズのキャンバスに左上基準で配置する仕様とした(ユーザー選定)。バックエンドはGalleryService.build_psd_from_images()、ルートはPOST /wfm/gallery/images/export-psd。

Image Edit: Save PSD / Open PSD — Image Editタブ自体にも「Save PSD」(アクションバー)を追加。LayerManager.layersを背面→前面順に走査し、各レイヤーをキャンバス全体サイズのoffscreen canvasにLayer.applyTransform()(回転・反転・拡縮)のみベイクして書き出す——PSDのピクセルレイヤーは矩形しか持てず回転角度を保持できないため、位置・変形は必ずピクセルに焼き込む一方、opacity・blend_mode・visibleは実際のPSDレイヤー属性としてそのまま渡し、Photoshop側で編集可能な状態を保つ。maskApply=trueのクリッピングマスクは対象レイヤーと合成して1枚にベイクする簡略化とし、PSD本来のレイヤーマスク機構は使わない(「ライブラリで素直に対応できない部分は無視してよい」というユーザーの了承のもとでの意図的な妥協)。続けて逆方向の「Open PSD」も実装——psd_tools.PSDImage.open()→psd.descendants()で全レイヤーを再帰的に走査し、layer.topil()でレイヤーのbbox範囲にクロップ済みの実サイズ・実位置の画像を復元、layer.bbox/opacity/blend_mode/visibleをLayerオブジェクトに変換する。グループ自体(フォルダ)はスキップしつつ中身は再帰的に取り込み、PSD側のブレンドモード(4バイトタグ)とCanvas2DのglobalCompositeOperation文字列の相互変換テーブルを新設した(Canvas2Dに対応物が無いモードはNormalにフォールバック)。

Gallery → Image Edit: 複数画像を一括レイヤー転送 — Galleryの一括操作バーに「レイヤーとして編集」ボタンを追加。選択した複数画像(mp4は自動除外)でImage Editタブに切り替え、各画像を独立レイヤーとして展開する。既存の単一画像版「Image Edit」送信ボタンと同じwindow._wfmImageEditTabグローバル参照の仕組みを再利用し、PSDファイルを経由しない直接URL読み込み方式(loadLayersFromUrls()新設)とした——各URLをnew Image()で並列ロードし、最大幅・高さをキャンバスサイズとして採用、fitToCanvas()(既存ヘルパー)で各画像をアスペクト比を保ったまま中央配置する。

Gallery: PSDファイルの一覧・サムネイル・プレビュー表示 — .psdをIMAGE_EXTENSIONSに追加し、Galleryのフォルダ一覧・検索・グループ機能に自動的に乗るようにした。ブラウザは<img>でPSDを直接レンダリングできないため、serve_image/serve_thumbルートで.psdの場合はpsd_toolsで全レイヤーを合成(非表示レイヤーは除外)した上でPNG/JPEGに変換し、mtimeベースのキャッシュキーでディスクキャッシュして配信する方式にした。情報パネルの幅・高さもPSDヘッダーから取得するようにした。

実機テストで発覚した2件のバグとその修正:

  1. サムネイル表示が遅く、他の無関係なPNGサムネイルまで真っ黒になる——原因はserve_image/serve_thumbルートがPSD合成という重いCPU処理をasync defハンドラ内で直接同期実行しており、aiohttpの単一スレッドイベントループ全体をブロックしていたこと。PSD1枚の合成中は他の並行リクエスト(別画像のサムネイル配信など)も完全に止まり、結果的に無関係な画像まで表示が固まって見えた。serve_image・serve_thumb・import_psd_layers・新設get_psd_layersの4ルートをloop.run_in_executor(None, ...)でデフォルトのThreadPoolExecutorに逃がすことで解消した。この教訓は今後の重い処理全般に適用すべき一般原則として別途メモリに記録した。
  2. 単一画像「Image Edit」送信ボタンでPSDを開くと1レイヤーしかない(「Open PSD」ボタンでは正しく全レイヤー開ける)——原因はこのボタンが常にloadFromUrl()(1枚のフラット画像として読み込む経路)を使っており、PSD専用のレイヤー分解経路を通っていなかったこと。サーバー上のファイルを再アップロードせず直接レイヤー分解できる新規GET /wfm/gallery/image/psd-layers(GalleryService.get_psd_layers_from_path())を追加し、既存のimport_psd_layers()とロジックを_extract_psd_layers()に共通化した上で、PSDファイルの場合はこの新エンドポイントを使うよう分岐させた。

Image Editタブのアクションバー・レイヤーパネル改善: 「Upload」を「File Open」に改名し「Open PSD」をその右隣に移動;新規「Close」ボタン(確認ダイアログ後、現在のドキュメントを破棄してプレースホルダー表示に戻す)を追加;レイヤーパネルヘッダーを2段構成(1段目=+/⧉(複製)/−/↑/↓、2段目=「Mask」ボタン単独、旧「M」から改名・移動)に変更;新規「複製レイヤー」ボタン(⧉)を追加し、Layer.toJSON()→Layer.fromJSON()のシリアライズ往復でアクティブレイヤーを複製(id再採番)して元レイヤーの直前に挿入する方式とした。実装にあたり、レイヤーリストクリック時の「選択」ロジック(種別・現在ツールに応じたキャンバス同期)を_activateLayerById()という共通メソッドに抽出し、複製直後の自動選択でも再利用することで、レイヤー種別(画像・描画・マスク・テキスト)を問わず正しく動作するようにした。

ヘルプドキュメント更新: 上記全機能についてヘルプタブを更新(templates/index.html + i18n.js英日中3言語 + app.jsのhelpIdMapの3点セット)。更新作業の検証中に、今回の変更とは無関係の既存バグを2件発見し合わせて修正した——Image Editの「タイプアイコン」説明が英語版で重複定義されておりマスクレイヤーの表記が欠けていた点(片方は死んだ定義だったため削除)、日本語版でStyle Catalogの「Load in GenerateUI」項目が丸ごと未翻訳で英語表示にフォールバックしていた点(翻訳を追加)。

検証: PSD書き込み/読み込みロジックは全て、実際のGalleryServiceコード(相対importを差し替えたコピーをテスト用venvで直接実行)に対してテストスクリプトで検証した——複数レイヤー・サイズ不一致・非表示レイヤー・opacity/blend_mode・グループ内レイヤー・クリッピングマスクを含むPSDを実際に構築し、書き出し→再読み込みで往復一致することを確認。Gallery一括PSD書き出し・Image Edit Save/Open PSD・Gallery表示・Gallery→Image Editレイヤー転送・アクションバー/レイヤーパネル改善は全てユーザーが実機(実ComfyUI環境)で動作確認済み。

How to apply: Cコンパイラを要求するPythonライブラリはWindows向けComfyUIカスタムノードでは避け、pip install --only-binary=:all: <lib>でプリビルドwheelの有無を先に確認してから採用する。aiohttpのasync defルート内でCPU負荷の高い同期処理(画像デコード・合成・リサイズ等)を直接呼ばず、loop.run_in_executor()でスレッドプールに逃がす——特にサムネイル/プレビューのように頻繁に並行して呼ばれるエンドポイントは、1回の遅延が他の全リクエストに波及するため重要。ヘルプ更新はindex.htmlだけでなくi18n.js(3言語)とapp.jsのhelpIdMapの3点セットで行う。

関連: project_gallery_psd_export, project_gallery_psd_display, project_gallery_to_image_edit_layers, project_v04x_image_edit_ui_polish, feedback_psd_export_library_choice, feedback_asyncio_blocking_in_aiohttp_routes

v0.5.8

Tagger DBタブに一括削除機能を追加(外部コントリビューターPRのレビュー・検証・修正・マージ)

GitHub上の外部コントリビューター(censor-ed氏、fork経由)からPR #2「Tagger DBの複数選択削除を追加」が届いたため、内容確認・実機検証・マージまで一貫して対応した。PRの変更内容はバックエンド(tagger_routes.pyにDELETE /wfm/tagger/dbエンドポイントを新設、tagger_db_service.pyにdelete_bulk()をプレースホルダ経由のSQLite DELETE ... WHERE id IN (...)で実装)とフロントエンド(DBテーブルの各行にチェックボックス、ヘッダーに全選択チェックボックス、既存の単一削除_dbDeleteと同じconfirm()パターンを踏襲した確認ダイアログ、英日中3言語のi18n)。

コードレビューでは危険な処理(外部送信・eval・shell実行等)は無く、SQLインジェクション対策も適切と判断。ただし2点の懸念を指摘した:(1) 一括削除のcountがAPIレスポンスで返る削除件数の妥当性、(2) 全選択チェックボックス_dbToggleSelectionが単純トグル(クリックのたびに全チェックボックスの状態を反転)方式になっており、一部だけ選択済みの状態でクリックすると直感に反する(選択済み行が外れ未選択行が入る)挙動になる懸念。

実機検証はKapture経由でComfyUI_5(ポート8189)に実際に接続して実施。データベースには実データ(既存のタグ付け済み画像)しか存在しないため、削除操作自体は実行せず安全に検証する方針を取った:一括削除APIはcurlで存在しないID(999999)を渡して直接叩き、ids未指定時の400エラー・不正な値混入時の500エラーハンドリングを確認。UI側はKaptureで実際にチェックボックスを操作し、行クリックとチェックボックスクリックが正しく分離されていること、未選択で削除ボタンを押すと警告トーストが出ること、削除ボタンでconfirm()ダイアログ(日本語i18n表示)が開くことを確認した上でmcp__kapture__dialogのaccept:falseでキャンセルし、実データには一切触れずに検証を完了した。この過程で指摘(2)の懸念が実機で再現することを確認——ID66のみ選択した状態で全選択チェックボックスをクリックすると、ID66のチェックが外れ他の全行がチェックされるという実際の誤動作を確認した。またAPIレベルの検証で指摘(1)も実バグと確定:存在しないID 1件を渡しても{"ok": true, "count": 1}が返っていた。原因はhandle_db_delete_bulkがdelete_bulk()の戻り値(実際の削除件数=cur.rowcount)を使わず、渡されたidsの件数(len(ids))をそのまま返していたため。

この検証プロセス中に、ComfyUIの「再起動」がユーザー申告だけでは信頼できないケースに遭遇した。Pythonファイル(tagger_routes.py)をコピーしてComfyUI再起動を依頼したところ、依頼直後は新エンドポイントへのDELETEが405 Method Not Allowed(Allow: GET,HEADのみ)を返し続けた。原因調査でComfyUI起動ログ(user/comfyui_8189.log)やapp.routerのルート登録順序(ComfyUIコア自身のSPAフォールバック用static routeとの競合の可能性)を疑ったが、最終的にはPowerShellのGet-CimInstance Win32_Processでプロセスの実際のCreationDateを確認し、動いているComfyUIプロセスの起動時刻がコードをコピーした時刻より前であることを突き止めた——つまりユーザーが「再起動しました」と報告した操作は、ブラウザページのリロード等では反映されず、プロセス自体は起動されたままだった。ユーザーに再度プロセスの完全停止・起動を依頼し、新しいプロセス起動時刻(コピー時刻より後)を確認してから再テストしたところ405エラーは解消した。

2点のバグ修正(handle_db_delete_bulkがdelete_bulk()の戻り値をcountとして返すよう変更、_dbToggleSelectionをクリックされたヘッダーチェックボックス自身の新しい状態(e.target.checked)に全行を揃える一般的な「全選択」挙動に変更)をコミットし、maintainerCanModify権限を使ってPR元のフォークブランチ(censor-ed/ComfyUI-Workflow-Studioのfeature/tagger-bulk-delete)に直接pushしてPRへ反映、gh pr mergeでGitHub上のPR #2を正式にマージした(マージコミットe067f34)。ローカルmainをfast-forward更新し、実行時custom_nodesフォルダへ最終版を再同期してdiff --strip-trailing-crで完全一致を確認した上で、最後にもう一度ComfyUI再起動→APIレスポンス確認まで実施した。

検証: バックエンドAPI(curl直叩き、正常系・異常系)とフロントエンドUI(Kaptureブラウザ操作、チェックボックス・確認ダイアログ・トースト)の両方を実機で確認。修正コミット後も同様に再検証し、countが正しい値(0)を返すこと・全選択チェックボックスが選択済み行を含めて正しく全ON/全OFFされることを確認した。実データは一度も削除していない。

How to apply: 外部コントリビューターのPRを取り込む際は、まず実行時custom_nodesフォルダと開発リポジトリの現在のブランチが完全一致していること(改行コード差分のみ)を確認してから安全にファイルを上書きする(project_dev_deploy_sync)。DBやファイルシステムなど実データを持つ機能のUIテストでは、可能な限り「削除・変更操作自体は実行せず、確認ダイアログの表示とキャンセル」「存在しないIDでのAPI直接テスト」など非破壊的な手段で検証を組み立てる。Pythonファイル変更後のComfyUI再起動は、ユーザーの「再起動しました」という申告だけでなく、Get-CimInstance Win32_Process等でプロセスの実際の起動時刻を確認し、コード変更時刻より後であることを検証してから動作確認に進む——ブラウザのページリロードやタブの再接続だけでは、バックエンドのPythonプロセス自体は再起動されていない場合がある。APIレスポンスの件数系フィールド(count等)は、渡された引数の長さではなく実際の処理結果(DBのrowcount等)を返すべきというありがちなバグパターンに注意する。「全選択」チェックボックスは常時トグルではなく、クリックされた自身の新しい状態を全体に適用する実装が直感に合う。

関連: project_dev_deploy_sync, feedback_comfyui_running_check_before_launch, feedback_realtime_debug_logs_and_kapture

v0.5.7

設定エクスポート/フルバックアップのドキュメント修正: v0.5.5のVideo Plan機能追加分の言及漏れ

「Videoプランの追加で設定のエクスポート、バックアップ機能の更新が必要では」との確認依頼を受けて調査。v0.5.5で追加されたvideo_plan/ディレクトリ(Plan JSON+インデックスPNG、lab_plan/と同構造)が、フルバックアップZIP機能の説明文言から漏れていないか確認した。実装(_build_full_backup_zip)自体はDATA_DIR配下のトップレベルエントリを汎用的に走査する設計(除外リスト_FULL_BACKUP_EXCLUDE_NAMESはgmic_temp/thumb_cache/wildcardの3つのみ)のため、video_plan/は既存コードの変更なしに自動的にバックアップ対象へ含まれていた——つまり機能的な欠陥は無かった。一方、ユーザー向け説明文(fullBackupHint/helpSettings15のi18n.js3言語、README.mdのFull Backup説明、settings_routes.pyのコード内コメント)は「AI Skills、Lab Plans、Style presets」という列挙のままVideo Plan機能追加時に更新されておらず、実際にバックアップされるデータの説明として不正確になっていた。該当6箇所+READMEを「Video Plans」を含む形に修正。

Video Plan (Planサブタブ) 中央パネルの設定項目レイアウト刷新 + MiniMax H3 Turbo/LTX-2.5 Prompt Enhance/Wan2.2 Turbo対応

「Duration・Noise SeedをMultipleの右隣に移動し、その下に新しい行でWidth/Height/MiniMaxのturbo_mode/strength/steps・LTX-2.5のprompt_enhance・Wan2.2のenable_turbo_mode(MiniMaxのturbo_modeと兼用)を追加したい」との要望。着手前にComfyUI_5環境の直近保存ワークフロー(minimax_turbo.json、LTX-2.5のltx_25_test/test2/test3、Wan2.2のwan22_test/test2/test3_i)を実際にPythonでパースしてサブグラフの公開入力を調査し、対応可否を事前検証してから実装した。

調査で判明した構造: MiniMax H3はturbo_mode(BOOLEAN)/turbo_model_strength(FLOAT、LoRA強度)/turbo_steps(INT)をサブグラフの直接ウィジェットとして公開しており、これらがユーザーの言う「strength」「steps」に対応する。LTX-2.5は3バリアントともprompt_enhance(BOOLEAN)を公開。Wan2.2はenable_turbo_mode(BOOLEAN)を公開しており、MiniMaxのturbo_modeと意味的に同一のためUI上は1つの「Turbo Mode」欄として兼用する設計にした。width/heightはMiniMax・LTX-2.5のText/Image版ではResolutionSelector経由(サブグラフ入力がリンク接続されている)だが、LTX-2.5のFirst & Last Frame版とWan2.2は全バリアントでResolutionSelectorを持たずサブグラフの直接ウィジェット値のまま——既存コードのコメント通りの構造を実データで確認できた。noise_seedは全ファミリーでname="noise_seed"に統一されている(LTX-2.5のText to Videoのみlabel="seed"を持つため、ラベルではなくname直接一致で検索するヘルパーを新設)が、Wan2.2のText to Videoだけサブグラフの公開入力にnoise_seedが存在しない(Image to Videoにはある)という非対称性を発見した。

実装はvideo-workflow.jsのlocateVideoModelNodes()を拡張し、上記7項目(width/height直接指定用インデックス、noise_seed、turbo_mode、turbo_strength、turbo_steps、prompt_enhance)のインデックスを検出して返すようにした(対応する入力が無ければ-1を返し、呼び出し側で無効化・非ハイライトの判断に使う)。既存のAspect Ratio/Megapixels/Multipleと全く同じ「ワークフローが対応する場合のみ入力可能+ラベルハイライト」の仕組みで新規7項目も統一し、新しい選択UIを作らず既存のwfm-model-label-activeパターンを再利用した。noise_seedについては値の書き込みロジックをあえて追加しなかった——comfyUI.generate()がAPI変換後のワークフロー全体からnoise_seed/seedキーを持つ全ノードを一括で上書きする既存のapplySeedToWorkflow()が必ず後から実行されるため、applyBlockToWorkflow()側で書いても意味がなく、Plan全体のシード管理は引き続きそちらに一任する設計とした。

UI面ではDuration(元々ブロックごとの独立値)とNoise Seed(Plan全体で共有)を、機能的な性質は変えずにDOM位置だけtopbarのbasesettings行へ移動した。DurationはブロックエディタのinnerHTML再生成に巻き込まれない固定要素にする必要があったため、change時のリスナー登録をブロック切替のたびに再アタッチする方式から、初期化時に一度だけ登録し値の同期のみブロック切替時に行う方式(_initDurationControl()新設)に変更した。新規7項目は_plan.base_settingsに追加しPlan保存/読込(_buildPlanData/_applyPlanData)にも対応させた——Aspect Ratio等と同様Plan全体で共有する値とし、ブロックごとには持たせない設計とした。

実機検証はComfyUI_5(ユーザーが起動中のポート8189、8188に誤って重複起動してしまった分は作業後に停止)上でKaptureブラウザ操作により実施。MiniMax H3ワークフローでTurbo Mode/Strength/Stepsが正しくハイライト・値反映されWidth/Heightは無効化されること、LTX-2.5 First & Last Frame版でWidth/Heightが有効化・ハイライトされること、Wan2.2 Text to VideoでNoise Seedのみ非ハイライト(disabled)になりTurbo Modeはハイライトされること、ブロック追加後にDurationがブロックごとに独立して保持される(1つ目=5秒のまま、2つ目=7秒に変更後も1つ目に戻ると5秒を維持)ことを、DOM要素のクラス・disabled状態を直接読み取って確認した。

ヘルプ文言はhelpVideo1/helpVideo2/helpVideo10をindex.html・i18n.js(英日中3言語)・app.jsのhelpIdMapの3点セットで更新——helpIdMap自体は既存のキーマッピングをそのまま流用でき変更不要だったが、applyI18nToHtmlが起動時にi18n.jsの内容でDOM要素を上書きする一方でindex.html側の静的テキストが更新漏れになっていたため、ソース整合性のため合わせて修正した。README.mdのVideo Tabセクション(Plan/Edit subtabsの基本設定説明、Field highlight colorの対象フィールド一覧)も新規7項目を反映して更新した。

検証: node --checkで変更した全JSファイルの構文確認。実機(ComfyUI_5、ポート8189)でMiniMax H3/LTX-2.5(First & Last Frame版)/Wan2.2(Text to Video版)の3ワークフローをKapture経由で実際にロードし、ハイライト・disabled状態・値反映・ブロックごとのDuration独立性をDOM評価で確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイル(video-workflow.js/video-plan-tab.js/i18n.js/index.html/video-tab.css)を都度両フォルダへ同期しdiffで完全一致を確認。

How to apply: UIに新しい項目を追加する際、「ワークフローによって対応する場合のみ有効化・ハイライトする」という既存パターンが確立している画面では、新規に選択UIやトグルを作らず既存の仕組み(本プロジェクトではwfm-model-label-activeクラス+ラベルID登録)にそのまま乗せるのが一貫性を保ちやすい(feedback_reuse_existing_ui_before_building_new)。同系統に見えるモデルファミリー間でも、実際のワークフローJSONをパースして公開入力の有無を確認しないと「あるはず」の項目が無い非対称性(今回のWan2.2 Text to VideoのNoise Seed欠如)を見落とす——README/ヘルプ文言に「ワークフローXにはYが無い」という例外を書く前に、必ず実データで裏取りする。UI上は1つのフィールドとして統一表示しつつ、内部的には複数のワークフローファミリーそれぞれの異なる入力名(turbo_modeとenable_turbo_mode)にマッピングする「兼用フィールド」パターンは、意味が同じであることをコメントで明記した上でフォールバック演算子(||)で検出すればよく、UI側の分岐は不要になる。生成時に上流のAPI変換・送信処理(今回のapplySeedToWorkflow)が特定のキーを一括で上書きする既存の仕組みがある場合、新規追加するフィールドの値が同じキーに書き込まれるなら、二重に値を設定するコードを書かず上流の処理に委ねる方が正確(後から上書きされる無駄なコードパスを残さない)。

関連: project_video_plan_asset_tab, project_dev_deploy_sync, feedback_reuse_existing_ui_before_building_new


v0.5.6

バグ修正: WD Taggerで不正確なタグが生成される問題を修正(GitHub Issue #1)

ユーザー(censor-ed氏)からのIssue報告: wd-swinv2-tagger-v3でタグ付けすると画像内容と一致しない不自然なタグが出力される。調査依頼者自身が原因を特定しており、tagger_service.pyのONNX推論前処理がWD Tagger系モデルの学習時前処理と異なっていた(単純リサイズ+RGBのまま0.0〜1.0正規化)のに対し、WD Tagger系標準の前処理は「透過は白背景で合成→アスペクト比を維持したまま白背景で正方形にパディング→BICUBICリサイズ→RGB→BGR→0〜255のfloat32のまま(正規化なし)」であるとの具体的な指摘だった。_predict_onnx()をこの標準前処理に修正(本プラグインがサポートするONNXタガーはREADME記載の通りWD Tagger系のみのためパス全体を統一。DeepDanbooru用_predict_dd()は元々正しい前処理のため変更なし)。実機でwd-eva02-large-tagger-v3を使い、くまの着ぐるみパペットを持つ緑髪キャラクターのアニメ調画像でタグ付けを検証したところ、修正前は前処理が公式実装と異なる状態だったのに対し、修正後はgreen_hair, bear_ears, bear_costume, paw_gloves, hoodie, animal_hood, animal_costumeなど画像内容と高精度に一致する自然なタグが生成されることを確認した。


v0.5.5

Videoタブ全面刷新: Plan/Asset/Editバッチ生成 + Projectサブタブ + プレビュー2ペイン化 + Video Assets/Video Temp運用整理 + Galleryメタデータ消失バグ根本修正

「Videoタブにバッチ動画生成機能を追加したい。既存の生成UIタブのLab機能を動画生成用に整えたイメージ」との要望を起点に着手。従来のVideoタブ(単発生成フォーム)をPlan/Asset/Editの3タブ構成に再設計し、複数セッションにわたるユーザー実機確認・レイアウト調整・追加要望への対応、および調査の過程で発見した重大バグの根本修正までを1リリースにまとめた。

1. Planタブ: タイムラインブロックによるバッチ動画生成: 「基本設定→タイムラインをブロック分割→各ブロックにFirst/Last画像とプロンプトを設定→実行で順次バッチ生成」という要望を実装。LabタブがCheckpoint/LoRA/VAE/Prompt/Ksamplerそれぞれ独立したキーフレーム配列を持つ設計なのに対し、Video Planは「各ブロックが完結した1本の生成ジョブ」という単位でデータを持つ点が本質的に異なるため、Lab方式(_buildWorkflowForIterationによるキーフレーム解決)ではなく新規のデータモデル({id, color, duration, prompt, first_image_mode, first_image_filename, last_image_filename}の配列)を設計した。First ImageはNone/画像を指定/前ブロックの最終フレームを使用の3択(first_image_mode)を持ち、chain_previous選択時は前ブロックに明示的なLast Imageがあればそれを直接使い(フレーム抽出不要)、無ければ前ブロックの生成動画から実際の最終フレームをextractLastFrameBlob()(Labのchain image機能で実績のある関数を再利用)でクライアント側抽出してアップロードする、という優先順位をユーザーと合意の上で実装した。Run/Pause/Cancelと進捗バーはLabタブの_runLabBatchと同じPromiseベースの一時停止機構を踏襲。ワークフロー操作(モデルファミリー自動判定・ノードへの値注入)はVideoタブの既存ロジックからvideo-workflow.jsとして抽出し、Plan/旧単発フォームの双方から呼べる純粋関数群にした。

2. Plan永続化: Labのws_labplan_プレフィックス+PNGコンタクトシートへのiTXt埋め込み方式をそのまま踏襲し、ws_videoplan_プレフィックスでPlan JSON+インデックス画像を保存する仕組みを新規実装(video_plan_service.pyはLabのサービスを複製、共通化はしない方針)。ブロックが1個のPlan=旧来の単発生成と等価になるよう設計し、「Load in Video」ボタンは常にブロック1個の新規Planを生成するようにした。

3. Assetタブ: Galleryのグループ機能を再利用した素材管理: 独自のフォルダブラウザを新規実装する代わりに、feeder-tab.jsの「Galleryモード」(Gallery既存バックエンドをグループスコープで叩く軽量グリッド)と同じパターンを踏襲。当初は生成された動画を全て予約グループ__VideoAssets__へ自動追加する設計だったが、ユーザーからのフィードバックで用途を再定義:__VideoAssets__はユーザーがGalleryで手動キュレーションする「素材」グループとし、バッチ実行の自動追加先は新設の予約グループ__vtemp__(Planを保存していない生成物の一時置き場)に変更。Plan保存済みの場合はVideoPlan:<name>サブグループへ、未保存の場合は__vtemp__へ、という条件分岐にした。Assetタブのグループ選択には「All Video Assets」(__VideoAssets__+__vtemp__の合算、バックエンドに単一グループでの合算フィルタが無いため2回フェッチしてマージ)に加え、両グループの個別選択・Plan別サブグループ選択も追加。Galleryタブ側には__vtemp__用の🔒予約表示(__Feeder__と同様リネーム・削除不可)と、詳細パネルに__vtemp__をワンクリックでクリアする「VtC」ボタン(既存の__Feeder__用「FC」ボタンと同じgroups/{name}/clearAPIを利用)を追加した。

4. UIレイアウトの複数回反復: 初回実装は左サイドバー(380px固定幅)にPlan/Asset/Editの3サブタブを縦積みする構成にしたが、実機確認で「Timeline+First/Last画像+Promptを横並びにしたい」という要望とレイアウト崩れの報告を受け、Planタブの中身を中央の広いプレビュー欄の下部に移動(ブロックエディタをFirst Image/Last Image/Prompt/Duration+colorスウォッチの横並び4カラムに再設計、実行系(Run/Pause/Cancel/進捗/Seed)とプラン管理系(Note/Save/Load)を「Run」「Plan」の2タブに整理)。続けて「これをPlanタブ化してEditタブを追加、左ペインのPlan/Editは削除」との指示で、中央パネルにPlan/Edit専用のタブナビを新設し、左サイドバーにはAssetのみを残した。この際、両タブナビが同じ.wfm-video-subtab-btn/.wfm-video-subtab-panelクラスを共有する実装だったため「Assetを選ぶと中央のPlan/Editも連動して消える」という不具合が発生 — 中央タブの切替処理を.wfm-video-center-panelスコープに限定したquerySelectorAllに変更し、サイドバーのAsset単独ボタンは独立したshow/hideトグルとして分離することで解消した。中央・右ペイン(プレビュー、Frame抽出/GIF変換/Video Source)はサブタブ切替と独立した常時表示パネルとして、レイアウト変更を通じて無改修のまま維持している。

5. バグ修正: Galleryのグループ追加がジャンクションフォルダ環境で一覧に反映されない: 「生成完了後に__vtemp__グループには入らない。Galleryで手動追加したコンテンツは表示される」との報告を受けた調査で発見。ユーザー環境のComfyUI_5\outputはStabilityMatrixの共有出力フォルダ機能によるWindowsジャンクション(実体はStabilityMatrix\Images\Text2Img)だった。save_image_meta(タグ/メモ保存)には既に「get_image_metadata()側はresolve()済みパスをキーにするため保存側も統一する」という同種バグの修正コメントがあったが、add_to_group/remove_from_group/toggle_favorite/bulk_set_favoriteの4箇所には同じ修正が未適用のまま残っていた。そのためグループ追加自体はサーバー上で成功する(ジャンクション形式の未解決パスをキーに保存)が、一覧取得(list_images)はフォルダを.resolve()した実パスでスキャンするためキーが一致せず、「追加はされるが一覧に出てこない」というサイレント不具合になっていた。4箇所ともsave_image_metaと同じresolve()統一パターンに修正。Video機能に限らずGallery全体(お気に入り・グループ機能)に影響していた既存バグで、今回の調査で偶然発見・修正できた。

6. バグ修正: Galleryのグループフィルタがサブフォルダ内のファイルを見つけられない: 5の修正後も「rootで__VideoAssets__フィルタ時は画像のみ、動画は表示されない」との報告。動画はvideo/サブフォルダに保存されるが、GalleryタブのloadImages()はグループフィルタの有無に関わらず常に非再帰スキャン(現在のフォルダ直下のみ)だったため、グループメンバーがサブフォルダにあると机上から漏れていた。グループフィルタが有効な時のみrecursive=trueを付与するよう修正。

7. バグ修正: チェイン画像が無関係な既存ファイルにすり替わる: 「2ブロックのPlanで2つ目のFirst Imageを前ブロックの最終フレームに設定したが、生成された2つ目の動画は無関係な画像が使われている」との報告。調査の結果、convertUiToApi()(UI形式→API形式変換の共通処理)が持つ「COMBO項目の値がキャッシュ済み/object_infoの選択肢に無い場合、無警告で選択肢の先頭に差し替える」というフォールバック機構(チェックポイント名の互換性対応が主目的で元々存在)が原因と判明。LoadImageノードの「image」もCOMBO型のため対象になり、バッチ実行中にアップロードした抽出フレーム画像のファイル名はその時点でキャッシュ済みの/object_infoにまだ存在せず、無関係な既存ファイル(選択肢の先頭)に差し替えられていた。LabタブのchainImage機能がこの問題に遭遇しないのは、Labが既にAPI形式のワークフローを直接操作しconvertUiToApi()を経由しないため。comfyui-workflow.jsにinvalidateObjectInfoCache()を新設し、チェイン用フレーム・ユーザーの手動画像アップロードの直後に呼んで次回変換時に最新の/object_infoを再取得させるよう修正した。

8. バグ修正: __VideoAssets__グループが再起動後に一覧・詳細パネルから消える: 「再起動してギャラリーを確認したがグループに__VideoAssets__、__vtemp__が表示されなくなった」との報告。gallery_metadata.jsonを実データで直接確認したところ、__vtemp__はレジストリ・10件のメンバーシップとも正常だった一方、__VideoAssets__はグループレジストリ配列からも全画像のメンバーシップからも完全に消えていた(delete_group()はレジストリと全画像のgroups配列を同時に消す実装で、症状と完全に一致)。原因は__vtemp__/VideoPlan:<name>と異なり、__VideoAssets__はバッチ実行側からensure_groupを呼ぶコードが一切無い設計だったこと — 3番で予約グループ保護(🔒表示・削除ボタン無効化)を追加する前の時点でManage Groupsから削除操作が可能だったため、その時点で削除され、以後add_to_groupは個別画像への書き込みは成功してもレジストリを再登録しないため二度と一覧に出てこなくなっていた。gallery-tab.jsにensureFeederGroup()と同型のensureVideoGroup()を新設し、VideoタブのAssetタブ初期化のたびに__VideoAssets__を確実に再登録するよう修正。削除時に失われた過去のメンバーシップ自体(開発中のテスト動画)はバックアップが無く復元不可能である旨をユーザーに報告した(このensure漏れが後に13番の伏線になる — __VideoAssets__側だけ直し__vtemp__側のensureが漏れていた)。

9. Assetタブに保存済みPlan一覧を表示する「Project」サブタブを追加: 「AssetタブにVideo Planを表示したい」との要望を起点に着手。何を指すか確認したところ「右ペインにプロジェクトタブを追加し、保存済み一覧、各動画のplan名表示としたい」との回答。調査の結果、バックエンドにはGET /api/wfm/video/plans一覧APIと/wfm_data/video_plan/{name}.pngサムネイル配信が2番のPlan永続化実装時から既に存在していたが、フロントエンドからは一度も呼ばれておらず丸ごと未使用のまま埋まっていたことが判明した。新規video-project-tab.jsで保存済みPlan一覧(サムネイル・名前・ブロック数・更新日時)表示、クリックでのPlanエディタ読み込み(video-plan-tab.jsにopenSavedVideoPlan()をexport)、削除機能を実装し、Assetカードには生成元のPlan名をバッジ表示する機能(img.groupsからVideoPlan:<name>グループを逆引き)も合わせて追加した。当初の指示通り右ペイン(Frame/GIFタブの隣)に実装したが、直後のフィードバックで「左ペインに変更したい」との指示を受け、左サイドバー(Assetタブの隣)へ丸ごと再配置した。この移動に伴い、左サイドバーのトグルロジックを「Assetボタン単体のshow/hide」から「クリックした方を開き他方を閉じる、同じボタンの再クリックで両方とも折りたたむ」という排他的トグルに拡張した。

10. 中央下部のワークフロー名表示行を1行に統合: 「Open Workflowボタン行を上段の現在読み込んでいるワークフロー名行と1行にしたい」との要望。wfm-video-wf-name(現在ロード中のワークフロー名)とwfm-video-plan-workflow-row(Planに記録されたワークフロー名+Open Workflowボタン)が縦に2行表示されていたのを、.wfm-video-wf-rowという横並びflexコンテナでまとめ1行に統合した。

11. ワークフロー設定可能項目ハイライト表示の復活: 「以前の単発生成フォーム時の設定項目のハイライト表示を行いたい」との要望を受けて調査したところ、v0.5.4で一度実装されていた機能(wfm-model-label-activeクラス、GenerateUI Modelタブと共通の設定色)が、4番のPlan/Asset/Edit再設計で単発フォームのコードごと失われ、ヘルプ文言だけが取り残されていたことが判明した。Planタブのブロックエディタ向けに_updateFieldHighlights()を再実装:Prompt/Duration/First Image/Last Image/Aspect Ratio/Megapixels/Multipleの各ラベルにIDを付与し、読み込んだワークフローが実際にそのフィールドをサポートしている場合のみハイライトする(Wan2.2のようにResolutionSelectorが無いワークフローではAspect Ratio/Megapixels/Multipleは非ハイライトのまま)。ワークフローロード時・Open Workflowボタン使用時・ブロック切り替え時(_renderBlockEditor()はブロック選択のたびにDOMを作り直すため都度再適用が必要)のいずれでも正しく再適用されるようにし、ハイライト色を注入するCSSセレクタ(settings-tab.jsのapplyModelTabActiveColor())も旧レイアウトの.wfm-video-form-panelから新レイアウトの.wfm-video-plan-contentに更新した(4番のレイアウト変更で参照先の無いセレクタになっていた)。この過程で、ヘルプ本文(helpVideo8)がHTML側とi18n.js側で別内容になっている既存の不整合(HTML側は旧ハイライト説明、i18n.js側はチェイン機能の説明で後者が実際に表示される)を発見し、ハイライト説明を新規helpVideo10として追加し直した。

12. Video Assets/Video Tempグループの命名統一・自動登録の運用整理: 「__vtempグループが消えてしまっている」という報告と、ユーザー自身による修正方針の提示(表示名をグループ名で統一、VideoPlan:自動登録を廃止し常にVideo Tempへ、VtCボタン名を「Clear Video Temp」に変更)を受けて実施。3番/8番で導入した__VideoAssets__/__vtemp__を__Video Assets__/__Video Temp__に改名し、Assetタブの表示名もグループ名からそのまま導出する形に統一(「Temporary (unsaved runs)」という独自表示名は廃止)。3番の「Plan保存済みならVideoPlan:<name>サブグループへ」という自動振り分けは廃止し、Plan保存の有無に関わらず常に__Video Temp__へ登録するよう変更 — __Video Assets__への整理は完全にユーザー任意のタイミングに委ねる設計に転換した。対応してAssetタブの「VideoPlan:」動的取得・フィルタ表示ロジックと、9番で追加したAssetカードへのPlan名バッジ表示も、対応するグループ自体が無くなったため削除した。バックエンド予約グループリスト(py/routes/gallery_routes.pyの_RESERVED_GROUPS)も新グループ名に追従させている。既存のVideoPlan:グループ自体はユーザー側で削除する前提で進めた。

13. 重大バグ修正: gallery_metadata.jsonの非アトミック書き込みによるデータ消失(根本原因): 「再起動するとグループに登録した内容が消えます」との報告を受けた調査。まず8番のensureVideoGroup()が実は__VideoAssets__(現__Video Assets__)しかensureしておらず__vtemp__(現__Video Temp__)はensureしていなかったという見落としを修正したが、実データ(user/default/Workflow-Studio/gallery_metadata.json、画像9,470件)を直接調べたところ、より深刻な問題が見つかった。GalleryMetadataStore._save_to_disk()がopen(path, "w")による直接上書きの非アトミック書き込みで、バックグラウンドインデックス処理(_bg_index_folder、未キャッシュ画像10枚ごとに保存)が大量画像フォルダに対して極めて高頻度にこのファイルへ書き込み続けていた(実測でファイルサイズが0.5秒間隔で数百KB単位で変動し、外部プロセスから読むとタイミングによってJSONパースが失敗する状態だった)。この書き込みの最中にComfyUIプロセスが終了すると、ファイルが不完全なJSONのまま残り、次回起動時の_load()がパースに失敗して警告ログを出すだけで中身を空の{"images": {}, "groups": []}にリセットしてしまう — 以降1回でも保存が走ると、それまでの全グループ登録・タグ・メモが跡形もなく上書きされて消える、という構造だった。実際に確認したところ、グループの登録リスト自体(__Feeder__/__Video Assets__/__Video Temp__等)は残っていたが、動画がどのグループに属しているかという個々のメンバーシップ情報は失われていた。_save_to_disk()を一時ファイル書き込み+Path.replace()によるアトミックな置き換えに変更し、_load()がJSON破損を検出した場合は単に空データにするのではなく.corruptファイルとしてバックアップしてから起動するよう変更した(今後同様の事態が起きても手動復旧の余地を残す)。単体テスト(アトミック書き込み・破損時バックアップ・復旧後の再保存)で動作確認。今回失われた登録内容自体はバックアップが無く復元不可能である旨をユーザーに報告した。

14. 中央プレビューの2ペイン化: 手描きの構成図での要望「動画ソース・ファイルドロップエリアを左端に移動、プレビューを2つにして左はアセットの選択動画/ドロップされた動画のプレビュー兼用、右は生成された動画のプレビュー用」を実装。従来は単一のプレビュー(Video Sourceドロップの表示専用で、Assetタブの選択動画やPlan実行結果を自動表示する仕組み自体がそもそも存在しなかった)だったのを、3カラム構成(左: Video Sourceフォーム、中央: Asset選択/Sourceドロップ兼用プレビュー、右: 生成動画専用プレビュー、いずれも新規実装)に再構成した。video-tab.js・video-asset-tab.js(Asset選択)・video-plan-tab.js(バッチ実行結果)の3ファイルがプレビュー状態を共有する必要があったため、新規video-preview.jsという共有モジュールに切り出した(video-tab.jsが既に他の2ファイルをimportしているため、逆方向のimportを追加すると循環参照になることを回避するため)。Frame/GIFツールは「直近にアクティブになった方のプレビュー」を対象に動作する設計にした。Asset選択時はGalleryの絶対パスをComfyUIのfilename/subfolder/type形式に変換する手間を避けるため、動画をBlobとしてfetchし「ローカルファイル」として扱うことで、GIF変換の既存アップロードロジックをそのまま流用できるようにした。Assetサイドバーの詳細パネルにあった動画プレビュー(<video>要素)は、中央プレビューに統合されたため削除した。

15. ヘルプの整合性チェック: リリース前の最終確認としてVideo Tabヘルプ全11項目(Title/Desc/1〜10)をHTML・i18n.js(3言語)・app.jsのhelpIdMapで突き合わせ、11番で発見したhelpVideo8の不整合以外にも、helpVideoDesc(旧「Plan / Asset / Edit」の一括り記述が9番以降のサイドバー分離構造と食い違っていた)とhelpVideo1/helpVideo2/helpVideo3(HTML側が旧単発フォーム時代の文言のまま放置され、実際に表示されるi18n.js側の内容と長年食い違っていた)を発見・修正した。README.mdのVideo Tabセクションも同様に、4番のPlan/Asset/Edit再設計自体が一切反映されないまま単発フォーム時代の説明で放置されていたことが判明し、9〜14番までの内容を反映して全面的に書き直した。

16. pyproject.tomlのlicense記述をComfy Registry仕様準拠の形式に修正: リリース準備の一環で、license = "MIT"という単純文字列形式が、公式仕様(license = {file = "LICENSE"}または{text = "..."}のいずれか)に該当しない非推奨形式であり、comfy env実行時にcomfy-cliが警告を出していたことを確認した(project_registry_flagged_versionsで追跡しているv0.5.0〜v0.5.3の原因不明のFlagged/Pending状態の調査依頼を受けて着手)。合わせて、参照先のLICENSEファイル自体がリポジトリに存在しなかった(README.mdの「MIT License」という一文のみ)ため新規作成し、license = { file = "LICENSE" }に修正した。調査の結果、Comfy Registryのセキュリティスキャンは主にAI+静的解析(Ruff)によるPythonコード内の危険なパターン検出(eval/exec/subprocess/pickle.loads/カスタムpip wheel等)が対象で、pyproject.tomlのメタデータ自体(license記述含む)がスキャン対象になるという情報は見つからなかった — つまり今回の修正はComfy Registryの公式仕様に沿った改善ではあるが、Flagged/Banned問題の直接的な解決策である確証はない。一方で、他の開発者(GitHub issue経由)でも同様の原因不明なFlaggedステータスが複数報告されており、pyproject.tomlの記述とは無関係にRegistry側の自動スキャンが誤検知(false positive)を起こしている可能性が高いことが分かった。具体例として、外部バイナリをsubprocessで呼び出す正当なコード(GraphicsMagick連携)が誤検知された報告があり、本プロジェクトにも同種のパターン(gmic_routes.pyのsubprocess.Popen+Windows固有のCREATE_NEW_CONSOLEフラグ、wildcard_service.pyの["cmd", "/c", "mklink", ...])が存在するため、これらが誤検知の一因になっている可能性はあるが確証はない(いずれも正当な機能で、変更は行っていない)。

検証: 全JS/Pythonファイルをnode --check/python -m py_compileで構文確認、templates/index.htmlはPythonのhtml.parserでタグ対応チェックとJinja2実レンダリングで検証。ジャンクションバグの調査・検証は、ユーザーが起動中のComfyUI_5(ポート8189で稼働)に対しcurlでAPIを直接叩いて実施 — os.path.realpath()で実際にジャンクションであることを確認し、groups/{name}/images(生成成功)とlist_images(グループフィルタで空)の食い違いを再現、修正版デプロイ後の再検証はユーザー自身の実機確認で完了。8番の__VideoAssets__消失バグは、実行中サーバーのgallery_metadata.jsonを直接読み込んで原因を特定した(読み取り中にサーバー側の_save_to_disk()と競合しJSONDecodeErrorになることがあったため、パース成功するまで数回リトライして実データを確認)。13番の根本原因調査では、同じファイルへの読み取りを連続実行してもファイルサイズと成否が毎回変わることを確認し、実データが継続的に高頻度書き込みされている実態そのものを証拠として特定に用いた。13番の修正自体はtempfileを使った独立テストスクリプトで正常保存・再ロード後のデータ保持・破損ファイル検出時のバックアップ・復旧後の再保存を単体検証(ComfyUI起動不要)。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更・新規ファイルを都度両フォルダへ同期しCRLF/LF差異を除いたdiffで完全一致を確認、Python変更(13番・12番のgallery_routes.py予約グループリスト)を含む回では実行時側の__pycache__もクリアした。各段階(レイアウト・Projectサブタブ追加・ハイライト復活・グループ運用整理・データ消失バグ・プレビュー2ペイン化・ヘルプ整合性)ともユーザー自身の実機確認・スクリーンショットや手描き構成図の提供を受けて次の修正に進む反復的な進め方で実施した。16番のRegistry仕様調査はWeb検索でComfy公式ドキュメント(docs.comfy.org/registry/specifications)とGitHub issue(Comfy-Org/ComfyUI-Manager #2927, #2997)を確認し、pyproject.tomlのlicense = { file = "LICENSE" }変更後はtomlパッケージでパース可能なことを確認した。

How to apply: 「◯◯機能では動くのに△△機能では動かない」という報告があった場合、両者の実行経路が本当に同じか(今回はLabがAPI形式を直接操作するのに対しVideoはUI形式からconvertUiToApi()を経由する、という違い)を疑うと真因に早く到達できる。ComfyUIフロントエンドのconvertUiToApi()のようなCOMBO値検証は「無警告でのフォールバック」を行う設計になっている場合があるため、実行時に動的アップロードした値をCOMBO項目に書き込む機能(画像チェイン等)を追加する際は、そのCOMBOの選択肢キャッシュが最新かどうかを必ず確認する必要がある。StabilityMatrixのような複数ComfyUIインスタンスでモデル/出力フォルダを共有するツールはジャンクション/シンボリックリンクを使うため、Path.resolve()をパスキーの一部に使うコードとファイルシステムスキャンで得たパスとの間に「解決する/しない」の不一致が生まれやすい — 同じ画像を指すパス文字列の保存/検索が全経路で一貫した規約(このプロジェクトでは「常にresolve()後の文字列で統一」)に従っているか、新しい書き込み経路を追加するたびに確認する価値がある。複数のタブ切替グループが同じCSSクラス/data属性を共有する実装は、後から片方だけ物理的に別の場所へ移動すると意図せず連動してしまうため、独立させたいグループは早い段階でクラス名やquerySelectorAllのスコープを分けておくとよい。予約グループ(__Feeder__のような「削除されると誰も自動で作り直さない」特別なグループ)を新設する際は、保護(削除禁止)の実装と同時に「常に存在することを保証するensure呼び出し」もセットで用意しないと、保護を追加する前の空白期間や手違いで一度消えた場合に二度と復活しない — この2つは別々の対策であり、片方だけでは不十分。実行中サーバーが継続的に書き込んでいるJSONファイルを外部から直接読んで調査する際は、書き込みと衝突して不完全な内容を読んでしまうことがあるため、パースが安定して成功するまでリトライするのが安全。JSON等の設定/メタデータファイルへの永続化処理を新規実装・レビューする際は、単純なopen(path, "w")による直接上書きになっていないか必ず確認する — 書き込み頻度が低いうちは問題が表面化しなくても、対象データ量が増えて書き込み回数が増えるほど「書き込み中のプロセス終了でファイルが壊れる」リスクが顕在化しやすく、かつ壊れたファイルのロード失敗を「空データにフォールバック」で処理していると、その後の1回の保存で過去の全データが消えるという最悪の組み合わせになる(一時ファイル+Path.replace()のアトミック置き換えと、破損検出時のバックアップ退避はセットで対策する)。UI再設計でコードごと削除した機能(今回のハイライト表示)は、対応するヘルプ文言だけが取り残されて「存在しない機能の説明」として残り続けることがあるため、大きめのUI再設計を行った際はヘルプ文言側の棚卸しも忘れずに行う。ユーザー向けドキュメント(README等)は実装が大きく変わった直後に更新を忘れると、次に触るまで長期間ズレたまま放置されやすい — 大規模なUI再設計コミットを作る際は、その場でREADMEの該当セクションも一緒に更新するのが安全(今回はPlan/Asset/Edit再設計そのものが数バージョン分READMEに未反映のまま残っていた)。

関連: project_v050_video_tab, project_dev_deploy_sync


v0.5.4

機能追加: Video/GenerateUIタブのLTX-2.5・Wan2.2対応拡大 + AI Assistant設定のUnsloth対応・独立モーダル化・モデルアンロード機能

複数セッションにまたがり実施された作業をまとめてリリース。Videoタブが従来MiniMax H3専用だった検出/生成ロジックをLTX-2.5・Wan2.2にも対応させ、続けて生成UIタブ側の解析ロジックにも同じ2ファミリーへの対応を追加した。並行してAI Assistant設定(Prompt/Tagger)をUnslothバックエンド対応・タブ別モーダル化・モデルアンロード機能へ刷新した。

1. Videoタブ: LTX-2.5 / Wan2.2対応(ノード検出の汎用化): 従来MiniMax H3専用だったサブグラフ検出/フォーム反映ロジックを、サブグラフが公開する入力のlabel(無ければname)ベースで意味的にスロットを特定する方式に一般化した。MiniMax H3/LTX-2.5系は内部ノード型のプレフィックス(LTXV*等)で、Wan2.2は該当する固有ノード型を持たないためサブグラフ名自体("...(Wan2.2)"表記)で家系を判定する。これによりVideoタブの「Load in Video」からLTX-2.5のText/Image/First&Last-Frame to VideoワークフローとWan2.2のText to Videoワークフローも読み込み・生成できるようになった。同梱していないminimax_test.jsonの自動読み込みも廃止。

2. バグ修正: convertUiToApi()のMultiTypeウィジェット脱落: object_infoが複合型(例: LTXVEmptyLatentAudio.frame_rate: "FLOAT,INT")を宣言している入力が、単純な完全一致判定(upper === "INT" || ...)に引っかからずウィジェット名/型リストから丸ごと脱落し、後続ウィジェットの値が1つずつずれる問題を発見・修正した。実際にLTXVEmptyLatentAudio.batch_sizeにframe_rateの値が誤って書き込まれ、動画生成が"Expected size 1 but got size 25"で失敗する原因になっていた。_resolveWidgetType()を新設し、カンマ区切りの型宣言から実際にウィジェットスロットを占める単一のスカラー型を解決するよう修正。

3. 動画再生ボリュームの保存設定: 動画を切り替えるたびに音量が最大へ戻ってしまう問題を解消するため、設定タブに音量設定を追加しVideo/Galleryタブの全プレーヤー間で保存・共有されるようにした。

4. GenerateUIタブ: LTX-2.5 / Wan2.2対応(プロンプト検出・Latent Image・タイムアウト): Videoタブとは独立した解析経路(analyzeWorkflow()、フラット化済みAPI形式ワークフローを解析)側でも同じ2ファミリーの通常ワークフロー読み込みに対応させる作業。実機確認したところ、Model タブ(Checkpoint/VAE/Diffusion Model/Text Encoder/LoRA)は既存のUNETLoader/CLIPLoader/VAELoaderベースの汎用検出でWan2.2/LTX-2.5とも既に問題なく動作していたが、次の2点に不備を発見した。

  • Positive/Negative Prompt検出(LTX-2.5): LTX-2.5はLTXVDualCFGGuider(positive/negativeが同一のLTXVConditioningノードの別出力スロットを指す)+ LTXVConditioning(自身のpositive/negative入力を介して実際のプロンプトソースへ分岐)という専用ノード構成を使うため、既存のCFGGuider/InstructPixToPixConditioning向けロール伝播ロジックでは拾えず、Positive/Negative Promptが空欄のままSaveVideoノードへのフォールバック表示になっていた。LTXVDualCFGGuiderをロール伝播の起点に、LTXVConditioningをInstructPixToPixConditioningと同型の分岐伝播ノードとして追加し解決した。
  • Latent Image検出(Wan2.2・LTX-2.5共通): SettingsタブのLatent ImageセクションがEmptyLatentImage/EmptySD3LatentImage/EmptyFlux2LatentImageのみ対応しており、Wan2.2のEmptyHunyuanLatentVideoとLTX-2.5のEmptyLTXVLatentVideoが未対応で「No EmptyLatentImage node found」表示になっていた。両ノード型を追加し、動画特有のlength(フレーム数)入力を編集するフィールドも新設した(Duration×FPSの数式ノードにリンクされ非リテラルなことが多いため、Width/Height同様「linked」表示にフォールバックする)。
  • あわせてisVideoWorkflow()(生成タイムアウトを30分に延長する判定)を、SaveVideo/CreateVideoノードや動画用latentノードの存在でも判定できるよう拡張した。従来はMiniMax H3のようなオールインワン*ToVideoノードの有無でしか判定できず、LTX-2.5/Wan2.2は通常の静止画と同じ短いタイムアウトで打ち切られる恐れがあった。

5. Videoタブ左パネルのハイライト表示: GenerateUIのModelタブが持つ「ワークフローに実際に該当する項目のラベルをハイライト」する仕組み(wfm-model-label-activeクラス、設定タブでカスタマイズ可能な共通色)をVideoタブの左パネルにも適用した。Prompt/Durationは常時、First Frame/Last Frameはサブグラフがそのスロットを公開している場合のみ、Aspect Ratio/Megapixels/MultipleはResolutionSelectorノードが存在する場合のみハイライトする — Wan2.2のように該当ノードがなく操作不可(disabled)な項目は、既存の無効化表示と矛盾しないよう非ハイライトのままにした。

6. バグ修正: 生成UIタブRAW JSON検索がヒット箇所にカーソルを当てて不意に編集させてしまう: 検索でヒットした際にeditor.focus() + setSelectionRange()でテキストエリアへフォーカス・選択範囲を作っていたため、検索直後にキー入力すると選択範囲が上書きされ意図せずJSONが書き換わってしまう問題があった。ハイライト表示(<mark>によるオーバーレイ)とスクロール追従は維持しつつ、フォーカス移動と選択状態の作成のみを取りやめ、編集はユーザーが明示的にエディタをクリックした場合のみに限定した。

7. AI Assistant設定: Unsloth対応・Prompt/Tagger独立モーダル化・モデルアンロード機能: Prompt AI(Prompt タブ)とTagger VLM(Taggerタブ)にUnslothバックエンドを追加した(サーバー側プロキシでAPIキーを付与)。Taggerタブは従来Ollama直呼び出しだった実装を、Ollama/LM Studio/Lemonade/Unslothの4バックエンド共通のvlm_models/vlm_predictエンドポイントに統一し、API URL直接入力欄を廃止した。中央Settingsタブにあった単一の「AI Assistant Settings」パネルを廃止し、Prompt/Tagger各タブに⚙設定ボタン+専用モーダル(ai-settings-modal.js、Backend/Connection/Model/Generation/Unload/Saveの共通UIコンポーネント)を新設、それぞれ独立したlocalStorageキーで設定を持つように変更した(PromptとTaggerで異なるバックエンド/モデルを使い分けられる)。Thinking mode / Max tokensの設定をPrompt/Tagger双方の生成呼び出しに反映(Tagger側はPython側のvlm_predictにも反映)。Ollama(keep_alive:0)/LM Studio(/api/v1/models/unload)/Lemonade(/api/v1/unload)向けのモデルアンロード機能を追加し、AI TOOLタブの設定にもUnloadボタンを追加した(Unslothは対応する公式APIが無いため非対応として明示)。

8. CI: publish-node-actionのバージョンをSHA固定: Comfy-Org/publish-node-action@mainという可変ブランチ参照は、アクション側が更新された際に挙動が変わりうるサプライチェーンリスクのため、リリースタグ1.0.1のコミットSHAに固定するよう.github/workflows/publish.ymlを修正した(project_registry_flagged_versionsで追跡している過去のRegistry Banned/Flagged原因調査の一環)。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、GenerateUIタブ(LTX-2.5/Wan2.2対応・RAW JSON検索の挙動)とVideoタブ(ハイライト色機能)のヘルプ本文を英語・日本語・中国語の3言語で追加・更新した(Prompt/Settings/AI TOOL/Taggerタブのヘルプは7番の対応セッション内で既に更新済み)。README.mdはWorkflowタブ(Load in Video対象の拡大)、GenerateUIタブ(MultiTypeバグ修正・LTX-2.5/Wan2.2対応)、Videoタブ(対応ファミリー拡大・ハイライト色・音量設定・見出しバージョン範囲更新)、Settingsタブ(AI Assistant Settings廃止の案内・音量設定・ハイライト色の共有先追記)、Prompt/Taggerタブ(AI Assistant設定のモーダル化・マルチバックエンド化)、AI TOOLタブ(モデルアンロード機能)を更新した。

検証: 全JS/Pythonファイルをnode --check/ast.parse()で構文確認。GenerateUIタブのLTX-2.5/Wan2.2対応は、実機ブラウザ(Kapture)でwan22_test.json / ltx_25_test.jsonの2ワークフローを実際に生成UIタブへ読み込み、Positive/Negative Promptの実文表示・Latent Image欄(Width/Height/Batch Size/Length)の表示・ハイライト色の点灯状態をスクリーンショットで確認した。RAW JSON検索のフォーカス修正は、検索実行後にキー入力してもtextareaの値が変化しないことを実機で確認した。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルを都度両フォルダへ同期しCRLF/LF差異を除いたdiffで完全一致を確認した。

How to apply: analyzeWorkflow()のような中央解析関数はワークフロー「ファミリー」が増えるたびに、既存パターン(KSampler系のロール伝播、EmptyLatentImage系のlatentノード検出)と同型の分岐を追加していく設計で拡張できる — 逆に言えば、新しいノード構成(今回のLTXVDualCFGGuider/LTXVConditioningのような「複数出力を持つ1ノードがpositive/negativeを兼ねる」パターン)はInstructPixToPixConditioningのような既存の同型ケースを探して倣うと実装が速い。同じワークフローファミリーへの対応が複数タブ(Video/GenerateUI)にまたがる場合、検出ロジック自体は各タブの解析経路(Video: サブグラフを直接歩く、GenerateUI: フラット化済みAPI形式を解析)が別実装なので、片方を直しても他方は直らない — 「Videoタブでは動くのにGenerateUIでは動かない」という状況は、まさにこの二重実装のズレが原因だった。テキストエリアへの自動フォーカス/selectionRangeは、検索・ハイライトのような「見せるだけ」の操作と「編集を許可する」操作を暗黙に混同させるため、ハイライト表示に徹する機能ではフォーカス移動自体を避けるのが安全。

関連: project_v050_video_tab, project_dev_deploy_sync, project_v0336_conventions, project_registry_flagged_versions


v0.5.3

バグ修正: Labタブのワークフロー切替時に前の内容が残留 + Galleryタブの読み込み高速化・root自動展開 + READMEスクリーンショット刷新

ユーザーからの複数のバグ報告・改善要望を1セッションで順に対応した作業。

1. バグ修正: Labタブで起動時デフォルトワークフロー読み込み後に別ワークフローを読み込むと前の内容が残る: 「別のワークフローを読み込んだ。一部以前のワークフローの内容が残ったまま表示される(クリックするか他のタブから戻ると消える)」との報告。Labタブの「Setting 1」列(Checkpoint/LoRA/VAE/Prompt/KSampler)はGenerateUIの現在の読み込み内容をライブ反映する仕組みだが、その再描画(refreshLabLiveDefaults())は「サブタブをLabに切り替えたとき」の一箇所(generate-tab.jsのサブタブクリックハンドラ)でしかトリガーされていなかった。Labタブを既に開いた状態で別のワークフローを読み込んだ場合、loadWorkflowIntoEditor()側では再描画が一切呼ばれず、前のワークフローの表示がそのまま残っていた。「セルをクリックしたり他のタブから戻ると直る」という報告内容は、それらの操作がたまたま再描画をトリガーしていたことの裏返しだった。loadWorkflowIntoEditor()の末尾で、現在アクティブなサブタブがLabの場合はrefreshLabLiveDefaults()を追加で呼ぶよう修正。

2. 改善: Galleryタブの生成後の更新が遅い: 「生成後の画像表示に更新して読み直すのに時間がかかる」との報告。実データ(Output先のText2Imgフォルダ、画像9,777枚)で_scan_folder()を実測したところ7.5秒かかっており、原因を切り分けたところentry.stat(follow_symlinks=True)と、各ファイルごとに呼んでいたPath(entry.path).resolve()の2箇所がボトルネックと判明。特にresolve()の再解決が支配的で、これだけで1ファイルあたり約0.7ms×9,777件のファイルシステム往復が発生していた。_scan_folder()の呼び出し元(list_images()/_bg_index_folder())は既にフォルダ自体をPath(folder_path).resolve()済みで渡しているため、entry.pathは既に絶対パスであり、エントリ単位での再resolve()は不要だった。entry.is_file()/entry.stat()をfollow_symlinks=Falseに変更し、パス生成もentry.path.replace("\\", "/")に簡略化(出力フォルダにシンボリックリンクの画像が置かれる想定はないため安全と判断)。同じ理由でos.walk()ベースだった_scan_folder_recursive()(ImagePrompt/Style Catalog用の再帰スキャン)も、os.scandir()を自前で再帰しDirEntryベースでstatする実装に書き換えた。実測で9,777枚のフォルダに対するlist_images()全体が0.14秒(コールド)/0.08秒(フォルダキャッシュ温存時)まで短縮(約50倍高速化)。

3. 改善: Galleryフォルダツリーのrootをデフォルト展開状態に: 上記の高速化を受けて「rootフォルダはデフォルトで展開状態で表示したい」との要望。フォルダスキャンが軽くなったことでサブフォルダの内容もすぐ確認できるようになった一方、renderTreeNode()はrootも他のノードと同じく初期状態は折りたたみ(▶)だったため気付きにくかった。root(isRoot)の場合のみ、ツリー構築時に矢印を展開済み(▼)にし子ノードを即座にレンダリングするようgallery-tab.jsを修正。

4. READMEスクリーンショット刷新: 「以下のスクリーンショットは削除したい」との依頼でGenUI Feeder / GenUI LoRA Stack / Models Multi-select Menu / Tagger Tab / Customizeの5件をREADME.mdの表とdocs/実ファイルの両方から削除(残った項目は2列レイアウトに詰め直し)。続けてVideo Tab・Lab Tab(2枚)・AI TOOL Tabの新規スクリーンショット計4枚をユーザーがdocs/に配置し、READMEの表に追加した。

検証: JS/Pythonともnode --check/ast.parse()で構文確認。Gallery高速化は開発元リポジトリのGalleryServiceを実データ(実際のText2Imgフォルダ、9,777枚)に対してvenv Pythonから直接呼び出し、修正前後の_scan_folder()/list_images()の実行時間をベンチマークして改善を数値で確認(7.5秒→0.063秒、list_images()全体は0.14秒)。返却されるパス文字列が修正前と同じ絶対パス形式であることも確認済み。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更・削除した全ファイル(generate-tab.js, gallery_service.py, gallery-tab.js, README.md, docs/配下の画像)を都度両フォルダへ同期しdiff/存在確認で完全一致を確認、Pythonファイル変更のため__pycache__もクリア。全項目ともユーザー自身の実機確認で解消・反映を確認済み。

How to apply: あるタブ/サブタブの表示を「別の場所(GenerateUI本体など)の現在の状態をライブ反映する」設計にする場合、再描画のトリガーを「そのタブへの切替」1箇所だけに置くと、切替を経由しない別の更新経路(今回はワークフロー読み込み)で表示が古いまま取り残される。ライブ反映される値の更新元(今回はloadWorkflowIntoEditor())側でも、現在表示中のタブを見て能動的に再描画を呼ぶ必要がある。パフォーマンス調査では「体感で重い処理」を分解して各行を実測するまで真犯人を決め打ちしない方がよい — 今回も当初follow_symlinks=Trueのstat呼び出しを主因と想定したが、実測するとPath.resolve()の方が支配的だった。Windows上で大量ファイルを扱うos.scandir()ループでは、entry.pathが既に呼び出し元でresolve済みのフォルダ配下の絶対パスであることを踏まえ、エントリ単位でのresolve()/follow_symlinks=Trueはファイル数に比例した無視できないコストになり得るため避ける。バックエンドの表示速度を改善した際は、それに応じてフロントエンドのデフォルトUI状態(今回は折りたたみ初期状態)も見直す価値がある — 「遅いから畳んでおく」という妥協がボトルネック解消後も残ったままになりがち。

関連: project_dev_deploy_sync


v0.5.2

機能追加: GenerateUI/Labタブの動画ワークフロー対応 + VideoタブにFrame抽出/GIF変換機能 + データ管理監査とZIPフルバックアップ + Send to LI node統一

v0.5.0/v0.5.1で専用Videoタブを整備したMiniMax H3動画生成ワークフローを、GenerateUI/Labタブ本体でも扱えるようにする要望を起点に着手。1セッションで複数の要望・バグ報告・監査依頼を順に実装した一連の作業。

1. GenerateUI/Labタブの動画ワークフロー対応: 「MiniMax H3の動画生成ワークフローを生成UIタブ、Labタブでも生成できるようにしたい」との要望。実際にはcomfyWorkflow.convertUiToApi()+comfyUI.generate()が既に汎用的で生成自体は動いていたが、analyzeWorkflow()がMiniMaxH3ImageToVideoのようなCLIPTextEncodeを介さないオールインワン動画ノードのpromptを検出できず、GenerateUIのInput>Promptタブが空になっていた。クラス名がImageToVideo/TextToVideoを含みinputs.promptが文字列のノードを汎用的に拾う分岐を追加(metadata-tab.js/node_sets_menu.jsに既にあったプロンプト抽出と同じ判定パターン)。GenerateUIとLabタブは共に同じcomfyUI.currentAnalysisを参照する設計だったため、この一箇所の修正でPromptタブとLabのキーフレーム書き込みの両方が動画ワークフローに対応した。画像(first_frame)は既存のLoadImage検出がそのまま拾う。あわせて、動画生成は静止画より大幅に時間がかかるためcomfyWorkflow.isVideoWorkflow(analysis)ヘルパーを新設し、動画ワークフロー検出時はGenerateUI/Labとも生成タイムアウトを30分に延長。GenerateUIの結果プレビューは<img>固定だったため動画結果が正しく表示できなかった問題も、<video>要素を追加し出力ファイルの拡張子で出し分けるよう修正した。

2. バグ修正: Labタブのindex imageが真っ黒になる: 実機確認で発覚。コンタクトシート生成(_buildIndexImageDataUrl)が各結果を<img>要素として読み込もうとしており、mp4は画像として読み込めず失敗、そのセルはcanvasの黒背景がそのまま残っていた。動画結果は既存のchain image機能で使っていた最終フレーム抽出ロジック(extractLastFrameBlob、<video>+<canvas>でクライアント側完結)を再利用してフレームを取り出してから描画するよう修正。

3. バグ修正: LabタブKsampler列が動画ワークフローでも表示される: 「Model同様に該当ノードがなければ非表示にしたい」との要望に対し当初sampler_nodes.length > 0で判定したが、実機確認で「まだ表示される」との報告。原因はMiniMax H3のサブグラフ内部に実際にSamplerCustomAdvancedが配線されており(内部プリセットの一部で、ユーザーが調整すべきものではない)、単純な有無判定では引っかからなかったこと。判定をcomfyWorkflow.isVideoWorkflow(analysis)ベースに切り替え、動画ワークフロー全体ではKsampler列自体を常に非表示にするよう修正した。

4. Videoタブ拡張: Frame抽出 / アニメーションGIF変換: Eagleプラグイン(video-to-frame, video2gif)を参考に要望された2機能。Eagle版はNode.js+ffmpegバイナリ直接呼び出しの実装だったため、ComfyUI-Workflow-Studioの構成(Python/aiohttpバックエンド)に合わせて設計し直した。フレーム抽出は<video>+<canvas>でブラウザ完結(サーバー往復・追加依存なし)。GIF変換はユーザー確認の上サーバー側実装を選択:PyAV(デコード)+ Pillow(GIFエンコード)— どちらもComfyUI本体が既に要求している依存のため、ffmpegバイナリや新規ライブラリの追加インストールは不要。py/services/video_service.py / py/routes/video_routes.pyを新規追加し、folder_paths.get_save_image_path()でComfyUI標準の自動採番保存パターンをそのまま踏襲。合成テスト動画(PyAVで生成)を使いvenv環境でデコード→GIFエンコードの実処理を検証済み。

  • あわせてVideoタブの中央パネルを再設計:「結果」見出し/「まだ結果がありません」表示を廃止し、生成結果とドロップされた任意のローカル動画を同じプレビュー枠に統合、横にVideo Sourceドロップ/クリック選択エリア(Clearボタン付き)を新設。右パネルの「Properties」プレースホルダーをFrame/GIFの2タブに置き換え。生成結果に限らず任意の動画に対してFrame/GIF機能が使える設計にした。

5. データ管理の監査とZIPフルバックアップ機能の新規実装: 「設定のデータ管理を現在のバージョンで対応できているか確認してほしい」との依頼を受け、既存の_DATA_FILES(単一JSONバンドル方式)と実際のデータディレクトリを突き合わせ監査。civitai_cache.json(3.6MB、CivitAI連携キャッシュ)がエクスポート対象から漏れていたことを発見し即修正。また、ヘルプが謳う「export all plugin data」に反し、フォルダ形式のデータ(ai_skills/、lab_plan/、style/)とSQLite DB(tagger.db)が単一JSONバンドル方式では原理的に扱えず完全に対象外になっていた点をユーザーに報告、ZIP形式のフルバックアップ機能を新規実装する方針で合意。DATA_DIR配下を丸ごとZIP化するGET /api/wfm/settings/export-full/POST /api/wfm/settings/import-fullを追加(一時ファイル系gmic_temp/thumb_cacheと、他custom_node(comfyui-impact-pack)へのシンボリックリンクであるwildcard/のみ除外、settings.jsonはAPIキーを除外してから含める)。復元側はZip Slip対策(../等でDATA_DIR外に書き込もうとするエントリはrelative_to()チェックでスキップ)を実装し、悪意あるZIPでの検証済み。

  • 追加要望「デフォルトワークフローフォルダー(user/default/workflows)とwildcardフォルダー(comfyui-impact-pack/wildcards)もオプションでバックアップに含めたいが、インポートは除外し手動対応としたい」に対応:エクスポート画面にオプトインのチェックボックスを2つ追加、有効時はZIP内に_external/プレフィックスで格納し、復元処理は_external/配下のエントリを常にスキップする実装にした(他プラグイン/ComfyUI本体が管理する領域を自動上書きしないため)。実データ(ワークフロー535件・wildcard 34件)で検証済み。

6. Send to LI node機能をGalleryタブにも追加 + ボタン名統一: Image Editタブに既にあった「選択画像をComfyUIキャンバス上の選択中ノードのimageウィジェットへ送る」機能(window.opener.wfmSendImageToSelectedNode、node_sets_menu.jsで定義されたクロスウィンドウ橋渡し)を、Galleryタブの選択画像にも使えるよう追加。Galleryの画像は任意のフォルダにあり得るため、既存の「Send GenUI Image」と異なりLoadImageノードの有無に依存せず、/upload/imageでinputフォルダへアップロードしてからファイル名を書き込む方式にした。あわせてImage Edit側の既存ボタン名「Send to Workflow」を「Send to LI node」に統一(トースト文言・コメント・ヘルプも合わせて更新)。

7. Galleryタブ ✕クリアボタンの位置移動: 「表示形式切替ボタン左隣に移動したい」との要望どおり、wfm-gallery-clear-filters-btnをSort selectとView切替ボタンの間から、View切替ボタン(Thumbnail/Table)の左隣へ移動。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、Video Tab(Video Source/Frame/GIFの3項目)、Lab subtab(Ksampler非表示・chain image動画対応の項目文言修正)、Settings(Data Managementのフルバックアップ・オプション外部フォルダの2項目)、Gallery Tab(Send to LI nodeボタンの項目)のヘルプ本文を英語・日本語・中国語の3言語で追加・更新。README.mdはVideo Tab(Frame/GIFタブ、Video Sourceパネル)、GenerateUI Tab(動画ワークフロー対応)、Lab subtab(動画ワークフロー対応・chain image動画対応、バージョン見出し範囲更新)、Settings Tab(Data Management更新)、Gallery Tab(Send to LI node・✕クリアボタン位置)、Image Edit Tab(ボタン名統一)を更新。

検証: 全JS/Pythonファイルをnode --check/ast.parse()で構文確認。GIF変換・フルバックアップZIP・Zip Slipガードはvenv Python環境で実データ・合成テストデータに対する実処理検証を実施(PyAVで生成した合成テスト動画のフレーム抽出→Pillow GIFエンコード、実際のデータディレクトリ(civitai_cache.json含む36ファイル・約2.6MB)のZIP化、悪意あるパストラバーサルZIPでのスキップ確認、外部フォルダオプション有効時のワークフロー535件・wildcard 34件の_external/格納とインポート時全skipの確認)。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更・新規ファイルを都度両フォルダへ同期し改行コード差異を除いたdiffで完全一致を確認、Pythonファイル追加時は__pycache__を都度クリア。動画ワークフロー対応・Labバグ修正・Videoタブ新機能・Send to LI node機能はユーザー自身の実機確認で動作を確認済み。

How to apply: analyzeWorkflow()のような中央解析関数に新しいノードパターンの検出を1箇所追加するだけで、それを参照する複数タブ(GenerateUI/Lab)に同時に効果が及ぶ設計は、機能追加のコストを大きく下げる — 逆に言えば、新しいワークフロータイプに対応する際はまずこの中央解析関数を疑うべき。「該当ノードが無ければ列を非表示にする」系の判定は、sampler_nodes.lengthのような表面的なノード種別の有無だけでなく、そのノードが「ユーザーが調整すべき設定」を表しているか(動画生成モデル内部に固定配線されたサンプラーのように、単に存在するだけでは判定材料にならない場合がある)を意識する必要がある。「all plugin data」のような謳い文句を持つ機能は、実装が実際にその範囲をカバーしているか定期的に実データと突き合わせて監査する価値がある — 今回civitai_cache.jsonの漏れは3.6MBというサイズながら発見されていなかった。

関連: project_v050_video_tab, project_v051_mp4_metadata, project_dev_deploy_sync, project_v0336_conventions


v0.5.1

v0.5.0で追加したmp4 Gallery対応(一覧・サムネイル・再生のみ)に続き、「ギャラリータブでmp4のメタデータのプロンプトを表示できるようにしたい。メタデータタブでもプロンプト、表示可能ならモデル等も表示させたい。またInfoタブで画像サイズ、再生時間も表示したい」との要望を起点に着手。Eagleでmp4のプロンプト/Steps/Sampler/Seed/Modelが見えている実例(MiniMax H3で生成した.mp4)を手がかりに、ComfyUIのSaveVideoがmp4にどう埋め込んでいるかをffprobe/実PyAVで直接確認した上で実装した。

1. 埋め込み形式の実地調査: ffprobe -show_formatで対象mp4を調べたところ、PNGのtEXtチャンクと全く同じキー名(workflow=UI形式寄りの内容、prompt=API形式)でJSON文字列がコンテナレベルのformatタグとして書き込まれていた。バイト単位で追うとmoov > udta > meta > (hdlr, keys, ilst)という、iTunes系メタデータ(mdta名前空間のkeys/ilst)の構造になっており、FFmpegのmovflags=+use_metadata_tags相当の書き込み方だと判明。ComfyUI側はcomfy_api/latest/_input_impl/video_types.pyのwrite_output_metadata()がPyAV経由でcontainer.metadata[key] = json文字列する実装。

2. バックエンド(py/services/gallery_service.py): 既存のserve_thumbnail()が動画フレーム抽出に使っていたav(PyAV)依存を再利用し、_read_mp4_metadata()(container.metadataからタグ辞書を取得)と_read_media_dimensions()(画像はPillowのImage.size、動画はPyAVのstream.width/height+container.duration)を追加。get_image_metadata()・_extract_embedded_workflow()にmp4分岐を追加し、既存のPNG向けワークフロー抽出ロジック(workflow/prompt優先順位の切り替え)をそのままmp4にも適用した。venv PythonでGalleryServiceを直接呼び出すE2E検証、および実mp4に対するffprobe/PyAVの値の突き合わせ(幅高さ608×352, 再生時間3.042秒, workflow/promptタグ長一致)で正しさを確認済み。

3. Galleryタブ Infoサブタブ: 幅×高さ(画像)または再生時間(動画)を表示する行を追加(gallery-tab.js renderDimensionInfo())。既存のPrompt/JSONタブはextract_workflow_from_metadata系のAPIをそのまま叩いているため、バックエンド対応だけでmp4でも自動的に動くようになった。

4. Metadataサブタブ(Iタブ、metadata-tab.js)とサイドパネルIタブ(web/comfyui/node_sets_menu.js): どちらもドラッグ&ドロップされたFile自体(サーバー未経由のバイト列)を直接パースする設計のため、MP4のボックス構造(moov→udta→meta→keys/ilst)をブラウザ上で直接読むreadMP4MetaTags()/_readMP4MetaTags()をゼロから実装(findMP4Boxでサイズ/タイプ解決→ keysボックスからキー名一覧 → ilstボックスのdataアトムから値を取り出し名前に対応付け)。Node.jsで実mp4のバイト列に対して単体テストし、workflow/promptタグの内容がPyAV/ffprobeの結果と完全一致することを確認した。サイドパネルIタブはmetadata-tab.jsと同一ロジックを保持する独立した複製実装(ComfyUIネイティブ拡張のため別ファイル)のため、同じ変更を両方に適用。

5. バグ発見・修正: オールインワン動画ノードのプロンプトが抽出されない: 上記の実装後、実際のMiniMax H3出力mp4でPromptタブを確認したところ「プロンプトなし」と表示される問題を発見。原因はMiniMaxH3ImageToVideoのような、CLIPTextEncode系ノードを介さずノード自身のprompt入力に直接テキストを持つ「動画生成オールインワンノード」を、既存のisTextEncoderNode()判定(クラス名にTextEncodeを含むかで判定)が拾えていなかったこと。extractPromptsAPI()(metadata-tab.js・node_sets_menu.jsの両方)に、クラス名がImageToVideo/TextToVideoを含みinputs.promptが文字列のノードをtextMapへ直接投入する分岐を追加して解決(positive/negativeの区別が無いノードのため、フォールバックのtextsバケットに載る)。

6. ヘルプ更新: Gallery Tab(Infoタブの寸法/再生時間表示、Formats行のMP4追加)、Metadata Tab(Supported Formatsカード)、Workflow Studio Library / Iタブ(サイドパネル)のヘルプ本文を英語・日本語・中国語の3言語(i18n.js)+ templates/index.htmlで更新。

検証: Python側はGalleryServiceをvenv Pythonで直接呼び出すE2Eテストと実mp4へのffprobe/PyAVでの突き合わせ、JS側のMP4パーサーとプロンプト抽出ロジックはNode.jsで実バイト列/実JSONに対する単体テストで検証。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイル(gallery_service.py, gallery-tab.js, metadata-tab.js, node_sets_menu.js, i18n.js, templates/index.html, README.md, pyproject.toml)を両フォルダへ同期し改行コード差異を除いたdiffで完全一致を確認。ComfyUI実機でのブラウザ操作確認はユーザー側で実施済み(Gallery Prompt/JSON表示、サイドパネルIタブとも問題なしとの回答)。

関連: project_v050_video_tab


v0.5.0

機能追加: mp4動画Gallery対応 + Videoタブ新設(MiniMax H3専用) + バグ修正: Galleryのタグ/メモが再起動後に消える

動画生成ワークフロー対応の第一弾。「1. 動画ファイルmp4対応 2. 動画生成に対応していきたい。まずはminimax_H3動画生成ワークフローに対応させたい」との要望を起点に、実機フィードバックを重ねながら仕上げた一連の作業。ブランチfeature/mp4-video-gallery-and-video-tabで実施。

1. Galleryタブのmp4動画対応: .mp4をIMAGE_EXTENSIONSに追加し、一覧・お気に入り・タグ・グループ・移動・削除・エクスポートなど既存の画像操作全てにmp4も対象として乗るようにした。サムネイルはPillowで開けないため、PyAV (av) で先頭フレームを抽出しJPEGとしてディスクキャッシュする専用ロジックを追加(ComfyUI本体が既にav>=17.0.0を要求しており追加インストール不要。未導入環境ではサムネイル生成のみ失敗しプレースホルダー表示にフォールバックする設計)。フロントエンドは詳細パネル・lightbox・Compareビューで拡張子に応じて<img>/<video controls>を出し分け、サムネイルグリッドには▶バッジを重ねて動画であることを示す。

2. 新規Videoタブ(MiniMax H3専用、GenerateUIタブとは完全に独立): C:\Users\...\ComfyUI_5\user\default\workflows\minimax_test.jsonをリファレンスに、first_frame/last_frame/prompt/aspect_ratio/megapixels/multiple/duration/noise_seedのフォームを持つ新規タブvideo-tab.jsを作成。モデル選択(UNet/CLIP/VAE/Audio VAE)はワークフローの既定値に固定し編集不可とした(ユーザー確認済み)。comfyWorkflow.convertUiToApi()が既にサブグラフのflatten処理を内蔵していたため、Videoタブ独自のグラフ→プロンプト変換は不要で、生成そのものは既存のcomfyUI.generate()をそのまま再利用できた(ComfyUI本体のSaveVideo/PreviewVideoが{"images":[...], "animated":true}という画像と同じレスポンス形式で動画を返すため)。

3. 実機フィードバックへの追随(ボタン配置・色・レイアウト): 一連の実機確認を経て、(a) Galleryの詳細パネルのダウンロードオーバーレイが動画の再生コントロール操作をブロックするため廃止し、Taggerボタン右隣に常時表示の「Download」ボタン(識別しやすいようオレンジ色)を新設、「Load GenUI」ボタンは「GenUI」に短縮、(b) Workflowタブのモーダルにあった「生成UIに読み込み」「Videoに読み込み」「キャンバスへ送る」の3ボタンを廃止し、一覧選択時のサイドパネルツールバー(生成UIに読み込みの右隣に新規追加した「Videoに読み込み」)へ統合、(c) Videoタブの結果パネルの位置・サイズを繰り返し調整 — 最終的に3ペイン構成(左:フォーム/中央:プレビュー+将来の編集機能用プレースホルダー/右:将来のプロパティ機能用プレースホルダー)に落ち着いた。各ペインを独立したflexカラムにしたことで、中央の生成結果の高さがどれだけ変わっても左右のペインの位置が動かなくなる(max-heightだけで高さを抑えていた前段の実装では、動画のコントロールバー分だけ実高さが伸び、下のフォームパネルを押し下げてしまっていた)。Kapture経由で実際のブラウザDOM(getBoundingClientRect())とスクリーンショットを直接確認しながら調整した。

4. First Frame/Last Frameを両方optionalに、LoadImageノードが無いワークフローにも対応: 「First Frameなしでも動作させたい(ノードをバイパスで処理)。またノードのないワークフローにも対応させたい」との要望。minimax_test1.json(first_frame/last_frame双方とも未接続、対応するLoadImageノードがトップレベルに存在しない構成)を新たに確認し、locateMiniMaxNodes()のLoadImageノード必須チェックを撤廃。first_frame未指定時はリンクなしのまま実行(バイパス)し、画像が指定された場合のみその場でLoadImageノードを動的に注入する設計に変更(ノードID/リンクIDはclone内の実際の最大値+1で採番するため重複の心配がない)。first_frameスロットにもlast_frameと同様のClearボタンを追加した。

5. バグ修正: mp4のタグ/メモがComfyUI再起動後に消える: 「タグ、メモadd、保存操作はできるが記録されていない。画像ファイルもできていない。エラー表示はなし、ブラウザもなし」との報告を受け、comfyui_8189.logを直接確認したところCleaned up 189 stale metadata entries from ...\Text2Imgという決定的なログを発見。根本原因: cleanup_stale_images()は「現在表示中のフォルダ配下」のメタデータキーを全階層(サブフォルダ含む)対象にstale判定するが、Galleryがルートフォルダを非再帰スキャンした場合、existing_paths(現在のファイル一覧)にはルート直下のファイルしか含まれない。Videoタブで生成した動画はSaveVideoのfilename_prefix設定でvideo/MiniMax_H3/というサブフォルダに保存されるため、ルートフォルダを開くたびに「サブフォルダ内ファイルは非再帰スキャンには映らない→存在しないと誤判定→メタデータ削除」が発生し、189件ものタグ/メモ/お気に入り情報が消えていた。cleanup_stale_images()にrecursive引数を追加し、非再帰スキャン時はfolder_path直下のみを対象にするよう修正(gallery_service.pyのlist_images()からrecursiveをそのまま伝播)。既に削除済みのデータはバックアップが無く復元不可能だった旨をユーザーに伝えた。

  • 副次的に発見したフロントエンドのバグも合わせて修正: saveMetaField()がサーバーのレスポンス({"ok": bool})を一切チェックせず常に成功したかのようにローカル状態を楽観的更新していたため、サーバー側で保存が拒否されてもUI上はエラーなく成功したように見えてしまう作りになっていた。レスポンスのokフィールドを検証し失敗時は例外を投げるよう変更、addTag/removeTag/メモ保存ハンドラは戻り値を見て成功時のみローカル状態を更新するよう修正した。保険的措置としてsave_image_meta(バックエンド)の保存キーもget_image_metadataと同じresolve()後のパスに統一した。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、新規Video Tabヘルプページを追加。README.mdはバージョンバッジ更新、Workflow Tab(サイドパネルツールバーの3ボタン構成)、Gallery Tab(Downloadボタンの新配置・mp4対応・GenUIボタンへの改名)、新規Video Tabセクション(3ペイン構成・optional First/Last Frame・ノードなしワークフロー対応)を追加した。

検証: 全JS/Pythonファイルをnode --check/ast.parse()で構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更した全11ファイルを都度同期しmd5ハッシュ比較で完全一致を確認、Pythonファイル変更時は__pycache__を全クリア。Kapture(ブラウザDevTools連携)で実際のブラウザに接続し、DOM要素のgetComputedStyle()/getBoundingClientRect()とスクリーンショットで3ペインレイアウトの実測検証、生成後状態はダミー画像をDOMに直接注入して左右ペインが動かないことを確認。mp4のGallery表示・再生、タグ/メモ保存とComfyUI再起動後の永続性、Workflowタブの新規ボタン導線はユーザー自身の実機で確認済み。

How to apply: サーバーAPIが常にHTTP 200を返し成否をレスポンスボディのokフィールドで表す設計(このプロジェクト全体で頻出するパターン)では、呼び出し側で必ずそのフィールドをチェックする — でないと「操作はできるがエラーも出ず、実は保存されていない」というサイレント障害が発生し得る。複数のパネルを横並びに配置するレイアウトで「あるパネルの内容量に応じて他のパネルの位置がずれる」不具合が出た場合、max-heightのような上限指定では実コンテンツが小さいときに縮んでしまい根本解決にならない — 各パネルを独立したflexカラムに分離し、パネル間で高さを共有させない構造にするのが確実。「フォルダ配下」を対象にした一括処理(今回のstale cleanup)を書く際は、その処理が使う「現在のファイル一覧」がフォルダの全階層をカバーしているか(非再帰スキャンならサブフォルダは含まれない)を必ず確認し、範囲の不一致がないか意識する。ブラウザで実際に何が起きているか切り分けが必要な場面では、コードを読むだけでなくKapture等でDOMの実測値を直接取得するのが最も確実。

関連: project_dev_deploy_sync, project_v0336_conventions


v0.4.8

機能追加: LabタブにPromptグループ一括適用+キーフレーム並べ替え + バグ修正: デフォルトワークフローの上書き保存が反映されない + Styleテーブルの「このファイルへ追加」対応

ユーザーからの複数の要望・不具合報告を1セッションで順に実装した一連の作業。

1. LabタブPrompt列に「PG」ボタン — Promptタブのグループをキーフレームへ一括変換: 「生成UIタブのLabのプロンプトにプロンプトタブのグループを使用できるようにしたい。'PG'ボタンを配置、モーダルでプロンプトのグループを選択、決定するとプロンプトのキーフレームが#1から自動で作成されます(#1は上書き)」との要望。prompt-presets.jsのpmGroups(グループ名→プリセットID配列)を参照するgetPresetsInGroup()を新設してexportし、lab-tab.js側にPresetManagerの一覧UI(.wfm-pm-item)を再利用したグループ選択モーダルを実装。選択→適用で_lab.columns.promptをグループ内プリセット順に#1から全置換(#1は上書き、以降は#2,#3...)する。グループのプリセット数がBatch数を超える場合はBatch数を自動引き上げ(Batch入力欄自体の上限50でキャップ)、それでも収まらない分は既存の「Batchを超えたキーフレーム」警告トースト(_warnIfKeyframesExceedBatch)をそのまま再利用して通知する形にした。

2. Lab: Model/Prompt/KSampler キーフレームの並べ替え: 続けて「モデル、プロンプト、Ksamplerそれぞれでキーフレームの順変更を可能にしたい。キーフレームのモーダル展開をWクリックに変更しシングルクリックでキーフレームを選択、PGボタン左隣に上下ボタンを設置し操作可能にしたい」との要望。キーフレームセルのクリック挙動を分離し、シングルクリックは選択状態(列ごとに_selectedIdx[col]で管理、青枠でハイライト)、ダブルクリックで従来の編集モーダルを開くように変更。各列ヘッダーに▲/▼ボタンを追加し(Promptは要望通りPGボタンの左隣)、選択中のキーフレームと隣接するキーフレームのatIterationを入れ替えることで並び順を変更する実装にした。キーフレーム#1(ベースライン — GenerateUIのライブ値を表示する特別な行)は移動不可、かつ他のキーフレームが#1の位置に入れ替わることもないよう制約した。Prompt Group適用・Plan読込・Plan Clearなど列を丸ごと置き換える操作では選択状態を自動リセットするようにし、配列外を指す不整合を防いだ。

3. バグ修正: デフォルトワークフローに設定した状態で同名上書き保存すると変更が反映されない: 「生成UIタブで読み込んだデフォルトワークフローのモデルを変更しワークフローを同名で上書き保存した。ワークフロータブでJSONでモデルが変更していることを確認。ブラウザハード再読み込みを行うと以前のモデルが表示される」との不具合報告(スクリーンショット添付、旧モデル名がインストール済みモデルの一覧に無く「見つかりません」警告が出ている状態)。調査の結果、「デフォルトワークフローに設定」機能(workflow-tab.js)は設定した時点のワークフローJSON全体をlocalStorage(wfm_settings.defaultWorkflowData)へスナップショットとして保存し、起動時の自動読込(generate-tab.js)はこのスナップショットを読む仕様であることが判明。GenerateUIタブでの同名上書き保存はファイル自体は更新するが、このスナップショットまでは更新していなかったため、リロードのたびに保存前の古い内容が復活していた。saveCurrentWorkflow()の保存成功後処理に、保存したファイル名が現在のdefaultWorkflowと一致する場合はdefaultWorkflowDataも同時に更新する処理を追加して修正した。

4. Prompt Tab Tableビュー Styleタブに「+ Add to this file」対応: 「プロンプトタブ、テーブルのスタイルにForm同様に既存のjsonファイルに追加する方法が欲しい(custom.json以外の場合)。該当jsonのスタイルを選択状態で+ Add to this fileボタンで追加できるようにしたい」との要望。Formタブのスタイルマネージャー(prompt-styles.js)には既にこの機能があったが、Tableビュー(prompt-table.js)の新規スタイル作成は常にcustom.json固定(styleApiCreate(..., null))だった。行選択が1件かつそのスタイルのfileがcustom.json以外の場合のみ有効化される「+ Add to this file」ボタンをツールバーに追加し、クリックすると保存先ファイルを保持したまま空の新規行を追加(File列に保存前からバッジ表示)、保存時にstyleApiCreate(name, pos, neg, pendingNewStyleFile)で対象ファイルへ追記する実装にした。バックエンド(settings_routes.py)は元々fileパラメータでの追記に対応済みだったため、フロントエンドの導線を追加するだけで実現できた。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、Lab subtab(Setting/Columnsカードへ選択・並べ替えとPGボタンの説明2項目)とPrompt Tab(Table Viewカードへ「+ Add to this file」の説明1項目)を追加。README.mdはGenerateUI Tab(Saveボタン説明にデフォルトワークフロースナップショット修正の注記)、Lab subtab(見出しのバージョン範囲をv0.4.8へ更新、選択/並べ替えとPGボタンの新規項目)、Prompt Tab(Styleテーブルの項目に「+ Add to this file」のTableビュー対応を追記)を更新した。

検証: 全JSファイルをnode --checkで構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルごとに改行コード(CRLF/LF)の一致を確認してから同期し、都度diffで完全一致を検証した。デフォルトワークフローのバグ修正はユーザー自身の実機(GenerateUIでの上書き保存→ハード再読み込み)で確認済み。PGボタン・キーフレーム並べ替え・Styleの「+ Add to this file」もユーザー自身の実機確認で動作を確認済み。

How to apply: 開発元リポジトリと実行時custom_nodesフォルダへの同期前には必ずfileコマンドで両ファイルの改行コードが一致しているか確認する(過去に一部ファイルだけLF化していた事例があるため — project_dev_deploy_sync参照)。一致していれば単純なcpで安全にデプロイでき、diffで完全一致を最終確認する。キーフレームや行の「並べ替え」機能を実装する際、内部的にソート順を保持する配列(今回はatIteration昇順)に対しては、配列そのものの並べ替えではなく「隣接要素同士でソートキーの値を交換する」実装にすると、他のロジック(_resolveKeyframe等)が前提とする「配列は常にソート済み」という不変条件を崩さずに済む。

関連: project_dev_deploy_sync, project_v0336_conventions


v0.4.7

ユーザーからの複数の要望を1セッションで順に実装した一連の作業。

1. graphifyでの掘り下げを起点にprompt-tab.jsを分割: graphifyの知識グラフでコードベースを掘り下げた結果、prompt-tab.js(1545行)がPreset管理・AI Chat・Wildcard管理・Style管理の4つの独立したクラスタで構成されていることが判明。prompt-presets.js/prompt-ai-chat.js/prompt-wildcards.js/prompt-styles.jsの4ファイルに分割し、prompt-tab.js自体は各モジュールを結線するだけの薄いオーケストレーターにした。prompt-table.js/app.jsは無変更(prompt-tab.jsからのre-export経由で既存のimportをそのまま維持)。

2. テーブルタブのWildcard入力支援ツールバー、実は配置ミス: 「テーブルタブのワイルドカードのファイルがtxtの場合、モーダルにフォームの入力支援ボタンを表示したい」との要望を受け実装したが、直後にユーザーから「ワイルドカード構文はワイルドカードのモーダルに追加するのは誤りでプロンプトに追加するのが正しい気がします」との指摘。Form側の既存設計(構文ボタンはWildcard Composerにのみ存在し、ファイル中身のエディタには無い — ファイルの中身は単なる候補リストという前提)に照らして誤りだったと確認し、Presetsのプロンプト編集モーダルへ移設した。ワイルドカードツールバーはprompt-wildcards.jsのcreateWildcardToolbar()として再利用可能な形に切り出し、Form側の静的ツールバーとテーブル側の動的モーダルの両方から使えるようにした。Styleモーダルには適用しない(Form側でもWildcard ComposerとStyleの連携導線は存在しないため)。

3. Style×Wildcard展開順序のバグ修正: 「スタイルにワイルドカード構文を使用しても使用時プロンプトに挿入されるので動作しますか?」との確認依頼から調査を開始。generate-tab.jsの_coreGenerate内でワイルドカード展開(_expandWildcardsInWorkflow)がStyle適用(_applyStyleToWorkflow)より先に実行されていたため、Style文字列内に書いた__file__参照が展開されないまま生テキストのままComfyUIへ送信される不具合を発見。実行順序をStyle適用→ワイルドカード展開に入れ替えて修正。実際にComfyUIで生成を実行し、comfyUI.generateを一時的にモンキーパッチして送信直前のペイロードを確認、Style由来の__flower__が"flower, gardenia scent"のように正しく展開されることを実証した。

4. yamlワイルドカードを使うサンプルワークフロー: 「yamlファイルのワイルドカードを使用するワークフローを作成してもらうことはできますか」との依頼。調査の結果、このプラグイン自体のワイルドカード展開(SPA側の_expandWildcardsInWorkflow)は.txt拡張子固定でyaml非対応であり、yaml形式はImpactWildcardEncode/ImpactWildcardProcessorノード(Impact Pack、サーバー側処理)でのみ解決できることが判明。プラグインのwildcardディレクトリはjunction経由でImpact Packのwildcards/フォルダへ直結しているため、通常のWildcard File Manager経由で保存したyamlファイルがそのままImpactWildcardEncodeから参照可能。サンプルyamlワイルドカードファイル(ws_sample_season_lighting.yaml)とImpactWildcardEncodeノードを使うサンプルワークフロー(ws_yaml_wildcard_sample.json)を作成し、実際にComfyUIで生成→出力PNGのメタデータでpopulated_textが階層キー参照から正しく展開されていることを確認した。

5. Galleryタブ詳細パネルに「Prompt」タブ新設: 「メタデータでも確認できるが頻度を考えるとマウス操作が多いため、プロンプトだけはこちらでも表示としたい。ボタンは右端でデフォルトで選択状態としたい」との要望。当初は単純な全文連結表示で実装したが、「メタデータタブの表示の様に複数プロンプトがあるケースも考慮したい」とのフィードバックを受け、MetadataタブのextractPrompts/buildPromptItemをexportし、完全に同じPOS/NEGバッジ付きリスト→クリックで全文表示のUIに作り直した。

  • さらに実機確認の中で、同一画像でもMetadataタブとGalleryタブでpositive数が食い違うケース(Krea2_turbo_00133_.png、Metadataでは2件のPositiveが見える)を発見。原因は埋め込みデータの優先順位の違い — Metadataタブはprompt(API形式、サブグラフ展開済みでフラット)を優先するのに対し、Gallery側サーバーAPIはworkflow(UI形式、ノード位置等を保持)を優先していた。トップレベルとサブグラフに独立した2系統のサンプリングを持つワークフローでは、UI形式のトップレベルnodes配列に一方の系統しか含まれず、extractPromptsLiteGraphの早期returnロジックがその系統しか拾えないため。既存のLoad GenUI/JSON表示/Copy & Send Canvas等には一切影響しないよう、gallery_service.pyに優先順位を選べる_extract_embedded_workflowヘルパーを追加し、新規extract_prompt_workflow_from_metadata(API形式優先)を/wfm/gallery/image/workflowのレスポンスへprompt_workflowとして追加、Galleryの Promptタブの抽出だけこちらを使うようにした。Python側の変更を反映するため、ユーザー自身の手でComfyUIを再起動していただいた。

6. Send to Canvasのフォールバック方式を刷新: 「このワークフローを生成UIタブに読み込むと...またキャンバスへ送るでAPI形式のためタイトルをキャンバスへドラッグのメッセージが表示されますがこの動作を変更したい...UI形式のサムネイルがないパターンのワークフローも同様にしたい」との要望。window.opener経由の直接ロードに失敗した場合の従来のフォールバック(localStorage+パネルタイトルのドラッグ、API形式のワークフローはそもそも非対応でエラーのみ)を、Workflow Studio LibraryのW(Workflows)タブを該当ワークフローで絞り込み表示する方式に統一した。node_sets_menu.jsにshowWorkflowInLibrary()をexportし、top_menu_extension.jsが新規localStorageキーwfm_library_filter_requestへのstorageイベントを監視して呼び出す(同一オリジンの別タブでのみ発火する仕様を利用)。フォーマット(UI形式・API形式)を問わず同じ導線になり、サムネイル付きの実物のワークフローカードからユーザー自身がドラッグできるようになった。Galleryタブの「Copy & Send Canvas」(画像埋め込みのその場限りのワークフローが対象で、そもそもLibrary一覧=保存済みファイルには存在しない)は今回のスコープ外として変更していない。

7. Presetsテーブルに★/Bソート機能: 「プロンプトタブのテーブル(プリセット)で上部メニューに★とBボタンによるソート機能を追加したい」との要望。ツールバーに★(favorite)/B(Batch選択済み)のトグルボタンを追加し、該当する行を先頭にソートする機能を実装。両方同時にONにでき、★を主キー・Bを副キーとした複合ソートに対応、該当しない項目同士は安定ソートで元の相対順序を維持する。ソート有効時に行内の★/Bボタンで状態を変更すると即座に再ソートされる。

8. graphifyの運用を一時停止: ユーザー指示によりCLAUDE.md内の「graphify」セクション全体をコメントアウトし、明示的な指示があるまでコード変更後のgraphify updateは実行しないことにした(graphify-out/自体は削除せず保持、再開時にコメントを解除するだけで元に戻せる)。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、Prompt Tab(Table Viewカードに★/Bソートボタンの新規項目)を更新。README.mdはGenerateUI Tab(Style×Wildcard展開順序修正)、Prompt Tab(Presetsモーダルのワイルドカードツールバー、★/Bソート)、Gallery Tab(新規Promptタブ)、Workflow Studio Library(Send to CanvasのWタブフィルタ方式)を更新。

検証: 全JS/Pythonファイルをnode --check/ast.parse()で構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルを都度両フォルダへ同期。Playwright MCP経由で実機ブラウザ操作を機能追加・修正ごとに実施: (1) prompt-tab.js分割後のPreset/AI Chat/Wildcard/Styleパネル全機能の回帰確認、(2) Wildcardツールバー移設前後でのモーダル表示・非表示の確認、(3) Style×Wildcard展開の実生成確認、(4) yamlサンプルワークフローの実生成とメタデータ確認、(5) Gallery Promptタブの複数プロンプト表示とprompt_workflow切替の確認、(6) Send to Canvasの別タブ間storage連携の実機確認、(7) Presetsテーブルの★/Bソート・複合ソート・安定ソートの確認。テスト用に作成したデータ(一時Style、yamlワイルドカードファイル等)はテストワークフロー用途のものを除き削除・復元済み。

How to apply: 開発時は graphify query "<question>" を使わず通常の Grep/Read/Explore で調査する(graphify運用停止中、明示的な再開指示があるまで)。ワイルドカード構文({|}, __|__, <lora>等)は「プロンプトを書く場所」に対する入力支援であり、「ファイルの中身を編集する場所」には付けない、という区別を今後の入力支援UI追加でも踏襲する。ワークフローの実行順序に依存するバグ(Style適用とワイルドカード展開の順序等)は、実際にComfyUIへ送信される直前のペイロードをモンキーパッチや/historyのPNGメタデータで確認するのが最も確実な検証方法。


v0.4.6

機能追加: MetadataタブをGalleryタブへ統合 + Promptタブに一覧編集可能なTable表示を新設

ユーザーからの複数の要望を1セッションで順に実装した一連の作業。

1. MetadataタブをGalleryタブのサブタブへ移動: 「Metadataタブをギャラリータブ内に移動したい。Style_Catalogタブ右隣としたい」との要望。トップナビの独立タブを廃止し、Galleryタブのサブタブ列(Output/ImagePrompt/Style_Catalog)の右端に追加。外部から画像をMetadata表示させるloadFileIntoMetadataTab()(metadata-tab.js)や、Helpタブの「Open tab →」ジャンプボタンなど、旧トップレベルタブへの参照箇所(Gallery側の「Open in Metadata Tab」ボタン、Style_Catalog側の同名ボタン)はすべて「Galleryタブへ切替→Metadataサブタブへ切替」の2段階クリックへ置き換えた。

2. GenerateUIタブ: Save ボタンをBatch/Labサブタブで非表示化: 「ワークフローのSaveボタンをBatch、Labサブタブ選択時、非表示にしたい」との要望。両サブタブは現在ロード中のワークフロー本体ではなく、ワークフローの複製やLab独自のPlanファイルを操作するため、Saveボタンが誤解を招く(クリックしても実際に触っているワークフローとは別物が保存される)ことへの対処。サブタブ切替ハンドラ(generate-tab.js)にワンライナーの表示切替を追加。

3. Promptタブに「Table」表示を新設: 「現在のプロンプトタブをタブ化してテーブルタブを追加したい(既存はフォームタブとする)。テーブルタブではプロンプトのプリセット、ワイルドカード、スタイルがそれぞれ表形式で一覧表示、編集可能としたい」との要望を起点に、複数回のフィードバックを経て以下の形に発展した:

  • 既存の3カラムレイアウト(AI Assistant / Presets・Preset Manager / Wildcard・Style)をまるごと「Form」パネルとしてラップし、新設の「Table」パネルと上部のトップレベルタブで切り替える構成にした。
  • Tableパネル内にPresets・Wildcards・Styleの3つの編集可能テーブルを新設(prompt-table.js新規ファイル)。既存のprompt-tab.jsが保持するAPIヘルパー(fetchPresets/apiUpdatePreset/wcSaveFile/styleApiUpdate等)とリフレッシュ関数をexportして再利用し、Table側での編集は即座にForm側にも反映されるようにした。
  • 「Presets右隣にPresets Groupを追加したい」: グループ管理専用の4つ目のサブタブを新設。グループの作成・リネーム・削除・メンバー一覧(バッジ表示・×で削除)に対応。
  • 「共通で左端に番号(自動)列を追加したい。個別の削除ボタンは誤操作が予想されるため上部にボタンを設置し番号列で該当番号選択、削除という手順にしたい」: 4テーブル共通で自動採番の#列を新設し、番号クリックで行選択(複数可・赤ハイライト)→ツールバーの「Delete」ボタンで一括削除、という手順に統一。個別の行削除ボタンは全廃した。新規行追加時は番号セルの代わりに小さな✓(Add)/✕(Cancel)アイコンを表示する設計にした。
  • 「バッチグループの登録方法を変更したい」: PresetsテーブルにForm側と同じ仕組みの「B」列(クリックでグレー⇔黄色にトグルしBatchグループへ登録/解除)と、Batchグループを一括クリアする「BC」ボタンをツールバー上部に追加。Presets Groupテーブルからは予約グループ「Batch」を非表示にし、Batch管理をPresetsテーブル側のB列/BCボタンへ一本化した。
  • 「PresetsのコピーボタンとグループドロップダウンをテーブルのPresetsタブに配置したい。これによりPresets GroupタブのAdd Presetsをなくしたい(プリセットが多くなった際に選択が困難なため)」: Presets Groupテーブルの「Add Preset」列(プリセットが多いと使いづらい長大なドロップダウン)を廃止。代わりにPresetsテーブルのツールバーに「PP Copy」「NP Copy」(選択中1件のPositive/Negativeをクリップボードへコピー)と、グループ選択+「Add to Group」(選択中の複数プリセットを一括で選んだグループへ追加)を新設した。プリセット側を検索・選択してから短いグループ一覧から選ぶ流れにしたことで、プリセット数が多くても操作しやすい設計になった。
  • 「選択クリアボタンも追加したい」: 4テーブル共通で「Deselect」ボタンをツールバーへ追加し、選択状態を一括解除できるようにした。
  • 「プリセット、ワイルドカード、スタイルで番号をWクリックでモーダルでプロンプトの表示、編集したい」: #列のダブルクリックで、既存の共有モーダル基盤(app.jsのopenModal/closeModal)を再利用したプロンプト編集モーダルを開くようにした。Presets/Styleは正負プロンプト切り替えボタン+共有の大型テキストエリア(切り替えても両方の編集内容を保持)、Wildcardsはファイル内容のみのテキストエリアで、Save/Closeボタンを備える。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、GenerateUI Tab(Saveボタン非表示の説明)・Prompt Tab(新規カード「Form / Table」「Table View」10項目)・Gallery Tab(Metadataサブタブの説明)・Metadata Tab(Galleryタブ内へ移動した旨の注記、metadata-tab.js側の独自i18n適用ロジックへ追加)を更新。README.mdはGenerateUI TabのSaveボタン説明を拡張、Prompt Tabセクションに新規ブロックを追加、「Metadata Tab」セクションをGalleryタブの4番目のサブタブとして「Metadata Gallery subtab」に改称の上、Style_Catalog Gallery subtabの直後へ移設した。

検証: 全JSファイルをnode --checkで構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルを都度両フォルダへ同期。Playwright MCP経由で実機ブラウザ操作を機能追加ごとに実施: (1) Gallery→Metadataサブタブの表示とHelpジャンプボタンの遷移、(2) Batch/LabサブタブでのSaveボタン非表示、(3) Presets/Wildcards/Styleテーブルの実データ表示・インライン編集の永続化・行追加・削除、(4) 番号列での選択トグルとDelete/Deselectボタンの有効化、(5) Presets GroupタブでのグループCRUD・メンバー追加削除・予約グループBatchの削除保護、(6) B列/BCボタンでのBatchグループ登録・一括クリア(既存の"pony"データが誤って変更されないことも確認)、(7) PP/NP CopyとAdd to Groupの単一選択/複数選択それぞれの動作、(8) ダブルクリックモーダルの表示・Positive/Negative切り替え時のテキスト保持・保存→サーバー反映→モーダル自動クローズ、(9) ヘルプ全項目の日本語UIでの表示、を一通り確認。テスト用に作成したデータはすべて削除・復元済み。

How to apply:

  1. 1つの機能要望(テーブル表示の追加)が実装後の実運用フィードバックを経て、削除方式・グループ登録方式・選択解除・詳細表示方式と段階的に発展していくケースでは、各段階で「既存の確立済みパターン(今回は#列クリック選択→ツールバー操作、共有モーダル基盤)を後続の要望にも一貫して適用できないか」を優先的に検討すると、機能ごとにUIパターンがバラつかず学習コストが低いまま拡張できる。
  2. 一覧が長大になりうるデータ(プリセット)をドロップダウンで選ばせるUIは、件数が増えると事実上使えなくなる。件数が少ないことが保証されている別の軸(今回はグループ)を主語にした選択UIへ倒すと、データ量に強い設計になる。
  3. 複数テーブルにまたがる共通パターン(番号列・選択トグル・ダブルクリックモーダル)は、テーブルごとに実装をコピーするのではなく共通ヘルパー関数(buildNumCell/openPositiveNegativeModal/openSingleTextModal)に切り出しておくと、後続の類似要望(今回で言えば選択クリアボタン・ダブルクリックモーダル)を全テーブルへ一度に反映しやすい。

関連: project_dev_deploy_sync, project_v0336_conventions


v0.4.5

調査: トップバー保存ワークフローのRMBG不具合報告(原因なし) + バグ修正: LabタブModel列の無意味な空キーフレーム表示を解消

ユーザーから「トップバーで保存した2つのワークフロー(I2I_rmbg.json/I2I_rmbg_2.json)を生成UIタブで読み込んで生成するとRemove Background(RMBG)ノードが機能せず透過画像が作成されない。ComfyUIのExport(API)で保存した3つ目のファイルは正しく動く」との報告を受けて調査した一連のセッション。

1. ワークフロー解析の調査: RMBGノードはbackground_colorという「ウィジェットを持たない特殊な入力(COLORCODE型、modelより前の位置)」を持つ特徴的な構造で、UI形式→API形式変換時にwidgets_valuesの対応がずれる可能性を最初に疑った。comfyui-workflow.jsのconvertUiToApi()を段階的に検証:

  • /object_info/RMBGを実機から取得し、_getWidgetInputNames()がforceInput相当(widgetキーを持たない入力)を正しく除外し、background_colorがウィジェット名リストから漏れることを確認。
  • ユーザー提供のI2I_rmbg_2.jsonをブラウザ内(Playwright MCP)で実際にconvertUiToApi()にかけ、変換結果を確認 — model/sensitivity/backgroundなど全フィールドが正しくマッピングされていた。
  • その変換結果を実際に/promptへキューイングして実行、さらにGenerateUIタブの本物のGenerateボタンをクリックする経路でも実行 — いずれも出力PNGはRGBA・アルファチャンネル0〜255の正しい透過画像だった。
  • 結論: 現在デプロイされているv0.4.3のワークフロー解析ロジックに該当バグは再現せず、コード変更は行わなかった。報告時点でより古いバージョンを使っていた可能性が高いと判断し、ユーザーに調査結果と再検証手順を報告した。

2. Labタブ: モデルの無いワークフローで無意味なキーフレームが作成される問題: 調査の副産物として、RMBGのみ(Checkpoint/LoRA/VAEいずれのノードも無い)ワークフローをLabタブで開くと、Model列に「1: (ワークフローの設定のまま)」という空のキーフレーム行が常に表示されてしまうことを実機で発見。ユーザーから「不要なため作成しない形にしたい」との要望を受けて修正。_buildWorkflowForIteration()は対象ノードIDが存在しない場合、書き込みを黙ってスキップするだけ(write()のwf[nodeId]ガード)なので、この行はRun時に何の効果も持たない死んだコントロールだった。_hasNodeForColumn(col)を新設し、_renderColumn()と_initColumnButtons()の+ボタンで判定 — 対応ノードが無い列は「このワークフローに該当ノードがありません」というプレースホルダーを表示し、キーフレーム追加もブロックする。VAEだけは特殊扱いで、専用VAELoaderが無くてもCheckpointノードが存在すれば内蔵VAEの上書き注入が意味を持つため(既存の_buildWorkflowForIterationのVAELoader自動注入ロジック)、Checkpoint存在時はVAE列も通常表示のままにした。

ヘルプ・README更新: project_v0336_conventionsの慣習に従い、i18n.jsに新規メッセージキーlabNoNodeForColumnを日本語/英語/中国語すべてに追加(このケースは既存キーワードで賄えないUI状態のため新設。ヘルプページ本文の更新は対象外の細部挙動のため省略)。README.mdのLab subtabセクションに「Empty state when no matching node exists (v0.4.5)」の項目を追記し、見出しのバージョン範囲をv0.3.87 – v0.4.5に更新した。

検証: Playwright MCP経由で実機ブラウザ操作: (1) RMBGのみのモデル無しワークフローを読み込みLabタブへ切り替え、Model列(Checkpoint/LoRA/VAEいずれのビューでも)が空プレースホルダーになることを確認、(2) Checkpoint+VAELoaderを含む通常のワークフローでは従来通りライブ値付きキーフレームが表示され回帰が無いことを確認、(3) 同じworkflow内でLoRAノードだけが無いケースではLoRAビューのみ正しく空プレースホルダーになることを確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイル(lab-tab.js/i18n.js/lab-tab.css)を両フォルダへ同期しdiffで完全一致を確認した。

How to apply:

  1. 「機能しない」という不具合報告を受けた際は、コードを読んで理屈上正しそうに見えても、実機(今回はPlaywright MCP経由の実ブラウザ+実ComfyUI)で変換結果と生成画像の中身(アルファチャンネルの実値など)まで直接検証してから結論を出す。理屈の検証だけでは「今のバージョンでは再現しない」という事実に辿り着けなかった。
  2. 「機能しない」の調査中に見つかった別の実害あるバグ(今回のLabタブの死んだキーフレーム行)は、本題の調査がクローズしても見逃さず、その場でユーザーに報告し必要なら合わせて修正する。
  3. UIに「設定できるが実行時には何の効果も持たない」コントロールを残すと、ユーザーは正常に設定できていると誤認する。対象データが存在しないなら、コントロール自体を出さない(無効化ではなく非表示)のが最も誤解を生まない。

関連: project_v042_lab_model_column, project_dev_deploy_sync


v0.4.4

機能追加: Lab/GenerateUI ModelタブへLoRAバイパス機能 + AI TOOL Chatの画像生成パラメータ拡張

ユーザーから「生成UIタブのLab機能のLora、ModelタブのLoraのLoraノード自体をバイパスする機能を追加したい。Lab機能ではキーフレームごとに切り替え対応可能としたい」との要望を受けて着手。同じセッション内で続けて「Lab機能のLoraにLora syntax、Trigger wordsの適用を可能にしたい」「AI TOOLタブのCHATの画像生成でチャットによる設定項目を増やしたい」という要望にも順次対応した一連のセッション。

1. LoRAノードのバイパス機能: 実装方針の分岐点(ComfyUI本来のBypass(mode:4)相当の配線迂回か、strength=0にする簡易実装か)をAskUserQuestionでユーザーに確認し、簡易実装(strength=0)を選択。理由は、Lab/GenerateUIが扱うworkflowは既にcomfyui-workflow.jsのconvertUiToApi()でAPI形式に変換済みでmodeの概念自体が失われており、mode:4を書き込んでも実行エンジンには一切効果がないため——本物のBypass相当の配線再接続を自前実装するのは過剰投資と判断した。

  • GenerateUI Model タブ: LoRA列ヘッダーの「Refresh Stack group」ボタン左に⛔トグルボタンを追加(comfyui-editor.jsのtoggleLoraNodeBypass()/_loraNodeBypass/_loraBypassSnapshot)。対象ノードのstrength値をゼロ化し、退避したスナップショットから復元をトグルする。Lora Managerノードの場合はLoRAリスト自体(複数エントリ)は差し替えず維持したまま各エントリのstrength/clipStrengthだけを0にする。Single/Stack Applyボタンを押すとバイパス状態は自動クリアされ、あとから無言で再適用されることはない。
  • Lab機能: 既存の行単位「bypassed」(キーフレーム自体を無視するスキップ機能)とは独立した、LoRA列専用のnodeBypassフィールドをキーフレームの値に追加。編集モーダルに「LoRAノードをバイパス(強度を0にする)」チェックボックスを新設。_applyLabLoraToWorkflow()でnodeBypass時はstrength/active/textを0/false/空に強制する。

2. Lab: LoRA syntax / Trigger words のポジティブプロンプト適用: LoRA値にapplyToPromptフィールドを追加。ONにすると<lora:stem:strengthModel:strengthClip>とCivitAIキャッシュのtrainedWordsをポジティブプロンプトへカンマ区切りで追加する(comfyui-editor.jsのSingle Applyと同じフォーマット)。「次のキーフレームでLora変更時はクリアされた上で新たなloraで追加」という要件は、_buildWorkflowForIteration()にprevLoraInjection引数を追加し、前回のイテレーションが正確に注入した文字列だけを次回ビルド時に文字列一致で取り除いてから新規注入を追加する設計で実現した——正規表現による汎用パターン除去ではなく、自分が直前に足した分だけを覚えて消す方式なので、ユーザーが手で書いた他のプロンプト内容を誤って消す心配がない。metadata/civitaiCacheはRun開始時に一度だけフェッチしてキャッシュする。

3. AI TOOL Chat: generate_imageツールのパラメータ拡張: AskUserQuestionで追加したい項目をユーザーに確認し、KSampler系(steps/cfg/sampler_name/scheduler/denoise/seed)・画像サイズ(width/height)・生成枚数(batch_size)を選択。comfyui-editor.jsにsetGenerationParams()を新設し、Lab機能の_buildWorkflowForIterationと同じsampler_nodesのstepsNodeId/cfgNodeId等のノードID間接参照パターンを再利用することで、Advanced Samplingワークフロー(Flux.1/2、SD3.5等でKSamplerが複数ノードに分割される構成)にも対応した。seedはwindow._wfmGenerateTab.generate()(実体はhandleGenerate)へのオプション引数として渡すよう、generate-tab.jsのhandleGenerate/_coreGenerateにgenOptions引数を追加(既存の呼び出し元との後方互換は維持)。batch_size>1時は複数画像がチャットバブルに表示されるようappendChatBubbleにimageUrls(複数URL)対応を追加した。実機のOllama(ornith-1.5:9b)でツール呼び出しから画像生成までEnd-to-Endで検証し、指定した各パラメータが正しく反映されることを確認した。

4. Resolution Selectorノード対応: ユーザーから実際のワークフロー画像(aspect_ratio COMBO・megapixels・multipleウィジェットを持つ「Resolution Selector」ノード)を見せられ、「3:2などで指定可能か」と問われて調査。/object_info/ResolutionSelectorで確認したところComfyUI本体組み込みノード(comfy_extras.nodes_resolution)で、aspect_ratioは"3:2 (Photo)"のような固定ラベル付きCOMBO値と判明した。当初のwidth/height直接指定実装ではこの手のノードに非対応だったため、追加で以下を実装:

  • comfyui-workflow.jsのanalyzeWorkflow()にresolution_selector_nodesカテゴリを新設。
  • comfyui-editor.jsにresolveAspectRatioOption(nodeType, requested)を新設。LLMが渡す短い"3:2"形式を、/object_infoから取得した実際のCOMBO選択肢と前方一致でマッチさせ"3:2 (Photo)"のような完全な文字列に解決する(未知の比率はそのままフォールバックし、ComfyUI側の検証エラーに委ねる)。
  • setGenerationParams()にResolution Selector優先ロジックを追加:aspect_ratio指定時はEmptyLatentImageへのwidth/height直接書き込みをスキップし、ResolutionSelectorノードのaspect_ratioだけを書き換える——EmptyLatentImageのwidth/heightがResolutionSelectorの出力へのリンクになっている場合、直接数値を書き込むとそのリンクを上書きしてしまい、意図した比率指定が無視されてしまうため。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、helpLab16/helpLab17(Lab LoRAバイパス・プロンプト適用)、helpGen29(GenerateUI Model LoRAバイパスボタン)を新規追加し、helpAi3(AI TOOL Chatの説明)を拡張パラメータの説明を含めて更新した。README.mdのGenerateUI Tab/Lab subtab/AI TOOL Tabの各セクションも同内容で更新した。

検証: 全JSファイルをnode --checkで構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルのみ両フォルダへ同期しハッシュ一致を確認。Kapture MCP経由で実機ブラウザ操作: (1) GenerateUI ModelタブのLoRAバイパストグルでLora Managerノードのloras配列のstrength/clipStrengthが0⇔元の値へ正しく切り替わることを確認、(2) LabタブのLoRAキーフレームモーダルで両チェックボックスが表示・保存され、セル表示にラベルが反映されることを確認、(3) 実際にLabをRunし/historyAPIから最終ワークフローのCLIPTextEncodeEditPlus.text_editを直接確認してLoRA構文+トリガーワードが正しく追加されていることを確認、(4) Ollama(ornith-1.5:9b)へChatで生成パラメータ付きの依頼を送りgenerate_imageのtool_callsが正しい引数を返し、実際に指定通りのwidth/height/seed/batch_sizeで2枚の画像が生成されることをネットワークログと生成結果の両方で確認、(5) resolveAspectRatioOption/setGenerationParamsをモックworkflow/analysisで単体テストし、ResolutionSelectorノードのaspect_ratioが正しく書き換わりEmptyLatentImageのリンクは維持されることを確認。ヘルプページも実機で3箇所とも日本語表示が正しく反映されていることを確認した。

How to apply:

  1. 「ノードをバイパスする」という要望を受けたら、まずそのワークフローが実行時にどのフォーマット(UI形式 mode:4対応/API形式 mode概念なし)で保持・実行されているかを確認する。今回のケースのように既にAPI形式へ変換済みの場合、本物のBypass(mode:4)相当の配線再接続を自前実装するのは過剰投資になりやすく、「効果が等価な簡易実装(strength=0)」で要件を満たせないか先にユーザーに確認するとよい。
  2. LLMのfunction calling引数にCOMBO値(enum文字列)を渡させる場合、ワークフロー解析結果には「ノードの現在値」しか含まれず「取りうる全選択肢」は含まれないことが多い。/object_infoをその場でフェッチして選択肢を取得し、LLMの短い入力("3:2")を実際の完全な文字列("3:2 (Photo)")へ解決するワンクッションを挟むと、ノードごとに異なるラベル文言に振り回されずに済む。
  3. 「切り替えたらクリアしてから新しいものを追加する」という要求は、正規表現による汎用パターン除去よりも「前回自分が何を注入したかを正確に覚えておいて、その文字列だけを取り除く」設計の方が、ユーザーが手で書いた他のテキストを誤って消すリスクが低く安全。

関連: project_v042_lab_model_column, project_dev_deploy_sync


v0.4.3

バグ修正: 共有モーダルのお気に入り☆/リネームがLabモーダルへ残留 + Lab Model列の+/-をCheckpoint/LoRA/VAEで完全独立化

v0.4.2リリース直後、ユーザーからLabタブに関する2件のフィードバックを受けて修正した一連のセッション。

1. 共有モーダルのお気に入り☆・リネームがLabモーダルに残留するバグ: ユーザーから「Labタブモーダルについて確認したい。各モーダルでお気に入り☆、名前変更が可能なようですがこれは機能していますか?」との指摘を受けて調査。Workflow Studioの全「詳細モーダル」(Workflow/Models/Nodesタブの詳細表示、Labのキーフレーム編集)は#wfm-modal-overlayというDOMを1つだけ共有しており、共通のopenModal()(app.js)はタイトルのテキストだけを書き換え、呼び出し元が動的に注入したヘッダー要素(お気に入り☆ボタン、タイトルクリックでリネームするイベントリスナー)は一切クリアしていなかった。そのため「Workflowタブでワークフローの詳細モーダルを開いた直後にLabのキーフレームモーダルを開く」と、直前のワークフロー用の☆ボタンとリネームリスナーがそのまま残留表示され、Lab側で☆をクリックするとLabのキーフレームとは無関係に、直前に見ていたワークフローのお気に入り状態が実際に切り替わってしまうことをPlaywright MCPで実機再現・立証した。openModal()にクリーンアップ処理(.wfm-fav-btnの除去、タイトル要素をcloneNodeで複製・差し替えて前の呼び出し元のクリックリスナー/属性を一掃、リネーム用隠し入力欄のリセット)を追加して修正。副次効果として、Workflow詳細モーダルを連続で複数回開いた際にタイトルのクリックリスナーが際限なく累積していく別の潜在バグ(workflow-tab.js側にはリネーム用clickリスナーの重複防止ガードが無かった)も同時に解消した。

2. Lab Model列の+/-が独立していなかった問題: v0.4.2で実装したModel列(Checkpoint/LoRA/VAEを1つの{checkpoint, lora, vae}マージ行として1つのキーフレーム配列にまとめる設計)には設計上の欠陥があった。ユーザーから「checkpoint/lora/vaeいずれかで+で追加すると他のモデルも追加されてしまいます。逆に-で他の2つも削除される」との指摘を受け、根本的な設計見直しを実施。マージ行方式ではなく、Checkpoint・LoRA・VAEを(Prompt・KSamplerと全く同じ)完全に独立したキーフレーム配列に戻し、C/L/Vトグルは「今どの配列を表示・操作するか」を切り替えるルーティングとしてのみ機能する設計に変更した。この結果、v0.4.2で新設した_resolveModelKeyframe()(3フィールドを1行にバンドルしたまま個別に前方向解決するカスタムロジック)が丸ごと不要になり、既存の汎用_resolveKeyframe()(Checkpoint/VAE列が元々使っていたもの)にLoRAを1つ追加するだけで済むようになった——継承機能・ハイライト・inheritedヒント表示など、v0.4.2で追加した機能はすべてこのシンプルな独立配列方式のままで自然に成立する。

設計上の教訓: 複数の独立した設定を1つのUI列にまとめる際、「見た目を1列にする」ことと「データを1行にマージする」ことは別の問題であり、後者を選ぶと今回のような「+/-が意図せず道連れになる」バグを生みやすい。データモデルは独立させたまま、UI層(表示するフィールドの選択・共有される+/-ボタンのルーティング)だけを1列に見せかける設計の方が、機能追加・バグ修正のコストが低く、直感的な挙動にもなりやすい。

ヘルプ・README更新: project_v0336_conventionsの慣習に従い、Lab関連ヘルプ(helpLab14・helpLab15、英/日/中3言語+templates/index.htmlの英語フォールバック)を独立配列方式の挙動に合わせて更新(特にhelpLab15の「Revert to #1's settingは3項目まとめて戻る」という記述はもう正しくないため修正)。README.mdのLabセクションも同内容に更新した。

検証: 全JSファイルをnode --checkで構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルのみ両フォルダへ同期しハッシュ一致を確認。Playwright MCP経由で実機ブラウザ操作: (1) Workflow詳細モーダル→Labモーダルの順で開き、☆・リネームツールチップ・cursorスタイルが一切残留しないことを確認、(2) 「C」表示での+/-がCheckpointのみに作用しLoRA/VAEは不変であること、「L」表示での+/-がLoRAのみに作用しCheckpoint/VAEは不変であることをPlan JSONタブのデータ構造で確認、(3) v0.4.2形式(マージ行)のPlan JSONを読み込ませ、新しい独立配列形式へ正しくマイグレーションされることを確認。実際の画像生成を伴うRun実行は割愛。

How to apply:

  1. DOM/モーダルを複数の呼び出し元で共有する設計では、各呼び出し元が「自分固有のUI要素」を動的に注入する場合、その呼び出し元自身が毎回クリーンアップするのではなく、共有側の入り口関数(今回はopenModal())で毎回リセットする設計にしないと、呼び出し順序に依存した残留バグ・誤操作バグを生む。cloneNode+replaceWithは、addEventListenerの参照を保持していなくてもリスナーを確実に一掃できる簡便な手段。
  2. 複数の独立した設定を1つのUI要素(列・パネル等)にまとめる要望を実装する際、「データも1つにまとめる」のではなく「データは独立させたままUI層だけ共有する」設計を最初から検討する。今回はユーザーの2度のフィードバック(継承挙動の誤り→+/-の誤り)を経てようやくこの設計にたどり着いたが、根本原因は同じ「フィールドを1行にバンドルした」という初期設計判断にあった。

v0.4.2

機能追加: Lab「Model」列統合(Checkpoint / LoRA Single / VAE)+ フィールド単位継承 + C/L/V表示切替

ユーザーから「生成UIタブのLabサブタブを改善したい。現在のCheckpoint、VAEのみのモデル設定をModelとしてタブ化しCheckpoint、Lora、VAEとし、Modelタブのワークフローの該当するモデルのハイライト表示を取り入れたい」との要望を受けて着手。LabのSettingパネルで別々だったcheckpoint列・vae列を1つの「Model」列に統合し、LoRA(Single、実験的機能のため今回はStack非対応)を新規追加した一連のセッション。

1. データモデル統合: COLUMN_KEYSを["checkpoint","vae","prompt","ksampler"]から["model","prompt","ksampler"]に変更。Model列の1キーフレームは{ checkpoint, lora: {name, strengthModel, strengthClip}, vae }をまとめて保持する。旧形式(checkpoint/vae 2列)で保存済みのPlan JSON/PNGは_migrateLabColumns()でiteration単位に自動マージして読み込めるよう後方互換を維持した。

2. フィールド単位の継承(当初リリース後にユーザー指摘で修正): 初回実装では「そのキーフレームの値をまるごと差し替え」方式だったため、「LoRAだけ設定したキーフレームを追加するとCheckpoint/VAEがワークフロー本来の値に戻ってしまう」という直感に反する挙動になっていた。ユーザーから「未設定の項目は直前のキーフレームの値を引き継ぐという認識でいいか」との確認を受け、_resolveModelKeyframe()を新設しCheckpoint/LoRA/VAEを個別に前方向へフォワードフィルする方式に修正。編集モーダルの未設定項目には「(引き継ぎ: ファイル名)」というヒントを表示し、実際に適用される値が一目でわかるようにした。「1回目の設定に戻す」は3項目まとめてキーフレーム#1へ戻る挙動を維持している。

3. C/L/V表示切替(列ヘッダー): 「Modelとしては1列だが、[C][L][V]ボタンで列の表示を切り替えたい」との要望を受け、Model列ヘッダーに[C][L][V]トグルボタンを追加。選択中の1項目だけをセルに単一行表示し(表示値はフィールド単位継承後の実効値)、続けて「セルをクリックして開くモーダルの表示も選択に応じて1つにしたい。ユーザーの思考と操作(表示)が一致しやすい」との要望で、モーダル内のCheckpoint/LoRA/VAEタブ切替UIを廃止し、ヘッダーで選択中の1項目だけを表示・編集する設計に変更した(Save時もその1項目だけを更新し、他の2項目は既存の保存値をそのまま維持)。あわせて「C、L、Vがハイライトする形にしたい」との要望で、GenerateUI Modelタブと同じ「ワークフロー内に該当ノードが実在する場合にラベルをハイライト色にする」仕組みをC/L/Vボタン自体に移植した。

4. LoRA(Single)適用ロジック: _applyLabLoraToWorkflow()を新設し、既存GenerateUI Model側(models-tab.jsのapplyToGenUI)と同じ「analysis.lora_nodes[0]優先、無ければlora_name入力を持つノードへフォールバック」ロジックを踏襲した。ワークフロー内にLoRAノードが1つも存在しない場合は、Run実行全体で1回だけ警告トーストを出しスキップする(VAEと異なりLoraLoaderノードの自動挿入は行わない — CheckpointのModel/CLIP出力を下流ノードへ配線し直す必要があり、単純な入力上書きでは済まないため)。

ヘルプ・README更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMap の3点セット)に従い、Lab関連ヘルプ(helpLabDesc/helpLab3/helpLab6/helpLab9/helpLab12/新規helpLab14・helpLab15)を英/日/中3言語で更新し、README.mdのLabセクションも同内容に更新した。

検証: 全JSファイルをnode --checkで構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルのみ両フォルダへ同期しハッシュ一致を確認。Playwright MCP経由で実機ブラウザ操作: 3列表示・モーダルでのCheckpoint/LoRA選択保存・Plan JSONのデータ構造・旧形式Plan JSONのマイグレーション・実ワークフロー(Checkpoint+LoRAノードあり/VAEノードなし)でのハイライト表示・フィールド単位継承の実効値表示・C/L/V単一項目モーダル、を一通りエンドツーエンドで確認。実際の画像生成を伴うRun実行(GPU負荷がかかるため)は割愛した。

How to apply:

  1. 複数の独立した設定(旧: checkpoint列/vae列)を1つのUI要素に統合する際、「キーフレーム=行まるごと差し替え」という既存の解決ロジックをそのまま流用すると、統合前は独立していたフィールド同士が意図せず道連れで上書きされる罠がある。フィールドをバンドルする場合は、解決ロジックもフィールド単位に分解しないと直感に反する挙動になる。
  2. UIの「今見ている/選択している項目」と「編集対象」が食い違う設計(列ヘッダーのトグルと別にモーダル内タブがある等)は、機能的には可能でも認知負荷が上がる。「操作と表示を一致させる」というシンプルな原則に立ち返ると設計がすっきりすることがある。
  3. 新規ノード自動追加(VAELoaderの単純な入力上書き)と、ノードをグラフの配線経路に割り込ませる操作(LoraLoaderのmodel/clip出力を下流へ再配線)は難易度が全く異なる。前者のパターンをそのまま後者に流用しようとする前に、既存コード(models-tab.jsのapplyToGenUI)に「ノードが無ければ警告のみ」という前例が無いか確認するとよい。

v0.4.1

UX改善: Mask Editor One連携ボタンを「選択中ノード優先」方式に再設計

v0.4.0でリリースした「Edit in Mask Editor One →」ボタンは、WFS生成UIタブに対象ワークフロー(MaskEditorOneノードを含むもの)を事前に読み込んでおく必要があり、comfyUI.currentAnalysis.mask_editor_one_nodesにボタンの有効/無効判定を依存させていた。ユーザーから「体感が悪い」とのフィードバックを受け、既存の「Send to Workflow」ボタンと全く同じ「ComfyUIキャンバス上で選択中のノードを優先、無ければグラフ内で最初に見つかったノードにフォールバック」方式に再設計した。

実装: MaskEditorOneBridge.jsからcomfyUI.currentAnalysis依存・refresh()/available/nodeプロパティを全撤去し、ボタンを常時有効化(Send to Workflowと同様、クリック時にノードが見つからなければトーストでエラー表示)。node_sets_menu.js側はwindow.wfmOpenMaskEditorForNodeのシグネチャからnodeId引数を除去し、内部で_wfmFindMaskEditorOneNode()(_wfmFindImageWidget()と同一ロジック)がノードを自動探索するよう変更。これにより「WFS生成UIタブへワークフローを読み込む」という間接的な手順が完全に不要になり、ComfyUIキャンバス上でMaskEditorOneノードを選択してボタンを押すだけで使えるようになった。

副次的な発見: 実装過程の実機検証で、「続けて描画」しても2回目のMask Editor One送信内容が変わらないという不具合報告があったが、調査の結果コードのバグではなく、新規マスクレイヤー作成時に自動的にそのレイヤーがアクティブになる(BG Remove/SAM3等、他のマスク生成ツールと共通の仕様)ため、レイヤー切り替えなしに続けて描画するとマスクレイヤーへの追記になり、エクスポート合成(maskApply=falseのマスクは除外)に反映されないことが原因と判明。

検証: 全JSファイルをnode --checkで構文確認。kapture MCP経由でComfyUI+WFS実機2タブを操作し、ComfyUIキャンバスに新規MaskEditorOneノードを追加・選択した状態で、WFS生成UIタブに何も読み込まずボタンをクリック→モーダルが正しく開く→Apply後もマスクレイヤーが正しくインポートされることを確認。

How to apply:

  1. WFS側のcomfyUI.currentAnalysis(ワークフロー読み込み状態)に依存する設計は、ComfyUIキャンバスの実際のグラフ状態とズレるリスクがある。ComfyUI実グラフを直接操作できる機能(window.openerブリッジ経由)では、「選択中ノード優先・フォールバック」パターン(Send to Workflowが最初の実装例)をデフォルトの設計として検討するとよい。
  2. Image Editタブの「新規マスクレイヤー作成時に自動アクティブ化」は全ツール共通の仕様のため、ある機能だけを個別に変更すると一貫性が崩れる。類似の不可解な挙動報告があった場合は、まずこの共通仕様が原因でないか確認する。

v0.4.0

リファクタリング: image-edit-tab.js / models-tab.js の低凝集度モジュール分割

graphifyのコミュニティ分析で、static/js/image-edit-tab.js(3665行/85メソッド)とstatic/js/models-tab.js(2522行/76関数)が異常に低いコミュニティ凝集度(density 0.07〜0.08)を示す「神クラス/神モジュール」と判明したため、段階的な分割計画を立てて1セッションで両ファイルとも完走した。

  • image-edit-tab.js(3665→2577行、約30%削減): G'MIC統合・Blur/Mosaicツール・背景除去(imgly+BiRefNet)・SAM3セグメンテーション・Inpaint/I2I実行・ファイル出力(PNG/Gallery/ComfyUIアップロード)の6モジュールをstatic/js/image-edit/配下へ切り出し。コールバック注入パターン(一方向依存)を採用し、Mask Editor One連携ディスパッチや画像ロード系など密結合な部分は意図的に本体へ残した。
  • models-tab.js(2522→592行、約77%削減): 共有state・バッジ機能・フィルタ/ソート・グリッド描画/選択操作/詳細パネルの5フェーズをstatic/js/models/配下へ切り出し。グリッド描画↔選択/サイドパネル↔詳細モーダルが相互に再描画をトリガーする構造のため、Phase 5のみ循環importで解決した。
  • graphifyによる定量検証: image-edit-tab.js側は各モジュールが独立した高凝集度コミュニティ(density 0.18〜0.48)を形成し2〜6倍改善。一方models-tab.js側は循環import設計のため、ファイル分割・可読性は向上したもののコミュニティ凝集度は改善しなかった(旧グラフとほぼ同じdensity 0.096)——コールバック注入で切り出せるモジュールは凝集度も改善するが、循環importで切り出したモジュールは凝集度が改善しないという教訓が得られた。

機能追加: Mask Editor One 連携ボタン(DEVLOGフェーズ2計画の実装)

Image Edit タブの Mask ツールに「Edit in Mask Editor One →」ボタンを追加。ワークフロー中に既存のMaskEditorOneノードがある場合のみ有効化され、クリックすると現在のキャンバス合成画像をそのノードへ送信し、window.opener経由でComfyUI側の実際のモーダルエディタ(Paint/SAM3/BiRefNet/Shape等フル機能)を開く。Applyクリック後、結果マスクを新規マスクレイヤーとしてWorkflow Studio側に取り込む。

計画段階では「window.maskEditorOpen()のようなグローバル起動関数」を想定していたが、実際のMask Editor One(v0.1.9+)ソースを調査した結果そのような関数は存在せず、モーダルは実際のグラフノードオブジェクト(node.widgets/node.graph)に強く依存していることが判明。WFSは別ウィンドウで動くSPAのため直接モーダルをインポートできず、ユーザーに「既存ノード再利用方式」(自動ノード生成方式との二択)を確認の上、window.opener経由でノードの「Edit Mask」ウィジェットを疑似クリックし、モーダルのoverlay DOM(.me-overlay)の表示変化をMutationObserverで監視して結果を受け取るブリッジ関数(window.wfmOpenMaskEditorForNode、web/comfyui/node_sets_menu.js)を実装した。kapture MCP経由でComfyUI+WFS 2タブを実機操作し、モーダル起動→Apply→マスクレイヤー取り込みまでのエンドツーエンド動作を確認済み。

機能追加: Image Edit タブ「Send to Workflow」ボタン + 「Send to ComfyUI」→「Save to Input」改名

既存の「Send to ComfyUI」(/upload/imageへアップロードするだけ)とは別に、アップロード後にComfyUI側で選択中のノードのimageウィジェットへファイル名まで自動反映する「Send to Workflow」ボタンを追加。ユーザーが別プロジェクトchat_TEカスタムノードの同名ボタンを参照として提示し、同じロジック(選択中ノード優先、無ければグラフ内の最初の該当ノードへフォールバック)をwindow.openerブリッジ(window.wfmSendImageToSelectedNode)で再実装した。

新ボタン追加に伴い、紛らわしくなった既存ボタン名「Send to ComfyUI」を「Save to Input」に改名(実際の動作=ComfyUIのinputフォルダへ保存、を正確に表す名前に変更)。

ヘルプ・README更新

project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMap の3点セット)に従い、Image Edit タブヘルプの Mask Tool カードに「Edit in Mask Editor One」項目(imageedit-mask-14)、Export & Navigation カードに「Send to Workflow」項目(imageedit-19b、既存番号を保持するため文字サフィックス方式で挿入)を追加し、「Save to Input」への改名も反映。README.mdのImage Edit Tabセクションにも同内容を追記。

検証: 全JSファイルをnode --checkで構文確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更ファイルのみ両フォルダへ同期しハッシュ一致を確認。Mask Editor One連携・Send to Workflowともkapture MCP経由でComfyUI+WFS実機2タブ操作によるエンドツーエンド動作確認済み。

How to apply:

  1. WFSはComfyUI本体とは別ウィンドウで動くSPAのため、ComfyUIの実グラフ(app.graph)を操作する機能を追加する場合はwindow.opener経由のクロスウィンドウブリッジパターン(web/comfyui/node_sets_menu.jsにwindow.wfmXxx関数を追加)を使う。WFS側はwindow.openerが無い場合のエラー表示(「ComfyUIのトップメニューからWorkflow Studioを開いてください」)を必ず用意する。
  2. カスタムノード等の他プロジェクトの類似機能を参照実装として提示された場合、まずそのロジック(widget検索の優先順位、フォールバック条件など)を正確に読み取ってから、WFSのウィンドウ分離アーキテクチャに合わせて移植する。
  3. 低凝集度モジュールの分割は、機能同士が本質的に相互依存している場合(循環import必須)はファイルサイズ削減・可読性向上は達成できてもコミュニティ凝集度は改善しないと事前に期待値を揃えておく。

v0.3.99

機能追加: AI TOOLタブ + サイドパネルAタブにUnslothバックエンド追加(APIキーは.env管理、SSRF脆弱性修正、reasoning_content対応)

ユーザーから「作業計画のUnslothをAI TOOLタブ、サイドパネルAタブに導入したい。APIキーは.envファイルで設定したい」との依頼を受けて着手。v0.3.98時点で「今回は見送り」としてproject_next_tasksメモリに保留していたUnslothバックエンド追加を実装した一連のセッション。

1. Unslothバックエンド追加: Ollama/LM Studio/Lemonadeに続く4つ目のバックエンドとしてUnsloth(OpenAI互換API、既定http://localhost:8888)を追加。既存3バックエンドと異なりローカルアクセスでも常にAPIキー認証が必須(Authorization: Bearer sk-unsloth-...)なため、フロントエンドから直接fetchする既存方式ではなく、新設したサーバー側プロキシ(py/routes/unsloth_routes.py, POST /api/wfm/unsloth/proxy)経由にした。CivitAI連携(civitai_service.py)のAPIキー管理パターン(環境変数優先)を踏襲しつつ、今回はユーザーの要望通りAPIキーを.envファイル(UNSLOTH_API_KEY)で管理する方式にした。python-dotenvを新規依存として追加し、ノードインポート前に実行される唯一のタイミングであるprestartup_script.py(新規作成)で.envを読み込みos.environへ反映する。フロントエンドはSPA(ai-tab.js)・サイドパネル(node_sets_menu.js)の両方でfetchModels/callLLM/callVLM/callChat各関数にbackend === "unsloth"分岐を追加(既存の重複実装パターンを踏襲)。

2. セキュリティレビュー対応(SSRF修正): 実装直後の自動セキュリティレビューで、プロキシがクライアント指定のbaseUrlをそのまま信頼しており、悪意のあるページがbaseUrlに任意の外部URLを指定すればUNSLOTH_API_KEYを外部へ流出させられる脆弱性(SSRF経由の資格情報窃取)が指摘された。baseUrlのホスト名をlocalhost/127.0.0.1/::1のみに限定するアローリスト検証を追加して修正(ポート番号は自由のままとし、Unslothのカスタムポート起動に対応)。

3. .env.exampleのコメントアウトの罠: 動作確認時、ユーザーが.env.exampleの# UNSLOTH_API_KEY=sk-unsloth-...という例示行をそのまま.envにコピーし、先頭の#を消し忘れてコメントアウトされたまま値を入力してしまい「UNSLOTH_API_KEY is not set」エラーが発生。テンプレート自体を「空の実行可能な行」(UNSLOTH_API_KEY=)+手順コメントの形に変更し、同じ罠が再発しないようにした。

4. Unsloth Desktop側「モデル自動切り替え」設定の仕様判明: 動作確認中、Unsloth側の「モデル自動切り替え (OpenAI API)」設定がOFFだと、Unsloth Studio UI上で事前にモデルを手動ロードしていない限りAPIリクエストがHTTP 404で失敗することが判明(公式ドキュメント調査で確認:未知のモデル名は安全に現在ロード中のモデルへフォールバックするが、通常は明示的にONにしてAPI経由のモデル切り替えを許可する必要がある仕様)。本プラグイン側の不具合ではなくUnsloth Desktop自体の仕様のため、ヘルプ(ai-6)とREADMEに「Unsloth Desktop側でこの設定をONにする必要がある」旨を追記した。

5. reasoning_content未対応バグ修正: 「モデル自動切り替え」をONにした後も、会話が数ターン進むとチャットの応答が空のバブルになる不具合が発生。Unsloth Studioのサーバーログ(~/.unsloth/studio/logs/server/)と、ブラウザ操作MCP(kapture)のevaluate経由でプロキシへ生のAPIリクエストを直接送って実レスポンスを検証した結果、Unslothのreasoningモデル(gemma-4-12B-it-QAT-GGUF)はOpenAI互換APIで思考過程をmessage.contentではなく別フィールドmessage.reasoning_contentに返す仕様であることが判明した。Ollama/LM Studio/Lemonadeはインラインの<think>...</think>タグ方式のため既存のThinking mode処理で問題にならなかったが、Unslothは別フィールド方式のため、Max tokens不足(初期値512)で思考中にトークンを使い切ると(finish_reason: "length")、contentが常に空文字列のまま返り、チャットに中身のないバブルだけが残っていた。reasoning_contentを<think>...</think>としてcontentに合成してから既存のThinking mode ON/OFF処理(stripThinkingTags)に流すよう修正し、SPA・サイドパネル両方に適用。Max tokensが小さすぎると根本的に回答本文が生成されない制約自体は残るため、ヘルプに「Unslothのreasoningモデルを使う場合はMax tokensを2048以上に」という推奨値も追記した。

ヘルプタブ更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMap)に従い、AI TOOL TabヘルプのSettings pane説明(ai-6)にUnsloth関連3項目(APIキーの.env設定・モデル自動切り替え要件・reasoning_content/Max tokens注意)を追記。sidepanel-16・ai-2にもバックエンド一覧へUnslothを追加。README.mdのBackend support・Settings pane等の該当箇所も同様に更新。

検証: 全JS/PythonファイルをそれぞれNode.jsの構文チェック(node --check)・ast.parse()で確認。開発元gitリポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更の都度diffで意図した差分のみであることを確認してから両フォルダへ同期し、cmpでファイル同一性を検証。実行時フォルダの__pycache__もクリア。最終的にユーザー自身が実機(Unsloth Desktop + ComfyUI)で一通り動作確認済み。

How to apply:

  1. 外部APIキーが常に必須なバックエンドを追加する場合、既存バックエンドと同じ「フロントエンドから直接fetch」方式は使えない(APIキーがブラウザに露出する)。サーバー側プロキシ方式が必須になる。
  2. サーバー側プロキシでクライアント指定のURLをそのまま信頼してAuthorizationヘッダーを付与すると、SSRF経由の資格情報漏洩につながる。ホスト名のアローリスト検証は必須。
  3. .envテンプレートに「コメントアウトされた例」を書くと、ユーザーが#を消し忘れてハマる典型パターンになる。テンプレートは「空の実行可能な行」+説明コメントの形にする。
  4. サードパーティAPIサーバーの独自拡張(reasoning_content等)は、標準的なOpenAI Chat Completions仕様と食い違うことがある。空応答やトークン切れなど不可解な挙動が出たら、実際の生レスポンスをブラウザから直接fetchして中身を見るのが最も早い切り分け方法(kaptureのevaluateツールでプロキシへ直接リクエストを送るのが有効だった)。

v0.3.98

機能追加・バグ修正: AI TOOL翻訳の信頼性向上 + GenerateUI Modelタブ再構成(ハイライト色・折りたたみ・並び替え)+ Thinking mode/Max tokens設定

ユーザーから「AI TOOLタブの翻訳、英語から日本語がうまく動かない(英語のまま、完了で翻訳されない)」との報告を受けて着手。原因調査後、関連するGenerateUI Modelタブの表示改善、AI TOOLタブへのLLM生成パラメータ設定追加と、複数の要望を連続して実装した一連のセッション。

1. AI TOOL翻訳の信頼性向上: 調査の結果、翻訳処理がOllamaの/api/generate(system role無しの生プロンプト文字列)またはLM Studio/Lemonadeの/v1/chat/completions(user roleのみ、system message無し)でLLMへ丸投げしており、指示追従が弱い小型・非力なローカルモデルが「翻訳してください」という指示を無視して原文をそのまま返すことがあった。それでもHTTPレスポンス自体は正常なため、画面上は「完了」と表示されたまま実際には未翻訳、という状態が発生していた。既にChatペインで使われていたcallChat()(system role対応のchat API)を翻訳にも流用し、system promptで「翻訳結果のみを出力し、説明・前置き・原文の繰り返しをしない」と明確に指示するよう変更。加えてモデルが前置き文("Here is the translation:"等)や引用符を付けて返す場合の後処理除去(cleanTranslationOutput)と、出力が原文とほぼ同一=未翻訳の疑いがある場合の警告トースト(looksUntranslated)を追加。SPA(ai-tab.js)とサイドパネルAタブ(node_sets_menu.js)は全く同じ不具合パターンの重複実装だったため、両方に同じ修正を適用(設定は同じlocalStorageキーwfm_ai_settingsを共有)。

2. GenerateUI Modelタブ: ハイライト色機能: ユーザーから「読み込んだワークフローに関係なく項目を並べているため該当する項目(Checkpointなど)の色を変更表示、デフォルトは明るい緑、設定タブに色変更設定を追加したい」との要望。既存の「RAW JSON Colors」機能(<style>タグ動的注入+localStorage保存+Resetボタン)と全く同じパターンを踏襲し、applyModelTabActiveColor()を新設。renderModelTab()で各セクション(Checkpoint/VAE/Diffusion Model/Text Encoder/ControlNet/Hypernetwork)のラベルとLoRA列見出しに、対応するノードが実際にワークフローへ含まれる場合のみwfm-model-label-activeクラスを付与。既存CSS.wfm-form-group label { color: var(--wfm-text-secondary); }の方が詳細度が高く単純なクラス指定では上書きされなかったため、注入CSS側のセレクタを.wfm-form-group label.wfm-model-label-active(および後続の折りたたみ対応でsummary要素向けセレクタも追加)として詳細度を確実に上回るよう実装。

3. ControlNet/Hypernetwork折りたたみ化 + Text Encoder並び替え: 「使用頻度が低い、意図的に利用するControlNet、Hypernetworkは折りたたみにし、最新モデルで主流のText EncoderをControlNetの上にしたい」とのフィードバックを受け、表示順をCheckpoint→VAE→Diffusion Model→Text Encoder→ControlNet→Hypernetworkに変更。ControlNet/HypernetworkはImage Editタブのプレースホルダ設定(v0.3.97)で既に使われていた<details>/<summary> + .wfm-settings-sectionパターンをそのまま再利用し、デフォルト折りたたみ状態に。折りたたんだ状態でもハイライト色(#2の機能)は見出し文字に残るため、展開しなくても「このワークフローはControlNetを使っているか」が一目で分かる設計とした。続けて「LoRAもハイライト可能にしたい」との追加要望で、templates/index.htmlのLoRA列見出し<span>にid付与、renderLoraPane()でワークフローにLoRAノードが1件でもあればハイライトクラスをtoggleするよう対応。

4. AI TOOLタブ: Thinking mode / Max tokens設定: 「Thinking mode ON/OFF、Max tokens設定を追加したい」との要望を受け実装。Ollamaはthinkパラメータ(bool)とoptions.num_predictで標準対応しているが、LM Studio/Lemonade(OpenAI互換API)には標準的なthinking切替パラメータが存在しないためmax_tokensのみ送信し、代わりにThinking mode OFF時は出力から<think>...</think>(<thinking>...</thinking>も)タグをクライアント側で正規表現除去する方式を採用。これはOllama自身がパラメータを無視した場合の保険としても機能し、バックエンドを問わず一貫した挙動になる。SPA(callLLM/callChat/callVLM)とサイドパネル(aiCallLLM/aiCallChat/aiCallVLM)の両方に_applyGenOptions()ヘルパーとタグ除去処理を追加し、Translation/Chat/TOOLS(VLM)/Wildcard生成の全呼び出し経路に反映。

  • 付随調査(見送り): 「Unsloth Desktopアプリのバックエンド追加は可能か」との質問を受け、公式ドキュメントを調査。OpenAI/Anthropic互換APIを同一ポート(既定localhost:8888)で提供する点は好条件だが、既存3バックエンドと異なりローカルアクセスであってもAPIキー認証が常に必須(Authorization: Bearer sk-unsloth-...、無いと401)と判明。実装には新規UI(APIキー入力欄)が必要になるためユーザー判断で今回は見送り、将来の作業候補としてproject_next_tasksメモリに記録。

ヘルプタブ更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、GenerateUI TabヘルプにModelタブの新しい表示順・折りたたみ挙動(gen-5更新)とハイライト色機能(新規gen-28)、AI TOOL TabヘルプにThinking mode/Max tokens(ai-6更新)、SettingsタブヘルプにModelタブハイライト色設定(新規settings-14)を追加。この作業中に、以前からindex.htmlにだけ存在しi18n.js(英語含む全言語)に対応キーが用意されていなかったため実際には一度も表示されていなかった「LoRA Stack — Apply writes...」の説明文を発見・復旧(app.jsのel.textContent = t(key)は未定義キーでもキー名をそのまま返してHTML側の内容を問答無用に上書きするため、i18n.js側の追加漏れは静かな表示欠落を引き起こす)。

検証: 全JSファイルについてnode --checkで構文確認。開発元git リポジトリと実行時custom_nodesフォルダは別実体のため(project_dev_deploy_sync参照)、変更の都度diffで意図した差分のみであることを確認してから両フォルダへ同期、CRLF/LF不一致がないこともfileコマンドで事前チェック。各機能追加後にユーザー自身が実機(ComfyUI再起動後)で動作確認し、フィードバックを都度反映する形で進行。

How to apply:

  1. SPA(ai-tab.js)とサイドパネル(node_sets_menu.js)のように、同じ機能が2箇所に重複実装されている場合(設定は共通のlocalStorageキーを共有していても、API呼び出しロジック自体は別ファイルにコピーされている)、片方だけ修正すると「サイドパネル版だけ直っていない」という見落としが起きる。ai-tab.js相当の修正が必要な要望が来たら、必ずnode_sets_menu.js側の対応箇所も同時に確認する。
  2. ローカルLLM APIへの指示は、user発話に埋め込んだ「〜だけ出力して」という指示より、system roleとして明示的に分離した方が小型モデルの指示追従率が上がる。それでも従わないモデルへの保険として、クライアント側での出力後処理(前置き除去、echo検知)を組み合わせるとより堅牢になる。
  3. 動的注入<style>タグでハイライト色のような「上書き用クラス」を追加する場合、既存CSSがelement+classなど複合セレクタで詳細度を持っていると単純なクラス指定では負ける。読み込み順に依存せず確実に上書きするには、注入側のセレクタも同等以上の詳細度(同じ複合セレクタ構造にクラスを足す)にする。
  4. 折りたたみ(<details>)と色ハイライトを組み合わせる設計は、「低頻度機能は隠したいが、関連情報があるかどうかは常に見えていてほしい」という要望に対する良い組み合わせになる——折りたたんだ見出し自体を色の表示場所として使う。
  5. i18n.jsの「3点セット」規約が守られていないと、HTML側にどれだけ内容を書いてもJS側のt(key)上書きで静かに消える。ヘルプ文言を追加・編集する際は、既存の該当キーが本当に全言語ブロックに存在するか(特に英語)を都度確認する。

v0.3.97

機能追加・バグ修正: KREA-2ワークフロー対応 + Labタブ拡張(Clear/Get from Previous/Bypass/ライブ反映)+ Input Imageプレースホルダ機能

ユーザーから「KREA2モデルワークフローを生成UIタブ、メタデータ、Workflow Studio LibraryのIタブ他対応させたい」との依頼を受けて着手。実機検証を進める過程で複数の既存バグを発見・修正し、続けてLabタブとInputタブImageサブパネルへの機能追加も行った一連のセッション。

1. KREA-2ワークフロー対応で発覚した2件の既存バグ修正: KREA-2 Turbo(krea2_turbo_int8_convrot.safetensors、Qwen3VLベースのLLMプロンプト拡張+サブグラフ構成)の2種類のワークフロー(T2I版・スタイル参照版)を実機で読み込み検証したところ、GenerateUIタブでPositive Promptが誤ってSaveImageノードを指し、プロンプト欄が空になる不具合を発見。原因は、ノードタイプ単位の役割伝播ロジック(comfyui-workflow.jsのanalyzeWorkflow())がPreviewAny(値タップ中継)とStringConcatenate(LoRAトリガーワード連結)という中継ノードタイプを一切考慮しておらず、KREA-2のプロンプト強化トグル配線(PrimitiveStringMultiline→ComfySwitchNode→PreviewAny→StringConcatenate→ComfySwitchNode→CLIPTextEncode)の途中で役割伝播が途切れていたため。同様の理由でスタイル参照版ではTextEncodeQwenImageEditPlusのリンク値(配列)がテキストとしてそのまま誤表示されるバグも発覚。「KREA2専用」ではなくPreviewAny/StringConcatenate/リンク経由TextEncodeQwenImageEditPlusという汎用ノードタイプ単位の役割伝播ルールとしてcomfyui-workflow.jsに追加し、Metadataタブ(metadata-tab.jsのresolveLinkedText系2関数)とサイドパネルIタブの重複実装(node_sets_menu.jsの_resolveLinkedText系2関数)にも同じ汎用キー拡張(source/string_a)を反映。

  • 修正後、実際に生成を実行したところ別の既存バグでPrompt outputs failed validationエラーが発生。調査の結果、TextGenerateノードのsampling_mode(COMFY_DYNAMICCOMBO_V3型ウィジェット)が選択中オプションのサブフィールド(temperature/top_k等の必須6個+presence_penaltyのoptional1個、計7スロット消費)を持つのに対し、サブグラフの境界値注入ロジック(_getDynamicComboSubNames・_simulateWidgetValues)がDynamicComboの複数スロット消費を全く理解しておらず単純に1スロットとして扱っていたため、sampling_modeより後に宣言されているthinking(LLM思考モード、サブグラフ境界に接続)の注入処理が実際にはsampling_mode.temperatureのスロットを誤って上書きし、temperatureに境界値のfalse(数値比較で0.0扱い)が入りmin: 0.01バリデーションに抵触していた。_getDynamicComboSubNamesをrequired/optional両方のサブフィールドを返すよう修正し、_simulateWidgetValuesにDynamicCombo展開に対応するcomboExpander引数を追加してスロット数を正しく消費するよう修正。LLMプロンプト拡張(DynamicComboを持つノード)をサブグラフ経由で使うワークフロー全般に影響する汎用バグだったため、KREA-2以外の同様構成のワークフローにも恩恵がある。

2. Labタブ: プロンプトモーダルにClear/Get from Previousボタン追加: ユーザー要望「プロンプトモーダルにプロンプトクリアボタンと前の設定からプロンプトを取得ボタンを追加したい(#3で実行、#2のプロンプトをポジティブ、ネガティブ両方コピー)」を実装。Clearは両フィールドを空にするのみ(ワークフローノードには触れない)。Get from Previousは同じカラムの直前のキーフレーム(idx-1)からPositive/Negative両方をコピー。キーフレーム#1には「前」が存在しないためボタン自体を非表示。

3. Labタブ: Setting 1(キーフレーム#1)のライブ反映: ユーザー要望「設定1のそれぞれのモーダルに生成UIタブの内容を初期自動反映したい(ワークフローの設定まま)表示は編集された場合に表示を変更したい」を実装。各カラム(Checkpoint/VAE/Prompt/KSampler)のキーフレーム#1が、まだ明示的に上書き保存されていない間は、GenerateUIタブで現在読み込まれているワークフローの実際の値(comfyUI.currentAnalysisから取得)をグリッドセル(破線ボーダー)と編集モーダル(フィールド事前入力+ヒント文言)の両方に表示する。何も変更せず保存しただけでも実際の上書き値として確定し、以後はライブ反映されなくなる(_isEmptyValue(col, kf.value)が false になるため)。GenerateUIタブの他サブタブからLabサブタブへ切り替えるたびにrefreshLabLiveDefaults()で再描画し、常に最新のGenerateUI状態を反映する。「Get from Previous」ボタンも_effectiveDisplayValue()経由でこのライブ反映値を透過的に扱う。

4. Labタブ: Bypassチェックボックス追加: ユーザーから「バイパスボタンを追加したい」との要望を受け、追加箇所(Labタブの各列 vs Model タブ vs Raw JSONパネル)と動作仕様(ComfyUIノードを実際にBypassモードにするか、Labタブ内部だけでキーフレーム行を無効化するか)の2点をAskUserQuestionで確認。KSamplerは入出力が同じLATENT型のため実際のBypass(mode:4)が構造的に成立するが、Checkpoint/VAE/Promptの各ローダー/エンコーダーは入力を持たないため下流が未接続になり生成エラーを誘発するリスクがあるとユーザーに説明した上で、「そのキーフレーム行を無効化(Labタブ内のみ)」を選択いただいた。各キーフレームにbypassedフラグを追加し、_resolveKeyframe()でbypassedな行を「存在しないもの」として完全スキップ(直前に有効だった設定がそのまま使われる)。キーフレーム#1自体もBypass可能で、その場合はライブ反映表示より優先される。BypassチェックはRevertチェックと排他(両方同時には使えない)。

5. GenerateUI Inputタブ Imageサブパネル: Clearボタン + プレースホルダ画像機能: ユーザー要望「クリアボタンと上部にプレースホルダ画像機能を追加したい。プレースホルダ画像機能は色と画像が選べ...LoadImageのプレースホルダボタンを押下した際、画像を作成し既存の画像を差し替えます...デフォルト画像の設定は記憶されます」を実装(comfyui-editor.jsのrenderImageTab)。各LoadImageカードにClear(保留中の選択/プレビューをリセット、ノードには触れない)とPlaceholderの2ボタンを追加。パネル上部に共有のPlaceholder Image設定:Colorモード(幅・高さ・色を指定、押下時にキャンバスでその場生成しアップロード)とImageモード(デフォルト画像を1枚アップロードし記憶、押下時は再アップロードせずファイル名を再利用)。設定はlocalStorage(wfm_i2i_placeholder)に永続化。各カード自身のPlaceholderボタンで個別に差し替え可能。

  • フォローアップ(低頻度機能のため折りたたみ化): ユーザーから「使用頻度は高くないため。折りたたみにしたい」とのフィードバックを受け、Settingsタブと同じ<details>/<summary>アコーディオンパターン(.wfm-settings-section/.wfm-settings-summary)に変更し、デフォルトで折りたたみ状態に。

ヘルプタブ更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、GenerateUI Tabヘルプに Imageサブパネルのプレースホルダ機能を1項目追加(gen-27)、Lab subtabヘルプにClear/Get from Previous・ライブ反映・Bypassの3項目を追加(lab-11〜lab-13)。EN側はapp.jsのhelpIdMapがel.textContent = t(key)でHTMLタグを問答無用に上書きするため、新規追加時はi18n.js側の全言語ブロック(EN含む)に対応キーを用意しないとリッチHTML(<b>/<code>タグ)がプレーンテキストで消えてしまう点に注意して実装。

検証: 各修正について、実際にComfyUI(127.0.0.1:8189)とWorkflow Studioの実行時ComfyUI_5インスタンスへPlaywrightで接続し、KREA-2両ワークフローの読み込み・GenerateUI/Metadata/IタブでのPositive Prompt検出・実際の生成実行(バリデーション通過・画像出力まで)・Labタブの新機能(Clear/Get from Previous/ライブ反映/Bypass)の各UI操作・Input ImageサブパネルのColor/Imageモード双方でのPlaceholder適用とリロード後の設定復元・ヘルプタブのEN/JA両言語表示、をすべて実機で確認。開発元リポジトリと実行時custom_nodesフォルダはハッシュ比較で同一性を検証。

How to apply:

  1. ノードタイプ単位の汎用ロジック(役割伝播、ウィジェット値のインデックス復元など)を持つコードベースでは、特定モデル対応の不具合であっても「そのモデル専用」の分岐を足すのではなく、原因となっているノードタイプ(今回はPreviewAny/StringConcatenate/COMFY_DYNAMICCOMBO_V3)を汎用ロジック側に正しく組み込む修正を優先する。結果として対象モデル以外の同構成ワークフローにも自動的に恩恵が及ぶ。
  2. ウィジェット値の「インデックスから名前への復元」ロジックが複数箇所(今回は_flattenSubgraphs内の境界値注入と、最終API変換ループ)に重複実装されている場合、片方だけがDynamicCombo等の可変長ウィジェットを正しく理解していても、もう片方が単純な1対1対応を仮定しているとデータ破損が起きる。両方の実装が同じスロット数計算ロジックを共有しているか必ず確認する。
  3. 「バイパス」のような一般的な語でも、対象ノードの入出力型構成によって実際に構造的なComfyUI Bypassが成立するかは列ごとに異なりうる(LATENT型のKSamplerは素通し可能、入力を持たないLoader/Encoderは不可)。曖昧な機能要望は、AskUserQuestionで実装方式の選択肢とその技術的トレードオフを具体的に提示してから着手する。
  4. 低頻度機能をUIに追加する際は、既存のアコーディオン/折りたたみパターン(今回は<details>/<summary>+ .wfm-settings-section)を新規CSSクラスを増やさず再利用できないか確認する。

v0.3.96

バグ修正: カタログ作成バッチが特定件数で無言停止する問題の修正 + Style Catalog/Promptタブ機能拡張

ユーザーから「カタログ作成機能ですべてのスタイル(277)を選択して実行した149でバッチ処理が止まってしまった」との報告を受けて調査・修正した一連のセッション。合わせてStyle Catalogタブとカタログ作成機能、Promptタブへのスタイル管理機能追加も行った。

1. バッチ処理が無言で停止するバグの修正(comfyui-client.js): 調査の結果、trackProgress()(WebSocket経由で生成完了を待つPromise)にタイムアウトが一切なく、execution_interruptedイベントも未処理だったことが根本原因と判明。ComfyUI側からの完了通知が何らかの理由で届かなくなると(WebSocket瞬断・生成中断など)、Promiseが永久に解決されないままバッチループのawaitが固まり、エラーも出さずに停止する状態になっていた。_pendingTrackers(promptId→reject関数のMap)を追加し、(a) trackProgress()に10分のタイムアウトを追加、(b) execution_interruptedイベントのハンドリングを追加、(c) WebSocket切断(onclose)時に待機中の全Promiseを即座にrejectする仕組みを実装。既存の_runBatchLoop(generate-tab.js)は元々「エラー時は次のアイテムへ進む」try/catch構造を備えていたが、trackProgress()が例外を投げないまま無限に待ち続けていたためこのcatchに到達できていなかった。今回の修正で、意図されていた挙動(1件失敗しても次へ進む)が正しく機能するようになった。停止した149件目(MK shepard-fairey-style)自体はJSON構文・エンコーディングとも正常で、スタイルデータ側に問題は無いことも確認済み。

2. カタログ作成/バッチのスタイル処理順序の調査(コード変更なし): ユーザーからの追加質問(Batchタブの表示順とカタログ作成の生成順が異なる、149件目が停止する原因はファイルにあるか)に対して調査のみ実施。バックエンド_load_styles()(settings_routes.py)はstyle/*.jsonをファイル名のアルファベット順に読み込み、各ファイル内はJSON配列の記載順を維持する仕様であることを特定。Style Catalogギャラリーの表示順は生成順とは独立に常にsort=name_asc(ファイル名のアルファベット順)である仕様も確認。いずれも設計通りの挙動でありバグではないと結論。

3. Style Catalogタブ: ワークフロー再利用ボタンの追加: 「スタイルとして選択」ボタンの下に「Load in GenerateUI」「Open in Metadata Tab」の2ボタンを追加。当初「Outputフォルダの該当画像へ遷移するボタン」を検討したが、カタログ画像はOutput画像のバイト列そのままのコピー(extra_pnginfoのワークフローも含め完全に同一のPNGメタデータを保持)であるため、Output側の対応ファイルを探しに行かずカタログ画像自身から直接ワークフローを読み込む方式の方が、実装がシンプルな上に既存の全カタログ画像に対しても即座に使える(Output→カタログの対応を新たに記録する仕組みが不要)という利点があり、ユーザー確認の上でこちらを採用した。

4. Style Catalogタブ: ダブルクリックでの拡大表示: gallery-tab.js既存のopenLightbox()と同じCSSクラスをそのまま流用し、右ペインのプレビュー画像に加えて中央ペインのサムネイルカード自体にもdblclickを追加(Output Galleryと同一の操作感になるよう、ユーザーからの「操作が異なるため都度、間違える」というフィードバックを受けて追加)。ESCで閉じる処理はgallery-tab.js側の既存グローバルリスナーがクラス名ベースで拾うため追加実装不要だった。

5. Promptタブ右ペインにStyle管理サブタブを新設: 右ペイン(旧Wildcard専用)をタブ化し、Wildcard/Styleの2モードを切り替えられるようにした。Wildcardファイルマネージャー(一覧→エディタ→保存/削除/キャンセル)と同一のUXパターンで、登録済みStyle(DATA_DIR/style/*.json)の作成・編集・削除を実装。

  • バックエンドにPOST /api/wfm/styles(新規作成)・PUT /api/wfm/styles/{name}(更新・リネーム)・DELETE /api/wfm/styles/{name}(削除)を新設。リネーム時は元々定義されていたファイル内でその場を書き換え(他ファイルへ移動しない)、名前は全スタイル横断で一意になるよう検証。新規作成は既定でstyle/custom.jsonへ追記し、同梱のスタイルパックファイル(sdxl_styles_*.json)は直接変更しない設計とした。
  • 保存・削除の都度、generate-tab.jsに新規exportしたrefreshStylesList()を呼び出し、GenerateUIのStyleドロップダウン・Batchタブのチェックリストをリロード無しで再同期する。
  • 一時ディレクトリを使った単体テスト(_find_styleのファイル横断検索、リネーム時の位置保持、重複名の拒否など)で動作確認。
  • ファイル名バッジ + 既存ファイルへの追加(フォローアップ、ユーザーからの追加要望): _load_styles()が各エントリに定義元ファイル名をfileとして付与するように変更し、一覧の各項目にファイル名を小バッジ表示。既存スタイル編集中は「+ Add to this file」ボタンが現れ、クリックするとフォームが空になり同じファイルへ追加される新規エントリとして保存できる(POST /api/wfm/stylesのfileパラメータで対象ファイルを指定、実在するファイルのみ許可しパストラバーサルを防止)。

6. カタログ作成: Outputフォルダにコピーを残さないオプション: カタログ作成は元々、ワークフローのSave Imageノードが生成の都度Outputフォルダへ通常通り書き出した上で、その画像をカタログフォルダへコピー保存する仕組みだった。大量スタイル(200件超)を一括作成するとOutputフォルダがほぼ重複ファイルで溢れる問題があり、モーダルに「Don't keep a copy in the Output folder」チェックボックスを追加。カタログへのコピー保存が成功した後にのみ、新設のPOST /wfm/gallery/output-image/delete(filename/subfolder/typeからComfyUI Output配下の実ファイルを削除、type="output"以外は拒否)を呼び出してOutput側の元ファイルを削除する。コピー保存に失敗した場合は削除せず元画像を保持するため、データを失う経路が無い設計。既存のGalleryService.delete_images()(_check_path_allowedによるパス検証済み)をそのまま再利用しており、Outputフォルダ外のファイルを誤って削除する経路は存在しない。

ヘルプタブ更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、GenerateUI/Prompt/Galleryタブヘルプに今回の追加項目をそれぞれ反映。

検証: 各段階でNode --check(JS構文)・python -c ast.parse(Python構文)・一時ディレクトリでのバックエンドロジック単体テスト(スタイルCRUD、ファイル横断検索、削除経路の安全性)・開発元と実行時custom_nodesフォルダのdiff比較を実施。ComfyUI再起動を要する変更(バックエンドAPI追加)の都度ユーザーへ明示。

How to apply:

  1. WebSocketやポーリングで「完了通知を待つ」Promiseには、必ずタイムアウトを設定する。通知が絶対に届く保証はネットワークにもサーバー実装にも無く、タイムアウト無しのPromiseはバッチ処理のような長時間ループで「無言のまま停止する」最悪のバグを生む。周辺のtry/catchが正しく書かれていても、例外が投げられなければ意味が無い。
  2. 画像のバイト列そのものをコピー保存する機能(今回のStyle Catalog)では、コピー先の画像自体に元のメタデータ(埋め込みワークフロー等)がそのまま残っていることを活用すれば、コピー元との対応関係を別途記録・追跡する仕組みを作らずに済む場合がある。「元データへの参照を持つ」設計と「元データの複製として自己完結させる」設計のどちらが適切かは、コピー後に元データが削除・移動されうるかどうかで判断するとよい。
  3. 「Outputフォルダにファイルを残さない」系のオプションを実装する際、生成自体を変更する(ワークフローのSaveImageノードをPreviewImageに差し替える等)よりも、「一旦生成させてから、後続処理が成功したのを確認した上で元ファイルを削除する」方が実装が単純で、かつ後続処理が失敗した場合にデータを失わない安全な設計になる。

v0.3.95

機能追加: Style/Prompt→ImagePrompt改名 + Style_Catalogギャラリー新設 + GenerateUI「カタログ作成」機能

ユーザーから「GenerateUIタブのスタイルを画像から決めるためのスタイルカタログ機能を追加したい」との要望を受けて実装。まず前提としてGalleryタブの既存Style/Promptサブタブを「ImagePrompt」に改名(Style_Catalogと名前が紛らわしくなるのを避けるため)、続けて新規Style_Catalogサブタブと、GenerateUI側のカタログ生成機能を追加した。ブランチfeature/style-catalog-imageprompt(前セッションで未コミットのまま残していたfix/output-gallery-style-prompt-displayブランチから分岐、そちらの修正も本エントリに含む)。

1. Style/Prompt → ImagePrompt 全面リネーム: ユーザーから「内部ID/関数名/ファイル名も含めて全面リネーム」の指示を受け、タブラベルだけでなくSTYLE_PROMPT_FOLDER_NAME→IMAGE_PROMPT_FOLDER_NAME(値もws_style_prompt→ws_image_prompt)、APIルート(/wfm/gallery/style-prompt/root→/wfm/gallery/image-prompt/root等)、ファイル名(static/js/style-gallery-tab.js→image-prompt-tab.js)、関数名(initStyleGalleryTab→initImagePromptTab等)、CSSクラス(.wfm-style-*→.wfm-imageprompt-*)、i18nキー(styleGallery*→imagePrompt*)まで一貫して変更。実データフォルダもmvでリネーム(既存データはそのまま保持)。GenerateUI Batchタブの「Style」バッチ機能(_stylesData/_batchStyleSelected等、DATA_DIR/style/*.jsonのFooocus形式スタイルプリセット)とは全く別の概念であるため、こちらは意図的にリネーム対象から除外した。

2. 事前調査: 既存Style機能・バッチ生成ループの理解: 実装前にExploreエージェントで調査し、以下を把握した。

  • GenerateUIの「Style」はプリセットJSON({name, prompt, negative_prompt})をDATA_DIR/style/から読み込み、_applyNamedStyle(workflow, style)(generate-tab.js)がワークフローの複製に対して適用する。Positiveは{prompt}プレースホルダ置換または末尾追記、Negativeは常に末尾追記。
  • Batchタブに既に「Style」バッチ種別(複数スタイル選択→順次生成)が存在し、_runBatchLoop(items, applyFn, labelFn)という汎用バッチループを使っていた。カタログ作成はこの既存インフラをそのまま流用できると判断(新規バッチシステムを作らない)。
  • Positive/Negativeプロンプトの構造化抽出ロジック(ComfySwitchNode・CLIPTextEncodeEditPlus等の特殊ノード対応込み)はmetadata-tab.jsのextractAllMetadata(file)が既にexport済みで、Gallery側からもそのまま呼び出し可能と判明(Python側に同等ロジックは存在しない)。

3. 新規: Style_Catalogギャラリーサブタブ: Galleryタブ3つ目のサブタブとして追加。バックエンドはget_style_catalog_root()(get_image_prompt_root()と同型)のみ新設し、フォルダツリー・画像一覧・作成の各APIは既存の汎用エンドポイント(root/folderパラメータ対応済み)をそのまま再利用。フロントエンドは新規static/js/style-catalog-tab.jsで、image-prompt-tab.jsと同じツリー/グリッド実装を土台にしつつ、右ペインはプロンプトビルダーではなく「選択画像をfetch→Blob→File化→extractAllMetadata()でPositive/Negativeを抽出→個別コピーボタン」という読み取り専用パネルにした。「Select as Style」ボタンは、画像ファイル名(拡張子除く)と同名の登録済みStyleをgenerate-tab.jsに新規exportしたselectStyleByName(name)で探し、GenerateUIのStyleドロップダウンを切り替える方式(埋め込みプロンプトを直接適用するのではなく、既存Styleシステムへの視覚的ショートカットとする — ユーザー確認済みの方針)。

4. 新規: バックエンド save_image_to_folder: 既存のsave_image_to_gallery(画像保存ルート、常に固定のギャラリールート直下にのみ保存)を一般化し、任意フォルダ+任意ファイル名+上書き保存に対応するGalleryService.save_image_to_folder(folder_path, filename, image_data)を新設。data URLデコード・拡張子解決・ファイル名サニタイズのロジックは_decode_image_data_url()/_sanitize_save_filename()としてモジュール関数に切り出し、既存のsave_image_to_galleryハンドラもこちらを使うようリファクタ(重複コード解消、既存動作は不変)。新規ルートPOST /wfm/gallery/image/save-to-folder。

5. 新規: GenerateUI「カタログ作成」/「カタログ」ボタン: Style選択欄の右隣に設置。

  • スタイル選択の対象は、当初「全スタイル/選択中の1件」のラジオ選択UIを新設する設計で計画していたが、ユーザーから「カタログ作成の選択をバッチタブのStyleタブで行えるようにしたい。これにより複数選択も可能にしたい」とのフィードバックを受け、新規UIを作らずBatchタブStyleサブタブの既存チェックリスト(_batchStyleSelected)をそのまま使う方式に変更(複数選択は元から対応済み)。カタログ作成モーダルは保存先フォルダの指定(新規作成/既存選択)のみを担当する、よりシンプルな設計になった。
  • _runBatchLoopに第4引数onResultFn(item, result)を追加(既存呼び出し元は省略時no-opのため無影響)。カタログ作成はこのコールバックで、生成結果をcomfyUI.getImageBlob()→data URL化→save_image_to_folderへPOST(ファイル名=スタイル名、拡張子はバックエンド側でBlobの実MIMEタイプから解決)。
  • 「カタログ」ボタン(カタログ作成ボタンの右隣、ユーザーからの追加要望)は、Galleryタブ→Style_Catalogサブタブへワンクリックで遷移するショートカット。

6. Output Galleryのプロンプト表示修正(前セッションからの持ち越し): v0.3.94リリース直後、ユーザーがKapture目視確認で「Output Galleryの詳細パネルにプロンプトが表示されていない」ことを発見(リリースノートには書いたが実装漏れだった)。gallery-tab.jsのloadImageDetail()がmetaRes(image_promptフィールドを含む)を取得していながら使っていなかったバグで、renderImagePromptSection()を追加し表示欄(Info タブ、Tags欄の上)を新設して解消。ユーザーの指示で当時はコミット・リリースまでは行わず、今回のブランチに持ち越して一緒に含めた。

ヘルプタブ更新: project_v0336_conventionsの慣習(index.html + i18n.js 3言語 + app.jsのhelpIdMapの3点セット)に従い、Galleryタブヘルプの「Style/Prompt Gallery」カードを「ImagePrompt Gallery」に改名・ws_image_prompt表記に修正、新規「Style_Catalog Gallery」カードを追加。GenerateUIタブヘルプにもCreate Catalog/Catalogボタンの説明を追加。

検証: 各段階でNode --check(JS構文)・python -m py_compile(Python構文)・開発元と実行時custom_nodesフォルダのdiff -rq比較を実施。ユーザー自身が実機でGallery 3サブタブ化・ImagePromptの既存データ動作・カタログ作成→保存→Style_Catalogでの閲覧→Select as Style→カタログボタンでの遷移、を一通り確認済み。

How to apply:

  1. 新機能が既存のバッチ処理・生成ループと概念的に重なる場合、専用の新規実装を書く前に「既存のバッチループに小さなフック(コールバック引数など)を追加するだけで実現できないか」を検討する。今回_runBatchLoopへの1引数追加で、進捗バー・Pause/Resume・エラーハンドリングを含む既存バッチUI全体をタダで手に入れられた。
  2. ユーザーから「選択はA画面で行い、B画面はC機能だけ担当させたい」といった役割分担の指摘を受けたら、独自の選択UIを増やさず既存の選択UI(この場合Batchタブのチェックリスト)を再利用できないか確認する — 実装量が減るだけでなく、ユーザーにとって覚える操作系統が1つで済む。
  3. 「ファイル名を画像から画面Aのデータに紐付ける」機能を作る際、画像に実際に埋め込まれた値をそのまま複製・適用するのか、それとも名前で元のマスターデータ(この場合Style JSON)を検索し直すのかは、ユーザーの意図次第で大きく設計が変わる(前者はスナップショット的、後者は常に最新のマスターデータを参照する)。曖昧なら実装前に確認する。

v0.3.94

ユーザーから「Galleryタブをサブタブ化してスタイル・プロンプト用ギャラリーを追加したい。サムネイルによるスタイル/プロンプトの閲覧・プロンプト作成を行いたい」との要望を受けて新規実装。既存のGalleryタブの挙動は「Output」サブタブとしてそのまま残し、隣に「Style/Prompt」サブタブを新設した。参考としてcomfyui_prompt_gallery(CivitAI配布PonyXL wildcardパックをサムネイル一覧化しクリックでプロンプト挿入するツール)と、ユーザーの別プロジェクトprompt_builder_proto(CLIP埋め込み検索まで持つ高機能プロトタイプ)の2つが示されたが、後者の3D空間表示・埋め込み検索等は対象外とし、ユーザー指定の「左フォルダツリー/中央サムネイル/右にプロンプト表示+作成欄/下部コピーボタン」という素の画面構成のみを踏襲した。ブランチfeature/style-prompt-galleryで作業。

1. データ設計判断: プロンプトは.txtサイドカーで保存し、gallery_metadata.jsonには触れない: 既存のGalleryMetadataStore(gallery_metadata.json)は起動時にメモリへロードし保存時にファイル全体を書き戻す実装のため、ComfyUI実行中に外部スクリプトで数百件を直接書き込むと、サーバー側の別の保存操作で上書き・消失するリスクがあった。加えてcleanup_stale_images()はフォルダパスの前方一致でメタデータを判定しており、非再帰スキャンで親フォルダをlist_imagesすると、実際はサブフォルダに存在する画像のメタデータを「無くなった」と誤判定して削除してしまう潜在バグが既存コードにあることも判明(Output側のフラットな日付フォルダ構成ではほぼ顕在化しないが、深い階層を持つStyle/Promptギャラリーでは実害が出うる)。これらを踏まえ、プロンプトデータは画像と同名の.txtサイドカーファイル(例: mobile_trooper.png + mobile_trooper.txt)として画像フォルダに直接置く方式にした。実行中のJSONストアを一切触らないため競合リスクがゼロになり、かつ「画像+txtを置くだけ」でユーザー自身が将来データを追加できる最も簡単なフォーマットになる、という一石二鳥の設計。

2. バックエンド (gallery_service.py / gallery_routes.py): _read_sidecar_prompt()/save_style_prompt()で.txtサイドカーの読み書きを実装し、get_image_metadata()の戻り値にstyle_promptフィールドを追加(Outputギャラリー側からws_style_promptフォルダを閲覧した際にも同じ仕組みでプロンプトが表示される)。list_images()の検索フィルタにもサイドカー内容を追加。新規ルートGET /wfm/gallery/style-prompt/root・POST /wfm/gallery/image/style-promptを追加。Settingsでユーザーが Output ギャラリーのルートを実outputフォルダ以外に変更していてもStyle/Promptギャラリー(常に実output直下)へアクセスできるよう、_comfy_output_rootという不変ルートを_allowed_rootとは別に保持し、_check_path_allowed()をいずれか一方の配下なら許可するよう拡張した。

3. フロントエンド: templates/index.htmlのGalleryタブ先頭にlab-tab.jsの.wfm-lab-subtab-*と同一パターンの.wfm-gallery-subtab-navを追加し、既存の3カラムmarkupを#wfm-gallery-panel-outputでラップ、新規#wfm-gallery-panel-styleを追加。既存gallery-tab.js(1994行、favorite/tags/groups/bulk操作等が密結合したシングルトン実装)への機能追加によるregression リスクを避けるため、Style/Prompt専用の新規ファイルstatic/js/style-gallery-tab.jsを作成し、既存RESTエンドポイントを再利用しつつレンダリングのみ独自実装した。i18n.jsはen/ja/zh 3言語に新規キーを追加(既存のgalleryFolders/gallerySelectFolder/galleryNoImages/gallerySearchPlaceholder等、定義済みだが未使用だったキーをここで初めて実際に使用した)。ヘルプタブはproject_v0336_conventionsメモの「index.html + i18n.js(3言語) + app.jsのhelpIdMapの3点セット」の慣習に従い新規カードを追加。

4. シードデータ投入: tools/import_style_prompt_seed.pyを新規作成(PyYAML不要、comfyui_prompt_gallery/web/prompt_gallery.jsのparseYamlForImagesと同じインデントベースの簡易パーサをPythonに移植)。ユーザーのwildcard_dataフォルダ(pack: set: category: leaf: [tag_string]形式のYAML + thumbnails/配下の対応画像)のうち、サムネイル画像を伴う6カテゴリ(artstyle/body/expression/job/pose/scene)から、画像とタグの対が実際に揃っている460枚のみをoutput/ws_style_promptへコピー+.txtサイドカー生成した(残り487枚は元データ側にタグ定義が無く「テキストのみ」相当のため、ユーザー指示通りスキップ)。CivitAI配布パックの再配布ライセンス問題を避けるため、実データ画像はリポジトリにコミットせず、書き込み先はユーザー個人のローカルComfyUI outputフォルダのみとした。

5. フォローアップ改善(実機フィードバックを受けて):

  • 再帰的な画像表示: 親フォルダを選択してもサブフォルダの画像が0件表示になっていた(インポートしたデータが深い階層に配置されるため)。list_images()にrecursiveパラメータを追加し_scan_folder_recursive()(os.walkベース)を新設、Style/Promptギャラリー側は常にrecursive=trueで呼び出すよう変更。フォルダツリーのlist_folder_tree()もimage_count(直下のみ、Output側で従来通り使用)とは別にimage_count_total(サブフォルダ込みの合計、後方互換で追加)を返すようにし、ツリーの件数バッジもStyle/Prompt側では合計値を表示するよう修正。
  • 右ペイン2カラム化: 選択画像プレビュー/プロンプト/選択済みチップ(左)と、最終プロンプト(右、縦いっぱいに伸びる)に分割。詳細パネル幅を320px→520pxに拡大。
  • 選択済みプロンプトのチップ表示から拡張子を除去: chip.filename表示時に.replace(/\.[^.]+$/, "")。
  • ponyxlWildcardsVault形式のライブ(未インポート)対応: ユーザーがwildcard_dataのカテゴリフォルダ(yaml + thumbnails/、thumbnails_option2/の別プレビュー画像{leaf}.preview3.ext含む)を生のままws_style_prompt配下に置いて確認したところ「対応させたい」との要望。_read_sidecar_prompt()を「1. .txtサイドカーを優先 → 2. 無ければponyxlWildcardsVault形式としてその場でYAML解決」の2段構えに変更。_find_vault_category_root()で画像の祖先フォルダを最大8階層遡りthumbnails/thumbnails_option2を直下に持つフォルダ(=カテゴリルート)を探し、そこに隣接する*.yamlを都度パース(YAML群のmtimeシグネチャでインスタンス内キャッシュ)。二重拡張子ファイル名(alchemist.preview3.jpeg)にも対応するため、リーフ名候補はファイル名.split(".")[0]で取る。同じロジックをtools/import_style_prompt_seed.py側にも移植し、thumbnails_option2もスキャン対象に含めた。

6. フォルダ名タイポ修正: ユーザーが当初指定したフォルダ名ws_style_plomptが実はスペルミス(正しくはws_style_prompt)と判明。実データフォルダをmvでリネームし(460枚+vaultフォルダのデータはそのまま保持)、コード内の全参照(STYLE_PROMPT_FOLDER_NAME定数、index.html、i18n.js3言語のヘルプ文、style-gallery-tab.jsコメント、import_style_prompt_seed.py)を一括修正。

トラブルシューティング: 「ComfyUI再起動したのに404」の切り分け: バックエンドのルート追加後、ユーザーが「再起動した」と報告したにもかかわらず新規エンドポイントが404/405を返す事象が発生。curl -iでヘッダを確認したところ、GETの404レスポンスがContent-Type: application/octet-stream+Transfer-Encoding: chunkedという、自前のJSON APIハンドラ(常にweb.json_responseでapplication/json)とは異質なシグネチャだったことと、POSTが404ではなく405 Method Not Allowed(Allow: GET,HEAD)を返したことから、リクエストが自作ルートに全く到達せずaiohttpの静的ファイル系フォールバックに吸収されている=新しいルート登録が実際には有効化されていないと判断。netstatでポートを握っているプロセスを特定し、Get-Processでその起動時刻を確認したところ、コード変更よりずっと前から起動し続けているプロセスであることが分かり、「再起動」が実際にはポートを握るプロセスに反映されていなかったことを特定できた。

How to apply:

  1. 複数プロセス/外部スクリプトから同時に書き込まれうるJSONストア(GalleryMetadataStoreのような「起動時ロード→都度書き戻し」実装)に、バッチ処理や別プロセスからデータを注入したい場合は、そのストアを経由せずファイルシステム上の独立したファイル(サイドカー等)に逃がすことを検討する。競合の心配なく安全に外部から書き込める。
  2. cleanup_stale_images()のようなフォルダ配下メタデータの「前方一致による孤立削除」処理は、呼び出し元が実際にスキャンした範囲(非再帰なら直下のみ)を正しく反映したexisting_pathsを渡さないと、スキャンしていない孫階層のメタデータを誤って削除しうる。再帰スキャンに切り替える際は、この既存ロジックとの整合性を必ず確認すること。
  3. バックエンドAPIの新規エンドポイントが「再起動したのに反映されない」ように見える場合は、curl -iでレスポンスヘッダ(Content-Type、Transfer-Encoding、405のAllow等)を確認すると、リクエストが自作ハンドラに届いているか・別のフォールバック機構に吸収されているかの切り分けに使える。合わせてnetstatでポートを握っているPIDを特定しGet-Process/Get-CimInstance Win32_Processでそのプロセスの起動時刻を見れば、「再起動」がそのプロセスに実際に反映されているかを客観的に確認できる。
  4. CivitAI等サードパーティ配布のデータセットをローカルで加工・利用する場合、生成物(画像そのもの)はリポジトリにコミットせず、変換・投入スクリプトのみをコミットしてユーザー自身のローカル環境に対して実行する方式にすることで、再配布ライセンス問題を回避できる。

v0.3.93

バグ修正・機能改善: Labタブ VAEキーフレーム不発バグの修正+Style適用UI刷新+Plan JSONスクロール同期修正+キーフレーム個別削除+Batch数超過警告+ワイルドカード対応+Save index image デフォルトON

ユーザーからの実機フィードバックを受けて、Labタブに対して一連の修正・改善をまとめて実施した。

1. VAEキーフレームが2回目・3回目以降に反映されないバグの修正: ユーザーから「Labでチェックポイント焼き込みVAE構成のワークフローに対し、VAEキーフレームを2列目・3列目に追加して実行したが、生成画像のメタデータを確認するとVAEが変更されていなかった」との報告を受け調査。_buildWorkflowForIteration()(lab-tab.js)はanalysis.vae_nodes[0]という「ワークフロー中のVAELoaderノードを機械的に拾っただけの最初の1件」に無条件で書き込む設計だったが、独立したVAELoaderノードを持たず、チェックポイントに焼き込まれたVAEをそのまま使う構成のワークフロー(実際にユーザーが使用したsimple_i2i_save.jsonはVAELoaderノードが存在した通常構成だったため、このケース自体には該当しなかったが、独立VAELoaderが無いワークフロー全般に共通する潜在バグとして特定・修正)ではvae_nodesが空になり、キーフレームで指定した値が警告なく黙って捨てられていた。analysis.vae_nodes[0]が存在しない場合、その回のワークフロー複製にVAELoaderノードを動的に追加し、vae入力を持つ全ノード(VAEDecode/VAEEncode等)の接続先をそのノードへ張り替えるフォールバック処理を追加した。comfyUI.currentWorkflow自体は変更せず、_buildWorkflowForIteration()が毎回作るクローンにのみ適用するため、他のGenerateUIサブタブには影響しない。

2. Style適用UIの刷新: これまでPromptキーフレームの「Style」欄は、上部GenerateUIツールバーの Style 有効化チェックボックスがONの場合のみ機能する「Apply」ボタン(クリックするたびに上部で選択中のスタイル名をボタンラベルに表示)という設計だった。ユーザー要望により、(a) 上部の Style 有効化チェックボックスの状態に関係なく動作するように変更、(b) モーダル内に専用のON/OFFチェックボックスを追加してキーフレームごとに適用状態を保持、(c) 適用したスタイル名はボタン上ではなく横の別要素に表示、(d) チェックボックスをOFFにした際はその名前表示のみクリアし、既にPositive/Negativeへ追記済みのテキストは自動削除しない(あくまでユーザーが手動管理)という仕様に変更した。実装の紆余曲折として、当初「チェックボックスをONにした瞬間に自動でスタイルを追記する」形で実装したが、ユーザーから「追加は(従来通り)Apply ボタンで行いたい」とフィードバックを受け、Apply ボタン押下時にのみ追記しチェックボックスを自動ON化する方式に修正した。適用状態(styleApplied/styleName)はキーフレームのvalueに永続化し、Plan保存/読込・モーダル再オープンでも復元される。

3. Plan JSONタブのスクロール同期バグ修正: Plan JSONタブのテキストエリアは、色付きシンタックスハイライト用の<pre>オーバーレイを背後に重ねる仕組みだが、他のRaw JSON表示(GenerateUIのRaw JSON等)と異なりscrollイベントリスナーの配線が漏れていたため、テキストエリアをスクロールしてもオーバーレイの表示位置が追従せず「スクロールしても表示が変わらない」ように見えるバグがあった。json-highlight.jsのsyncScroll()をlab-tab.jsにimportし、_initPlanJsonTab()にscrollイベントリスナーを追加して解消した。

4. キーフレームをモーダルから個別に削除可能に: 各キーフレームの編集モーダルにある「削除」ボタンは、従来は列内の最後のキーフレームでのみ表示されていた。ユーザー要望により、1回目(baseline、常に固定)以外の全てのキーフレームで表示するよう変更(「−」ボタン自体は従来通り末尾のみ削除する仕様を維持)。途中のキーフレームを削除すると配列のspliceにより後続が自然に繰り上がる。

5. Batch数超過時の警告トースト追加: 「プランの適用回数よりBatch数が少なくなった場合はどうなるか」という質問を受けて挙動を調査したところ、Batch数を後から減らしてもキーフレームは削除・再採番されず、単に実行ループ(for (let i = 1; i <= _lab.batchCount; i++))が届かなくなるだけで無警告だった(データは残るため、Batch数を再び増やせば復活する)。ユーザー要望を受け、_maxKeyframeIteration()で全列の最大atIterationを求め、現在のBatch数を超えていれば警告トーストを出す_warnIfKeyframesExceedBatch()を追加。Batch数入力欄のchangeイベントと、_applyPlanData()(Plan読込・Plan JSON Apply共通)の両方から呼び出す。

6. Promptキーフレームでのワイルドカード対応: 「プロンプトでワイルドカードを使いたい」との要望を受け、GenerateUIメインの生成フローが既に持っていたSPA側ワイルドカード展開処理(generate-tab.jsの_expandWildcardsInWorkflow()、A1111形式__name__構文、Workflow-Studio/wildcards/配下の.txtから1行ランダム選択、ネストは最大5パス、ImpactWildcardEncode/Processorはサーバー側処理のため対象外)をexportし、lab-tab.jsの_runLabBatch()から各イテレーションのワークフロー構築直後・生成実行直前に呼び出すよう配線した。これによりPromptキーフレームのテキストに__name__を書いておけば、同じキーフレームを複数イテレーションで使い回していても毎回ランダムに再展開される。generate-tab.jsは既にlab-tab.jsからinitLabTabをimportしており、今回の変更で双方向の循環importになるが、両関数とも関数宣言(hoisted)でトップレベル即時実行はしていないため問題なし(Node上での動的import検証でモジュールグラフの解決自体に問題がないことを確認済み)。

7. 「Save index image to Output on Run」のデフォルトON化: ユーザー要望により_emptyLabState()の初期値をfalse→trueに変更。あわせて、修正過程で_clearPlan()がsaveIndexEl.checkedを無条件にfalseへ固定していた(内部状態とチェックボックス表示が食い違う潜在バグ)ことに気づき、_lab.saveIndexOnRunの実値を反映するよう修正した。既存の保存済みPlanファイルはsave_index_on_runの値をそのまま保持するため影響を受けない。

検証: 各修正・機能追加ごとにnode --checkで構文検証、開発元リポジトリと実行時custom_nodesフォルダのdiff比較で意図した差分のみであることを確認してから同期。VAE修正はanalyzeWorkflow()の実データ(ユーザー提供のsimple_i2i_save.json)に対するNode単体実行でvae_nodes検出ロジックを確認済み。循環import追加分はNode上でimport()によるモジュール解決を検証済み(window is not definedはNode実行環境固有の話で、モジュールグラフ解決自体は正常に完了することを確認)。

How to apply: (1) analysis.*_nodes[0]のような「先頭要素決め打ち」パターンは、そのノード種別がワークフロー中に必ず1つ存在する保証がない限り、存在しないケースへのフォールバックを用意すること(VAELoaderのように「省略可能」なノードは特に注意)。(2) UIのON/OFFチェックボックスとその内部状態(_lab.xxx)を分離して持つ場合、リセット系の関数(_clearPlan()等)で値をハードコードせず、必ず内部状態から読み出すこと——デフォルト値を変更した際に無言でズレるバグの温床になる。(3) generate-tab.jsとlab-tab.jsは既に循環importの関係にあるため、新たに一方から他方の関数を使う場合はトップレベルで即時評価される値(constの即時代入等)を挟まないよう注意する(関数宣言はhoistされるため安全)。


v0.3.92

バグ修正: CLIPTextEncodeEditPlus使用ワークフローがGenerateUI経由だと直接実行と異なる画像になる問題(根本原因はconvertUiToApiのForceInput 未考慮バグ)+LoRA Stack機能の改善一式

ユーザーから「CLIPTextEncodeEditPlus(CLIP Text Encode edit+、model-and-prompt-from-metadata由来)とLora Loader (LoraManager)を組み合わせたワークフローを、ComfyUIで直接実行した場合とWorkflow StudioのGenerateUI経由で実行した場合とで生成画像が異なる。シードも固定しているのに一致しない」という報告を受け、一連の調査・修正を行った。

1. Metadata/Infoタブでのプロンプト抽出漏れ: CLIPTextEncodeEditPlusはPositiveプロンプトの実体をtext1(リンク入力、例えばLoraManagerのtrigger_words出力)とtext_editウィジェットをmode(RAW/EDIT/front/back)に応じて結合する専用ノードだが、metadata-tab.js(およびその移植版node_sets_menu.jsのIタブ側)のextractPromptsAPI/extractPromptsFromNodeSetはwidgets_values[0](=text_editのみ)を素朴に読むか、API形式ではinputs.text/text_g/promptキーしか見ておらずこのノード自体を全く認識できていなかった(結果、生成画像のPNG内蔵メタデータを見るとPositiveが空、Negativeしか表示されない)。ノード本体のPython実装(metadata_checkpoint_node.pyのCLIPTextEncodeEditPlus.encode())と完全に同じ結合ルールをresolveEditPlusText(mode, textEdit, text1, text2)として両ファイルに実装し、LiteGraph形式・API形式それぞれのtextMap構築ロジックに組み込んだ。なおtext1がLoraManagerのtrigger_wordsのように実行時に動的計算される値の場合、ワークフローJSON自体にはそのテキストが保存されていないため静的解析では復元できない(この制約はユーザーに明示済み)。

2. 根本原因: convertUiToApiの_getWidgetInputNames/_getWidgetInputTypesがforceInput: trueを考慮していなかった: 上記の表示バグを追っている過程で、実際にComfyUIサーバー(http://127.0.0.1:8189/object_info/CLIPTextEncodeEditPlus)から取得したobject_infoと突き合わせたところ、真の実行結果不一致の原因を発見した。text1/text2はComfyUI側で["STRING", {"forceInput": true}](リンク専用・ウィジェット化されない入力)として定義されているが、comfyui-workflow.jsの_getWidgetInputNames/_getWidgetInputTypesはこのforceInputフラグを一切チェックしておらず、型がSTRING/INT/FLOAT/BOOLEAN等であればoptional入力も無条件にウィジェット名列へ含めていた。そのためtext2(未接続)が誤って「ウィジェット」として扱われ、widgets_valuesの3番目に入っていた本来どのウィジェットにも対応しない余りの空文字列""がtext2の値として吸い込まれ、送信されるAPIワークフローにtext2: ""が明示的に含まれてしまっていた。Python側のencode()はinsert = text2 if text2 is not None else text_editという判定をしており、text2=""はNoneではないためinsert = ""が採用され、mode: "back"の場合selected = text1 + ", " + ""——text_editの内容が完全に欠落していた。ComfyUI本体から直接実行した場合は未接続のforceInput入力が送信データに一切含まれずtext2=Noneとなるため、この欠落は発生しない。これが「同じワークフローなのに画像が異なる」「LoRA強度を変えても結果が変わらない(そもそも肝心のプロンプト本文が消えていたため無関係だった)」の直接の原因。_getWidgetInputNames/_getWidgetInputTypes双方の必須・オプション入力ループにif (Array.isArray(spec) && spec[1]?.forceInput) continue;を追加して除外するよう修正。この修正はforceInput付きオプション入力を持つ他のノード全般にも同様に効く汎用修正。

3. GenerateUI Model タブ — LoRA Stack機能の改善: 上記の検証と並行して、LoRA周りのUI/UXも3点改善した。

  • Use Stack Groupチェックボックス: Lora Loader (LoraManager)ノードが存在するワークフローを読み込んだ際、従来はStackタブが常にModelsタブの登録済み「Stack」グループを参照しており、ワークフロー自体が保持している実際のLoRA構成とは無関係だった。新設したチェックボックスで、OFF(ワークフロー読み込み時の既定)はLoraManagerノードに実際に設定されているLoRAをそのまま表示・編集し、ON はModelsタブの「Stack」グループを表示・編集するよう切替可能にした。切替時は必ず対象ノードのLoRA一覧をクリアしてから新しいソースを読み込み、OFFへ戻すとワークフロー読み込み時点のLoRA一覧(読み込み時にスナップショットとして保存)へ正確に復元される。
  • LORA SYNTAX表示が1件しか出ない不具合の修正: SingleタブのLORA SYNTAXはLoraManagerノードのloras[0](1件目)のみを見る設計(Single=1件専用のため仕様通り)だが、Stackタブ側もModelsタブ登録グループしか見ておらず、結果的に「ワークフローに実際設定されている複数LoRA」を表示する手段がどこにもなかった。上記のチェックボックスと合わせて、新規読み込み時にLoRAが2件以上あれば自動でStackタブを初期表示するようにし、正しく全件表示されるようにした。
  • タブ切替でStackのON/OFF状態が意図せず戻る不具合の修正: 「ワークフロー状態」表示モードでは、Modelタブへ戻るたびの単純な再描画のたびに各LoRAの有効/無効状態(_stackActive)をワークフロー本来のデータで毎回強制上書きしていたため、ユーザーがStackパネルのトグルオールチェックボックスをOFFにしても次の再描画で無条件にONへ戻ってしまっていた。ワークフロー実データからの同期を「新規読み込み時」と「Use Stack GroupチェックボックスをOFFに戻した直後」の2タイミングのみに限定し、それ以外の再描画では既にメモリ上にある値を維持するよう修正。ついでにstackModels.every()が空配列に対し空虚な真を返すことで、LoRAが0件のときトグルオールチェックボックスが誤ってON表示になる副次的な表示バグも修正した。

検証: ユーザー自身が実機で確認。(1) lora_manager_normal_clip.json(ネイティブCLIPTextEncode使用)でComfyUI直接実行とGenerateUI実行の生成画像が同一であることを確認。(2) lora_m_editplus_comfyUI.json(CLIPTextEncodeEditPlus使用、mode: back)でも同様に画像が同一であることを確認。(3) 残る3モード(RAW / EDIT / front)についても結果が同一であることを確認済み。実サーバーの/object_infoを取得しての突き合わせ、および修正前後のconvertUiToApiロジックの単体シミュレーションで根本原因を再現・修正確認済み。

How to apply: (1) ComfyUIのカスタムノードでoptionalのforceInput: true入力を追加する場合、Workflow Studio側のUI→API変換ロジックはこのフラグを尊重する前提で書くこと(_getWidgetInputNames/_getWidgetInputTypesが該当)——見落とすと、未接続時に本来送信されるべきでない値が紛れ込み、Pythonのif x is not None判定を静かに壊す。(2) ワークフローJSON上に存在しない・実行時にしか計算されない値(今回のLoraManagerのtrigger_wordsのように)は、静的なメタデータ抽出では原理的に復元できないことをドキュメント・ユーザー説明の両方で明示すること。(3) 複数箇所に同種のロジックが移植されている場合(metadata-tab.jsとnode_sets_menu.jsなど)、片方だけ直して終わりにせず両方に同じ修正を適用し、node --check+差分比較で開発元/ランタイム両フォルダの同一性を確認してから同期する。


v0.3.91

機能拡張: Labインデックス画像にPlanメタデータをPNG埋め込み+README構成整理(Changelogセクション廃止)

v0.3.90でLabタブに追加した「Run時インデックス画像Output保存」機能について、ユーザーから「インデックス画像にPlanのメタデータは埋め込まれていないのでは」と確認を受け調査したところ、実際に埋め込まれていないことが判明。_buildIndexImageDataUrl()はcanvas.toDataURL("image/png")でピクセルデータのみのプレーンなPNGを生成しており、バックエンド側(lab_plan_service.pyのsave_index_image()/save_index_image_to_output())も受け取ったbase64をそのままバイト列として書き込むだけだった。特にOutputフォルダ側のコピーは対になる.jsonが存在しないため、画像単体では「どのPlanで生成したか」を後から追跡する手段が一切なかった。「Planドロップエリアにドロップして読み込める形にしたい」という要望を受け対応。

PNG埋め込みの実装: static/js/util.jsにembedPngTextChunk(dataUrl, keyword, text)を新規追加。PNGのIHDR直後にiTXtチャンクを挿入する(挿入位置・CRC計算ロジックは、ComfyUI本体が既に持つワークフロー埋め込み機能=web/comfyui/top_menu_extension.jsのembedWorkflowInPngと同じ方式を踏襲)。tEXtではなくiTXtを選んだ理由: このコードベースの既存PNGチャンク読み取り(metadata-tab.jsのreadAllPNGTextChunks)は、tEXtのtextフィールドをLatin-1でデコードする実装になっている。一方書き込み側(embedWorkflowInPng含む)はTextEncoder().encode()でUTF-8エンコードしているため、非ASCII文字(日本語のNoteやプロンプトなど)を含むテキストをtEXt経由で埋め込むと、読み取り時にLatin-1誤変換で文字化けする。iTXtチャンクは仕様上UTF-8前提であり、readAllPNGTextChunksのparseITXtChunkは既に正しくUTF-8デコードを行っているため、新規キーwfm_lab_planはiTXtで統一した(既存のtEXt/workflow埋め込みが同じ問題を抱えている可能性があるが、それは既存機能でありスコープ外として手を付けていない)。

読み書きの配線: metadata-tab.jsのreadAllPNGTextChunksを新たにexport(元は非export、Lab側から再利用するため)。lab-tab.jsにLAB_PLAN_PNG_KEY = "wfm_lab_plan"を追加し、_buildIndexImageDataUrl(planData)が省略可能引数planDataを受け取ってJSON文字列化・埋め込むよう変更。呼び出し元の_savePlan()と_maybeSaveIndexImageOnRun()双方で、既に構築済みの_buildPlanData()の結果を渡すようにした——Plan Save側(lab_plan/内、対になる.jsonが既にある)にも同じ埋め込みを行い、画像単体を持ち出しても再現可能にする一貫性を優先。_loadPlanFromIndexImage(file)は、まずreadAllPNGTextChunks(file)でチャンクを直接読みwfm_lab_planがあればJSON.parseして_applyPlanData()へ渡す(サーバー通信不要)よう変更し、埋め込みが無い古いインデックス画像に対してのみ従来の「同名.jsonをサーバーから取得」にフォールバックする。

検証: (1) Node単体テストでembed→PNGバイト解析→JSON.parseの往復を検証(日本語+絵文字を含む文字列が完全一致することを確認)。(2) Kaptureで実際にComfyUIを操作し、T2Iモード+Save index on Run+日本語/絵文字入りNoteでRunを実行、実際に生成されたLab_index_00003_.pngの実バイトを別のNodeスクリプトで直接読み取り、埋め込まれたwfm_lab_planチャンクの内容が実際のPlan状態と完全一致することを確認。(3) ブラウザのevaluate経由でfetch+File+DataTransfer+合成dropイベントを構築し、サーバー側に対応する.jsonが一切存在しないこのOutputフォルダPNGを実際にPlanドロップゾーンへドロップ、T2Iモード・Save index on Runチェックボックス・Note(日本語+絵文字)・Batch数・ワークフロー記録(Open Workflowボタン)が全てサーバー通信なしで正しく復元されることをスクリーンショットで確認済み。

README構成整理: README.mdの## Changelogセクション(v0.1.1〜v0.3.90まで692行)を全削除。バージョンを重ねるごとに肥大化しており、GitHub Releaseのリリースノートと内容が重複していたため、ユーザーの意向で「今後はGitHub Releaseのリリースノートのみでバージョン履歴を管理する」方針に変更。あわせてCLAUDE.mdのリリース手順から「READMEのChangelogにエントリを追加」の手順を削除し、代わりにgh release createのリリースノート本文に変更点を記載する旨を明記した。README.mdは692行分の削減(1248行→556行)。

How to apply: (1) 今後PNGに新しいカスタムメタデータを埋め込みたい場合は、必ずtEXtではなくiTXtを使うこと(このコードベースのtEXt読み取りはLatin-1decodeのため非ASCII文字が壊れる)。util.jsのembedPngTextChunk(dataUrl, keyword, text)とmetadata-tab.jsのreadAllPNGTextChunks(file)(export済み)がその書き込み/読み取りのペア。(2) リリース時のバージョン履歴はREADME.mdではなくgh release createのリリースノートに記載する(README.mdにChangelogセクションを復活させない)。


v0.3.90

機能拡張: Labタブの進化(ワークフロー呼び出し・T2I対応・Run時インデックス画像Output保存+Eagle連携・プラン接頭辞)+Galleryタブのルート表示簡略化

v0.3.87〜v0.3.89で実装したLabタブ(GenerateUI)に対する、実際の運用を通じたユーザーからの一連の追加要望をまとめて対応。

1. Plan JSONへのワークフロー記録+呼び出し(Open Workflowボタン): これまでLabのPlanには「どのワークフローに対して」実行したキーフレーム設定なのかが記録されておらず、後日Planを見返しても再現できない問題があった。_buildPlanData()(lab-tab.js)が#wfm-gen-wf-nameのdataset.filename(GenerateUIに現在ロード中のワークフローファイル名)を読み取りworkflow_filenameとしてPlan JSONに保存するようにし、Plan読込後にPLANボックス内へ表示されるOpen Workflowボタンから、その記録されたワークフローファイルを/api/wfm/workflows/raw経由で取得しloadWorkflowIntoEditor()へ渡してGenerateUIへ読み込み直せるようにした。自動読込はせず、常にwindow.confirmで確認を挟む(現在編集中のワークフローを不意に上書きしない安全策、ユーザー指定)。loadWorkflowIntoEditorはgenerate-tab.jsにあり、generate-tab.jsは既にlab-tab.jsからinitLabTabをimportしているため、静的importすると循環import(generate-tab.js→lab-tab.js→generate-tab.js)になる。v0.3.87でgetEagleSettings/saveToEagleをutil.jsへ移設して回避した前例があるが、loadWorkflowIntoEditorはgenerate-tab.js固有の依存が多く移設は非現実的なため、今回はボタンのクリックハンドラ内でconst { loadWorkflowIntoEditor } = await import("./generate-tab.js");と動的importする方式を採用(gallery-tab.jsがtagger-tab.jsに対して既に使っている前例パターンを踏襲)。

2. T2Iワークフロー対応: LabはもともとI2Iバッチ生成専用で、ソース画像が無いと_runLabBatch()がlabNoSourceImageエラーで実行をブロックしていた。Imageペイン最上部に**T2I workflow (no source image)**チェックボックスを追加し、ON時は(a) ソース画像必須チェックをスキップ、(b) _buildWorkflowForIteration()でLoadImageノードへの書き込みを完全にスキップ(imageRef = null)、(c) Imageドロップゾーンと「Use generated image for next」をグレーアウト(既存の画像は削除せず、I2Iに戻したときのため保持)。Resultsパネルのソース画像欄には代わりに「T2I workflow (no source image)」の注記を表示。設定はt2i_modeとしてPlan JSONに保存。実機確認では、実際にT2Iワークフロー(EmptyLatentImage使用、LoadImageノード無し)で画像生成に成功し、Eagle自動保存も正常動作することを確認した(検証中、テスト対象ワークフローのCheckpointが存在しないモデル名だったことによる/prompt 400エラーに遭遇したが、これはT2I機能自体のバグではなくテスト環境のモデル設定の問題だった)。

3. Run時にインデックス画像をOutputフォルダへ保存+Eagle連携: 右ペインにSave index image to Output on Runチェックボックスを追加。ONの場合、Run完了ごとに_buildIndexImageDataUrl()で作った9枚コンタクトシートを新設バックエンドAPI POST /api/wfm/lab/index-image/save-to-outputへ送信し、py/services/lab_plan_service.pyのsave_index_image_to_output()がfolder_paths.get_output_directory() + folder_paths.get_save_image_path("Lab_index", output_dir)を使って、通常の生成画像と同じ自動採番規則でLab_index_00001_.pngのように保存する(上書きしない)。Plan Save側の動作(lab_plan/への保存)は完全に独立したまま変更していない。続けて「インデックス画像もEagleに保存したい」という追加要望を受け、Output保存が成功した後に既存のグローバル設定(SettingsタブのeagleAutoSave、生成画像のEagle保存と同じ仕組み)を流用してsaveToEagle()を呼ぶだけの実装を追加——Lab専用の別トグルは増やさず、既存パターンにそのまま乗せた。設定はsave_index_on_runとしてPlan JSONに保存。バックエンドのルート登録はComfyUI起動時の1回のみのため、この機能だけはPythonファイル変更を伴いComfyUI再起動後に実機確認(OutputフォルダへのLab_index_00001_.png保存、コンソールの[Eagle] Saved: Lab_index_00002_.pngログの両方を確認)。StabilityMatrix環境ではcustom_nodes/.../outputが実体Images/Text2Imgへのsymlinkになっている点に留意(folder_paths.get_output_directory()はsymlink越しに正しく解決される)。

4. プランファイル名へのws_labplan_接頭辞自動付与: _savePlan()(Plan Save/Save As共通処理)で、プロンプトに入力された名前から_stripLabPlanPrefix()で既存の接頭辞を除去してからws_labplan_を付け直す方式にし、二重接頭辞を防止(ユーザーが手動で接頭辞込みの名前を入力しても安全)。JSON内部の"name"フィールドは接頭辞なしのプレーンな名前のまま(表示用と実ファイル名を分離)。Plan JSONサブタブのプレビュー(_syncLabJsonView)も同じ接頭辞除去ロジックで統一。

5. Galleryタブ — ルートフォルダ表示の簡略化: フォルダツリーのルートエントリが実際のディレクトリ名(例: StabilityMatrix環境のText2Img — outputフォルダがImages/Text2Imgへのsymlinkになっているため)をそのまま付けて「[root] Text2Img」と表示していたが、ルートにはT2I以外の画像(Lab_indexなど各種生成物)も含まれるため実態と合わず紛らわしいという指摘を受け、gallery-tab.jsのrenderTreeNode()で単に「[root]」とだけ表示するよう変更(label.textContent = isRoot ? "[root]" : node.name;)。

README構成の見直し: Featuresセクションが17個の### <タブ名>セクションで非常に長くなっていたため、GitHub Flavored MarkdownのHTML <details><summary>要素で各タブセクションを折りたたみ可能にした(見出しレベルは<summary><h3>...</h3></summary>として維持)。Changelog等の他セクションは対象外(ユーザー指定は「タブの項目ごと」=Featuresセクションのみ)。

検証: 4機能とも実機(Kapture経由でComfyUIのhttp://127.0.0.1:8189/wfmを直接操作)で確認済み。ワークフロー呼び出しはPlan保存直後にファイル名とボタンが表示されること・クリックで確認ダイアログ→再読込を確認(当初Save経路でUI更新関数_updatePlanWorkflowUI()を呼び忘れる不具合があり、実機確認で発覚・修正)。T2Iモードは実際の画像生成成功、Output保存はComfyUI再起動後にLab_index_00001_.pngの生成、Eagle連携はコンソールログとファイル両方で確認、Galleryルート表示はスクリーンショットで確認。いずれもテスト用に生成したファイルは検証後に削除(Output画像の削除はユーザー側で実施)。

How to apply: (1) Lab関連でstateを更新する処理(Save/Load/Clear)を追加・変更する際は、対応するUI更新関数を全ての状態変更経路で呼び忘れていないか確認すること — 今回まさにSave経路で呼び忘れて実機確認で発覚した。(2) lab-tab.jsからgenerate-tab.jsの関数を使いたい場合、util.jsへの機能移設が大げさすぎるなら動的import(await import("./generate-tab.js"))を検討する。(3) 新しいLabのRun時副作用(Output保存等)を追加する際は、可能な限り既存のグローバル設定(Eagle自動保存など)に乗せ、機能ごとに新しいトグルを増やさないこと。(4) StabilityMatrix環境のようにComfyUIのoutputフォルダがsymlinkになっている場合でもfolder_paths.get_output_directory()は正しく解決されるが、ls -lt等で直接調査する際はsymlinkの実体を辿る必要がある。


v0.3.89

バグ修正: v0.3.88で追加したLab機能への実動作確認フィードバック3件を反映

v0.3.88で追加したLabタブのPromptセル拡張機能について、実際に触ったユーザーから3件の修正依頼を受けた。

1. Get from Imageが任意のファイルを選ばせる仕様だった: 当初はファイル選択ダイアログで別途画像を選ぶ実装だったが、「Labタブで現在読み込んでいる画像から取得したい」という指摘を受け、_lab.sourceImageFilename(IMAGE欄に既にドロップ・アップロード済みの画像)を/view?filename=...&type=input経由で取得しFileオブジェクトへ変換した上でextractAllMetadata()にかける方式に変更。ファイル選択ダイアログ(隠し<input type="file">)は不要になったため削除。画像未読込の場合は既存のlabNoSourceImageトーストで案内する。

2. スタイル適用が{prompt}プレースホルダを置換していた: generate-tab.jsの_applyNamedStyle()と同じ「スタイルテンプレートの{prompt}を既存テキストで置換する」ロジックを流用していたが、「既存のプロンプトはクリアしないで追加したい。既存があればカンマ区切り、なければそのまま」という明確な要望を受け、_applyStyleToText()をプレースホルダ置換なしの単純追記(既存 + ", " + style.prompt、既存が空ならstyle.promptをそのまま)に書き換えた。GenerateUI本来のStyle機能(_applyNamedStyle)とは意図的に異なる仕様になったため、コード上にその旨のコメントを残した。

3. GenerateUIタブ Modelサブタブの各フィルターにクリアボタンがなかった: Checkpoint/VAE/Diffusion Model/ControlNet/Hypernetwork(comfyui-editor.jsのrenderModelTab、ループでフィルター入力を動的生成)とLora Single(renderLoraPane)のフィルター入力に、Models/Nodes/Workflow/Galleryタブの検索ボックスやLabタブのCheckpoint/VAEフィルターと同じsetupSearchClearBtn()(util.js)+.wfm-search-wrap/.wfm-search-clear-btnパターンでオーバーレイ✕クリアボタンを追加。renderModelTabは複数フィルターをループ生成するため、各入力にid="wfm-model-${s.key}-filter"/クリアボタンにid="wfm-model-${s.key}-filter-clear"を採番し、フィルター再構築処理をinputイベントとsetupSearchClearBtnのコールバックで共有する形にした。

あわせてヘルプを更新: Labサブタブのヘルプ(helpLab8、index.html+i18n.js日英中)を新しい挙動(Get from Imageの取得元、スタイルの追記方式)に合わせて修正。

検証: Kaptureで実ブラウザ確認 — GenerateUI Modelタブの各フィルター(Checkpoint/Lora)で入力→✕クリア→リスト復元、Labタブで画像未読込時のGet from Imageのエラートースト、既存Positiveテキストへのスタイル追記結果({prompt}が置換されずそのまま末尾に追加され、既存テキストが先頭に保持されることを確認)。いずれもコンソールエラーなし(既存の無関係なOllama接続エラーを除く)。

How to apply: 新機能を実装した直後の「実際に触ってみた」フィードバックは、当初の設計判断(本実装ではEagle本来のUI流用や{prompt}置換の踏襲)を覆す具体的な仕様変更を伴うことが多い。既存の類似機能(ここではgenerate-tab.jsの_applyNamedStyle)のロジックをそのまま流用するのではなく、要望に応じて意図的に別実装へ分岐させてよい場面があることを踏まえ、分岐させた箇所には理由をコメントで残すこと。フィルター入力を動的ループで生成するコード(renderModelTab等)にクリアボタンを追加する際は、ループ内で一意なIDを採番しsetupSearchClearBtnをループ内で呼び出す。


v0.3.88

機能拡張: Labタブ Promptセルにスタイル適用・画像/GenerateUIからのプロンプト取得、Checkpoint/VAEフィルター、Plan JSONサブタブを追加

v0.3.87で追加したLabタブ(GenerateUI)に対するユーザーからの一連の改善要望をまとめて対応。

Promptセル編集モーダルの拡張:

  • Get from GenerateUI: 現在GenerateUIに読み込まれているワークフローのcomfyUI.currentAnalysis.prompt_nodesからPositive/Negativeを直接取得
  • Get from Image: 画像/ワークフローJSONをドロップ/選択すると、Metadataタブの抽出ロジック(metadata-tab.jsのextractAllMetadata()。新たにexportして再利用)でプロンプトを抽出
  • Style適用: 当初はモーダル内に独自のスタイルドロップダウンを設けたが、ユーザーから「GenerateUIツールバー上部の#wfm-style-enabledチェックボックス/#wfm-style-selectドロップダウンをそのまま使えないか」と指摘を受け設計変更。モーダル内の選択UIを廃し、Applyボタンが常にツールバー側の現在値を読んで適用する方式にした(チェックボックスOFFまたは未選択ならエラートースト)。ボタンラベルにも選択中のスタイル名を動的表示(例:「Apply: cinematic-diva」)。プロンプトへのマージ処理自体はgenerate-tab.jsの_applyNamedStyle()と同じ置換ルール({prompt}プレースホルダ置換/ネガティブ追記)を、ワークフローではなく文字列に対して行う_applyStyleToText()として実装

Checkpoint/VAEセルにフィルター機能追加:

  • GenerateUIのModelサブタブ(comfyui-editor.jsのrenderModelTab)と同じ「Filter...」テキスト入力で<select>の候補を絞り込み
  • 続けて「オーバーレイ✕でクリア可能にしたい、Modelタブの各フィルタリング機能・Raw JSON検索と同様に」という要望を受け、Models/Nodes/Workflow/Gallery各タブの検索ボックスやRaw JSON検索と同じsetupSearchClearBtn()(util.js)+.wfm-search-wrap/.wfm-search-clear-btn(main.css)パターンを流用し、入力欄内に✕クリアボタンをオーバーレイ表示するよう変更

Plan JSONサブタブ追加:

  • Setting/Resultsに続く3つ目のサブタブとして新設。現在のLab状態(列のキーフレーム・Note・Batch数・画像連鎖設定・元画像・結果一覧)を、プランファイル保存時と同じ構造のJSONとしてjson-highlight.jsのsyncJsonHighlight()で構文ハイライト表示する
  • Refresh: Settingタブの現在値で再同期(未適用の編集は破棄)
  • Apply to Setting: テキストエリアの内容をJSON.parseし、Plan Load(_loadPlanFromFile/_loadPlanFromServer)と共通の_applyPlanData(filename, data)を呼んでSettingタブへ反映。不正なJSONの場合はJSON.parseの例外メッセージをそのままエラートーストに出し、状態は変更しない
  • 保存用のプランオブジェクト構築ロジックが_savePlan()内にインライン実装されていたのを_buildPlanData(name)として切り出し、Save・Plan JSON表示の双方から共用するようリファクタリング

あわせてヘルプを更新: Labサブタブのヘルプページ(index.html+i18n.js日英中+app.jsのhelpIdMap)に、Promptモーダルの追加ボタン群とPlan JSONサブタブの説明を追記。

検証: 各機能をKaptureで実ブラウザ確認 — Get from GenerateUI/Get from Imageでの実際のプロンプト取得、Style Apply(トップバーOFF時のエラートースト・ON時の{prompt}置換とネガティブ追記)、Checkpoint/VAEフィルターの絞り込みと✕クリア、Plan JSONの表示→編集→Apply→Setting反映、および不正JSON入力時のエラーハンドリング。いずれもコンソールエラーなし(既存の無関係なOllama接続エラーを除く)。

How to apply: 複数モジュールで使う汎用UI部品(setupSearchClearBtn、extractAllMetadata等)は積極的にexportして使い回すこと。「モーダル内に専用UIを新設する」より「既存のツールバー等の状態をそのまま参照する」方がユーザーにとって一貫性があり実装も軽い場合があるため、要望の意図が既存UIの流用にあるかどうかを都度確認する。プラン保存構造とJSONビュー表示は同じビルダー関数(_buildPlanData)を共有させ、二重管理を避ける。


v0.3.87

追加機能: GenerateUIタブに実験的な「Lab」サブタブを追加(I2Iバッチ生成・キーフレーム方式)

「I2Iのバッチ生成を、回数ごとにCheckpoint/VAE/Prompt/KSamplerを個別に変更しながら実行したい」という依頼を受け、GenerateUIの新しいサブタブとして実装(ブランチfeature/lab-tab-i2i-batch)。既存のBatchサブタブが「1軸を単純に流すバッチ」なのに対し、Labは「列(Checkpoint/VAE/Prompt/KSampler)ごとに独立したキーフレームで、何回目からどう変わるか」を指定できる点が異なる。

データモデルと解決ロジック: 各列は行グリッドを共有せず、{atIteration, value, revertToBase}のキーフレーム配列を個別に持つ。反復回数Nでの実効値は「N以下で最大のatIterationのキーフレーム」を採用し、次のキーフレームまで引き継ぐ。revertToBaseは1回目の値(空なら読み込み中ワークフローの既定値)を再利用する。1回目のキーフレームは削除不可で、空欄なら常にワークフロー本来の設定を使う。Labはどの列もcomfyUI.currentWorkflowを直接書き換えず、実行時にディープクローンした上でのみ値を適用するため、Input/Model/Settings/Batchサブタブの編集状態と干渉しない。

バックエンド: py/services/lab_plan_service.py(wildcard_service.pyのディレクトリCRUDパターンを踏襲)+py/routes/lab_routes.pyを新設し、GET/POST /api/wfm/lab/plans*をpy/wfm.pyに登録。プランはuser/default/Workflow-Studio/lab_plan/<name>.jsonとして保存し、結果画像がある場合は同stemの<name>.png(最大9枚・3列のコンタクトシート、Canvasで生成しbase64で送信)をworkflow_service.pyのchange_thumbnailと同様のサムネイル対のパターンで自動保存する。

フロントエンド: static/js/lab-tab.js(新規、feeder-tab.jsと同じ「GenerateUIサブタブモジュール」パターン)をgenerate-tab.jsからinitLabTab()として呼び出し、サブタブ切替時に共通右ペイン(Generate/Seed/Batch状況パネル)をdisplay:noneにする分岐を追加。Setting/Resultsの2画面構成で、Setting画面は「画像ドロップ+プラン読込ドロップゾーン(左)/4列キーフレーム(中央)/Run・Batch数・Note・Plan Save(右)」、Results画面は「元画像(左)/生成結果グリッド最大9枚(中央)」。

実装後にユーザーからの追加要望で反映した変更点:

  • キーフレームセルの表示を、2回目以降は「値/Applied: N」の2行表示に変更(1回目のみ従来の1行表示のまま)
  • 生成した各画像をEagle自動保存に対応(設定タブのeagleAutoSaveを尊重)。getEagleSettings/saveToEagleは元々generate-tab.jsにのみ定義されていたが、lab-tab.jsからの循環import(generate-tab.js→lab-tab.js→generate-tab.js)を避けるためstatic/js/util.jsへ移設し、両ファイルからimportする形に整理
  • Plan Saveの下に「Save As」ボタンを追加。Plan Saveは読込済みプランがあれば無言で上書き、Save Asはプラン読込中でも常に新しい名前をプロンプトで確認する
  • 「Auto」チェックボックス(実行完了時の自動保存)を削除 — インデックス画像の自動生成があるため不要と判断
  • 「Use generated image for next」チェックボックスを追加。ONの場合、2回目以降は直前の反復で生成された1枚目の画像をI2I入力として使う連鎖生成に対応。再アップロードは行わず、ComfyUIのLoadImageが解釈できる"name [type]"(folder_paths.get_annotated_filepathが対応する記法。サブフォルダがあれば"subfolder/name [type]")形式の参照をそのままimageウィジェットに設定する方式を採用
  • インデックス画像の最大枚数を10→9、グリッド列数を5→3(3×3相当)に変更
  • Plan Loadを、独自のサムネイル一覧モーダルから、IMAGEドロップゾーンと同じ見た目のドラッグ&ドロップ+クリック選択に変更。.jsonをドロップした場合はブラウザ内で直接読み込み(サーバー不要)、.png(インデックス画像)をドロップした場合は同じstemの.jsonをサーバーから取得する。ブラウザの仕様上Web page側からファイル選択ダイアログの初期フォルダを指定することはできないため、初回のみユーザーがlab_planフォルダまで手動で移動する必要がある旨をヘルプに明記

ヘルプ更新: 既存の3点セット規約(index.htmlのヘルプページ+i18n.jsの日英中3言語+app.jsのapplyI18nToHtml内helpIdMap)に沿って、サイドバーに「Lab subtab」ページを新設(Feederページと同じ構成: 概要+カード3枚 - 画像と列/実行とプランファイル/Results)。

検証: 各段階でKaptureによる実ブラウザE2E確認を実施 — サブタブ切替時の右ペイン非表示化、4列の+/-とセル編集モーダル(1回目/2回目以降の項目差)、Plan Save(無言上書き)とSave As(常にプロンプト表示)の挙動差、Plan Loadドロップゾーンのクリック/ドラッグ配線、ヘルプページの表示とスクロール、各段階でコンソールエラーが出ていないことを確認。実際のRun実行によるバッチ生成そのものはユーザー自身の環境で確認済み。

How to apply: GenerateUIサブタブを新設する際はfeeder-tab.jsのモジュールパターン(export function initXxxTab()をgenerate-tab.jsから呼び出し、moveRawJsonToTab()の特殊タブ判定に追加)を踏襲する。サブタブがGenerateUI共通の右ペインを使わない場合は、generate-tab.jsのサブタブ切替ハンドラで.wfm-gen-rightのdisplayを明示的に切り替えること(.wfm-gen-subtab-content.activeはdisplay:flex; flex-direction:rowがデフォルトなので、縦積みレイアウトが必要なサブタブは#wfm-gen-subtab-<name>.active { flex-direction: column; }のようなCSS上書きが別途必要になる点に注意)。複数モジュールから使う汎用関数(Eagle保存など)は、モジュール間の循環importを避けるためutil.jsのような共有モジュールに置く。ComfyUIの生成済み画像を再度I2I入力として使いたい場合は、再アップロードせずとも"filename [type]"のannotated-filepath記法で直接参照できる。


v0.3.86

バグ修正: 画像を一括生成(I2I)等の連続実行で "GalleryMetadataStore: save error: dictionary changed size during iteration" が発生

comfyui-comic-creator側で「画像を一括生成(I2I)」を実行すると、ログに[ERROR] GalleryMetadataStore: save error: dictionary changed size during iterationが出る不具合をユーザー報告により調査。

原因: ギャラリー機能のメタデータ永続化クラスGalleryMetadataStore(py/services/gallery_metadata.py)は、self._data["images"]という単一の辞書を、フォルダを定期的に再スキャンするバックグラウンドスレッド(gallery_service.pyの_bg_index_folder)と、リクエストを処理するメインスレッドの双方から、ロックなしで並行に読み書きしていた。_save_to_disk()のjson.dump(self._data, ...)は内部でself._data["images"]を反復してシリアライズするが、その最中に別スレッドがキーを追加/削除すると、PythonのRuntimeError: dictionary changed size during iterationが発生する。画像を一括生成(I2I)はコマごとに生成結果のメタデータ保存リクエストを短時間に連発するため、バックグラウンドインデックススレッドとの競合が起きやすいタイミングだった。同様の反復(.items()/.values()/for key in ...)はrename_group/delete_group/list_images_in_group/cleanup_stale_images等、他の複数メソッドにも存在し、いずれも無防備だった。

修正: GalleryMetadataStoreにthreading.RLockを追加し、_dataを読み書きする全メソッド(save/get/delete/rename_path/cleanup_stale_images/グループ管理系すべて)をwith self._lock:で保護した。ensure_group()が内部でcreate_group()を呼ぶ再入呼び出しがあるため、通常のLockではなくRLockを採用。

How to apply: バックグラウンドスレッド(_bg_index_folder等)とリクエストハンドラの双方から触られる共有可変状態(辞書・リスト)を持つクラスを新設・変更する際は、読み取り専用に見えるメソッドも含めて必ずロックで保護すること。「dictionary changed size during iteration」はこの種の無防備な共有辞書の典型的な症状であり、例外を握りつぶすログ出力(except Exception as e: logger.error(...))の裏に隠れて原因特定が遅れやすい。


v0.3.85

機能拡張: Generate UI連携ブリッジ(T2I)にデフォルトワークフロー・Negativeプロンプト対応を追加

Comic Creator側のスクリプトタブ「画像を一括生成」(T2I)が、既存のI2I/Inpaint連携(_wfmReceiveI2IRunRequest/_wfmReceiveInpaintRequest)と同じ「デフォルトワークフローを実行前に自動読み込み」機能を持てるよう、_wfmReceiveGenerateRequest(gallery-tab.js)を拡張した。

  • 引数にnegative・workflowData・workflowFilenameを追加。workflowDataが渡された場合は既存2関数と同じパターン(comfyUI.connected確認→未接続ならcheckConnection()→モデルリスト未取得ならloadModelLists()→loadWorkflowIntoEditor())で実行前にワークフローを読み込む。渡されない場合は従来通りGenerate UIに現在ロード中のワークフローをそのまま使う。
  • negativeが渡された場合はcomfyEditor.setPromptText("negative", negative, { workflow, analysis })で反映(ai-tab.jsのInpaint連携と同じ呼び出しパターン)。

経緯: ユーザーから「Workflow Studioの生成UIタブにernie_t2i.jsonを読み込んで実行したが、別のワークフローが実行されたようだ」との報告を受け調査。原因はコードのバグではなく、ユーザーが手動でワークフローをロードしたのがComic Creator画面内に埋め込まれたWorkflow Studio(wfmgallery-iframe)ではなく、ブラウザの別タブとして独立に開いた/wfmだったこと(同じURLでも別ウィンドウ=別JS実行コンテキストのためcomfyUI.currentWorkflow等の状態が共有されない)と判明。この調査を踏まえ、T2I版にもI2I版同様のデフォルトワークフロー指定機能を持たせることで、Comic Creator画面から明示的にワークフローを固定できるようにした(対応する連携コードはComic Creator側14-integrations.js/26-auto-comic-bridge.js、詳細はcomfyui-comic-creator側のDEVLOG参照)。

Kaptureで実機E2E確認済み: Comic Creator画面内のWorkflow Studioタブ経由でT2I設定にernie_t2i.jsonを指定し、スクリプトタブ「画像を一括生成」からRunを実行。ComfyUIサーバーの/queue監視・コンソールログで、ernie_t2i.jsonのモデル(Ernie-Image-Turbo)による生成が実際に行われ画像がEagleへ保存されたことを確認(2コマとも成功、レイアウトタブへ自動挿入)。新規コード起因のコンソールエラーなし。

v0.3.84

バグ修正: ernie_t2i.jsonをGenerateUIタブで実行すると "Prompt outputs failed validation" エラー

v0.3.83でプロンプト表示は修正できたが、ユーザーが実際にGenerateを実行するとRequired input is missing: sourceで失敗する不具合が新たに見つかった。ComfyUIサーバーへ直接/promptをPOSTして検証エラーの詳細を取得したところ、原因はPreviewAny("Preview as Text"、テキストデバッグ表示用)ノードの必須入力source(型*のワイルドカード、ウィジェット表現を持たない)が空になっていたこと。

Ernie Imageのサブグラフでは、外側の折りたたみノードが持つwidth/heightウィジェット値が、内部で2方向に分岐していた: 一方はEmptyFlux2LatentImage.width(ウィジェット値として注入・解決済み)、もう一方はPreviewAny.source(幅の値をプロンプトテンプレート文字列 {width} に埋め込むために使われる中継ノード)。既存の_flattenSubgraphsの境界値注入ロジックは「1つの境界ポート→1つの内部ターゲット」を前提にしており、inputPortTargetsが複数ターゲットのうち最後の1つで上書きされていたため、fan-out先の一方(多くの場合ウィジェットではない方)が完全に未接続のまま残っていた。

  • inputPortTargetsをポートごとに配列で保持するよう変更し、全fan-outターゲットを追跡できるようにした
  • ウィジェット注入ループをポート単位で回すよう再構成: ウィジェット型ターゲットには従来通りwidgets_valuesへ注入しつつ、非ウィジェット型(PreviewAny.sourceのようなワイルドカード必須入力)には_wfmLiteralInputsという新しい経路でリテラル値をそのままinputsに注入するようにした(convertUiToApi側で_wfmLiteralInputsをマージ)
  • 外側ノードのwidgets_valuesが空(=ユーザーが未設定のテンプレートそのまま)の場合でも、fan-out先の別ターゲット(例: EmptyFlux2LatentImage)が保持する内部デフォルト値を読み取ってフォールバックとして使うようにし、image_ernie_image.json(未設定テンプレート)でも同様に動作するようにした

検証: /object_infoを提供する実際のComfyUIサーバーに対しNode.jsから直接/promptをPOSTして修正前後の検証結果を比較(修正前: node_errorsにRequired input is missing: source、修正後: node_errors: {})。さらに実ブラウザのGenerateUIタブからernie_t2i.jsonを読み込みGenerateボタンを実行し、実際に画像が生成され「Done (1 image)」になることを確認。

How to apply: サブグラフの境界ウィジェット値は1つのポートから複数の内部ノードへ分岐しうる(今回のような「実際に使う値」と「表示・テンプレート埋め込み用の値」の二重利用パターン)。_flattenSubgraphsの境界値解決ロジックを触る際は、常に「1ポート=1ターゲット」ではなく「1ポート=Nターゲット」であることを前提にすること。またウィジェット表現を持たない必須入力(型*など)はwidgets_valuesに書けないため、_wfmLiteralInputsのような別経路でリテラル値を注入する。

v0.3.83

追加機能: Ernie Image(ernie-image / ernie-image-turbo)ワークフロー対応 + プロンプトがリンク経由のノードから供給されるパターンの修正

Flux2 Klein(Flux2-Klein_00025.json)とErnie Image系2ワークフロー(image_ernie_image.json / ernie_t2i.json)で、GenerateUIタブ・Metadataタブ・サイドパネルIタブのいずれもプロンプトが表示されない不具合を修正した。

いずれも「プロンプトの実体がCLIPTextEncode自身のウィジェット値ではなく、リンク経由で外部の別ノードから供給される」という、v0.3.81/v0.3.82で対応した構成とは異なる新しいパターンが原因だった。

  • Flux2 Klein: CLIPTextEncode.textがPrimitiveStringMultilineノードへのリンク参照になっている。GenerateUIタブ側(comfyui-workflow.js)は既存のrole伝播ロジックで対応済みだったが、Metadataタブ側(metadata-tab.jsのresolveLinkedText()、API形式のリンク解決用)がtext/text_g/prompt等のキーしか見ておらず、PrimitiveStringMultilineが使うvalueキーを見ていなかったため未検出だった。
  • Ernie Image: プロンプト強化トグル(prompt_enhancement)用にComfySwitchNode(if/else switch)を使い、生プロンプトを保持するPrimitiveStringMultilineと、LLMで書き換えるTextGenerateノードのどちらかへ実行時に切り替える構成。TextGenerateの出力は実行時まで内容が決まらず静的解析では取得不能なため、3タブともComfySwitchNodeの両分岐(on_false/on_true)を辿った上で、リテラルな文字列へ解決できる方(=ユーザーが入力した元のプロンプト)を採用するようにした。

comfyui-workflow.js(GenerateUIタブのanalyzeWorkflow()のPass 1bロール伝播にComfySwitchNode対応を追加)、metadata-tab.js(resolveLinkedText()とextractPromptsFromNodeSet()を再帰的な解決ロジックに書き直し、ComfySwitchNodeとvalueキーに対応)、node_sets_menu.js(サイドパネルIタブのローカル複製、同様の修正)の3ファイルを修正。

検証: 実際に稼働中のComfyUIサーバー(/object_info)を使い、convertUiToApi()によるサブグラフ展開を経た上でGenerateUIタブ・Metadataタブ・サイドパネルIタブの3箇所すべてで実ブラウザ上からプロンプトが正しく表示されることを確認。

あわせてヘルプを更新: GenerateUIタブ(helpGen2)・Metadataタブ(helpMetadata5)のヘルプ本文、およびMetadataタブ内の対応フォーマット補足(metaFormatTodo)に記載している対応サブグラフワークフローの例にErnie Imageを追加(EN/JA/ZH)。

How to apply: v0.3.82で整理した「GenerateUIタブとMetadataタブ(およびIタブの複製)は別実装で自動同期しない」という注意点に加え、プロンプトがCLIPTextEncode自身の値ではなくリンク経由で別ノードから来るパターン(PrimitiveStringMultiline直結、ComfySwitchNode等の中継ノード経由)は今後も現れうる。中継ノードがLLM呼び出しなど実行時にしか値が決まらないノードへ分岐している場合は、無理に実際の出力値を再現しようとせず、静的に解決できる(=ユーザーが直接入力した)分岐を優先して表示する方針でよい。

v0.3.82

バグ修正: MetadataタブとサイドパネルIタブでLongCat/Boogu/HiDream E1/FireRed等のプロンプトが表示されない不具合

v0.3.81でFlux.2 Klein/LongCat/Boogu/HiDream E1/FireRedをGenerateUIタブに対応させた後、ユーザーがAI TOOL Chatで実際に5系統すべてを生成しMetadataタブで確認したところ、qwen_image_edit2509/2511・LongCat・Boogu・FireRedの画像でプロンプトが一切表示されない不具合が見つかった。同じ画像をGenerateUIタブへ読み込むと正しく表示されたことから、GenerateUIタブの解析(comfyui-workflow.jsのanalyzeWorkflow())とは別に実装されているMetadataタブ独自のプロンプト抽出ロジック(extractPromptsAPI()/extractPromptsFromNodeSet())が、v0.3.81で対応した新しいノードパターンに追従していなかったことが原因と判明した。

  • promptキーの欠落: PNG画像はworkflowチャンク(UI形式)よりpromptチャンク(ComfyUIが実行時に生成した、既にAPI形式のペイロードそのもの)が優先的に読まれるため、常にextractPromptsAPI()のtextMap構築ロジックが使われる。ここがtext/text_gキーのみを見ており、TextEncodeQwenImageEdit(Plus含む)やTextEncodeBooguEditが使うpromptキーを見ていなかった(LongCat/Boogu/FireRed、および同種のQwen-Image-Edit系公式テンプレートすべてに影響)。
  • Guider経由のrole判定漏れ: SamplerCustomAdvancedはpositive/negativeを直接持たず、guider入力の先にあるCFGGuider/DualCFGGuider/BasicGuiderノードが持つため、サンプラーノードのinputsを直接見るだけの既存ロジックでは見つけられなかった(Flux.2 Klein/HiDream E1)。GenerateUIタブ側で先に実装した「guiderノードを辿る」対応と同じロジックをMetadataタブ側にも追加。
  • TextEncodeBooguEditの1ノード2ロール対応: prompt/negative_promptを1ノードに両方持つ構造はTextEncodeMageFlowEditと同じパターンのため、既存の専用分岐にBooguも合流させた。
  • InstructPixToPixConditioningの追加ホップ解決: HiDream E1のこのノードはpositive/negativeを中継するだけで自身はテキストを持たないため、guiderのcond1/negativeから一段先まで辿るようにした。

extractPromptsAPI()(API形式JSON・PNGのpromptチャンク用)とextractPromptsFromNodeSet()(UI形式・サブグラフ内共用)の両方を修正。さらにnode_sets_menu.js(ComfyUIサイドパネルのIタブ、web/comfyui/はESモジュールをimportできない制約からcomfyui-workflow.js/metadata-tab.js相当のロジックを_wfm接頭辞でローカル複製している)にも同じ修正を適用した。

あわせてヘルプを更新: GenerateUIタブ(helpGen2)・Metadataタブ(helpMetadata5)のヘルプ本文、およびMetadataタブ内の対応フォーマット補足(metaFormatTodo)に記載している対応サブグラフワークフローの例に、LongCat/Boogu/HiDream E1/FireRedを追加(EN/JA/ZH)。あわせてmetaFormatTodoにMage-Flowが記載漏れだった既存の抜けも修正した。

検証: ユーザーがAI TOOL Chatで実際に5系統のワークフローを生成し、Metadataタブでプロンプトが正しく表示されることを確認。サイドパネルIタブでも同様に確認。

How to apply: GenerateUIタブとMetadataタブ(および複製先のIタブ)は、ワークフロー解析ロジックを別々に実装しており自動的には同期しない。新しいノードパターン(プロンプトの格納キー名、guider経由のconditioning構造など)へ対応する際は、comfyui-workflow.jsのanalyzeWorkflow()だけでなくmetadata-tab.jsのextractPromptsAPI()/extractPromptsFromNodeSet()、およびnode_sets_menu.jsのローカル複製版も合わせて確認・修正すること。特にPNG画像のメタデータはprompt(API形式)チャンクがworkflow(UI形式)チャンクより優先されるため、実機検証はワークフローJSONの直接ドロップだけでなく、実際に生成したPNG画像でも行うこと(今回はまさにこの違いで問題が後から発覚した)。

v0.3.81

追加機能: GenerateUIタブでFlux.2 Klein / LongCat / Boogu / HiDream E1 / FireRedのImage Editワークフローに対応

「Flux2.Klein4b_comic.json / Flux2.Klein9b_comic.jsonをGenerateUIタブで動作させたい」という依頼を皮切りに、続けてlongcat_comic / boogu_image_comic / hidream_comic / Firered_comicの4ワークフローにも対応してほしいという依頼が続いた。いずれも公式・サードパーティのImage Edit系ワークフローで、KSamplerを使わない「Advanced Sampling」構成(RandomNoise+何らかのGuiderノード+KSamplerSelect+*Scheduler+SamplerCustom系ノードに分散した seed/steps/cfg/sampler/scheduler)や、独自のプロンプト/サンプラーノードを使っており、既存のanalyzeWorkflow()(comfyui-workflow.js)が前提とするKSampler中心の設計では検出できず、GenerateUIタブのSettings/Latentパネルが「ノードが見つかりません」表示のまま操作不能だった。

  • Flux.2 Klein(4b/9b): CFGGuider+RandomNoise+SamplerCustomAdvanced+KSamplerSelect+Flux2Schedulerという構成。SamplerCustomAdvancedを起点にguider/noise/sampler/schedulerの4ノードをたどり、Settingsパネルに複合サンプラーとして集約表示・編集できるようにした。EmptyFlux2LatentImageもLatentパネルに対応させ、GetImageSizeへリンクされたwidth/heightは編集不可の代わりに空欄+説明表示にして誤上書きを防止。CFGGuider/BasicGuiderをpositive/negative判定の起点にも追加。
  • LongCat: 標準KSamplerだが独自プロンプトノードTextEncodeQwenImageEdit(Plus無し版)を使用。FluxGuidance/FluxKontextMultiReferenceLatentMethodをrole伝播のパススルーノード集合(COND_PASSTHROUGH)に追加。
  • Boogu: SamplerCustom(SamplerCustomAdvancedとは入力構成が別物で、CFGGuider/RandomNoiseを介さず自身がpositive/negative/cfg/noise_seedを直接持つ)を誤って同一視していた既存実装を分離し、それぞれ専用ロジックで検出。独自プロンプトノードTextEncodeBooguEdit(1ノードにprompt/negative_promptを両方保持)にも対応。
  • HiDream E1: cond1/cond2/negativeという3つのconditioning入力を持つ特殊ガイダンスノードDualCFGGuider(cfgの代わりにcfg_conds/cfg_cond2_negativeを持つ)と、画像編集用の中継ノードInstructPixToPixConditioning(positive/negative別々の入力・出力を持つため、通常のCOND_PASSTHROUGH一括処理では役割を取り違える)のrole伝播に個別対応。
  • FireRed: KSamplerのseed/steps/cfgがPrimitiveノード経由でリンクされる構成(値が数値ではなくリンク参照配列になる)で、既存の直接値前提のコードがリンク配列をそのままSettingsパネルに表示しようとしていた不具合を修正(数値でない場合はundefined扱いにし、リンク接続時は空欄+「linked」表示、Apply時は未入力なら書き込みをスキップ)。

実機検証で発見・修正した既存バグ(今回の対応範囲拡大で初めて表面化)

  • サブグラフ内部で完結するリンク(GetImageSize→EmptyFlux2LatentImageなど)の扱いが誤っており、widgets_values変換でbatch_sizeが1024になったりCLIPTextEncodeのtextが消えたりする不具合。
  • 同じロール(positive/negative)のプロンプトノードが複数存在する場合、ドロップダウンの選択とテキストエリアの表示内容が食い違う不具合(全<option>に無条件でselectedを付けていたため)。
  • ミュート/バイパスしたサブグラフの内部ノードにmode情報が伝播せず、無効なパイプラインのノードがModelタブ等のデフォルト表示に紛れ込む不具合。
  • サブグラフ展開処理(_flattenSubgraphs)のwidgets_valuesノーマライズが「配列長とwidget数が完全一致する場合のみ」動作する設計だったため、seed直後にcontrol_after_generate拡張値('fixed'等)が挟まるノード(KSampler等)では条件が不成立となり、リンクされたwidget(steps/cfg等)のエントリが除去されないまま後続処理に渡り、cfg/denoiseなど後続フィールドの値が1つずつズレて上書きされる不具合(Firered_comicの実機検証で発覚)。_simulateWidgetValues/_stripLegacyLinkedWidgetValues/_findInjectedWidgetIndexという共通ロジックに全面書き直し、legacy-full/modern形式の自動判定とcontrol_after_generate込みの正確な位置計算・注入に統一した。
  • Rerouteノード(配線整理用のUI専用パススルーノードで、ComfyUIバックエンドにノードクラスが存在しない)がそのままAPIペイロードに含まれ、missing_node_typeの400エラーで生成が失敗する不具合(hidream_comicを実際にGenerateして初めて発覚)。既存のBypass(mode:4)ノード解決ロジック(_resolveBypassSource)をReroute対応に拡張し、常に配線を透過解決するようにした。Rerouteの入力型は"*"(汎用型)のまま保存されることが多く、Bypass用の型一致検索では見つからないため、Reroute(常に1入力1出力)は型チェックを省略する専用分岐を追加。

検証: 実際にComfyUI上で6ワークフロー(Flux2.Klein4b_comic/Flux2.Klein9b_comic/longcat_comic/boogu_image_comic/hidream_comic/Firered_comic)をGenerateUIタブへ読み込み、Prompt/Settings/Model/Imageタブの表示とRaw JSON変換結果を1つずつ突き合わせて確認。hidream_comicは実際にGenerateを実行し、画像生成成功(Eagle保存ログ)まで確認。

How to apply: KSamplerを使わない「Advanced Sampling」構成(CFGGuider/DualCFGGuider/BasicGuider + RandomNoise + SamplerCustom(Advanced) + KSamplerSelect + *Scheduler)に新しく遭遇したら、SamplerCustomAdvanced/SamplerCustom自体を起点にguider/scheduler/samplerノードをたどるのが基本形だが、guiderノードのconditioning入力名(positive/negative vs cond1/cond2/negative等)やSamplerCustom系のwidget有無はノードごとに異なるため、実際のノード定義を確認してから個別対応を追加すること。サブグラフのwidgets_values関連の不具合は、_simulateWidgetValues系の共通ロジックが「legacy-full/modern形式の自動判定」「control_after_generate込みの正確な位置計算」まで一括して担うようになったため、今後同種の問題が出たら個別対応ではなくこの共通関数を確認・拡張するのが筋。RerouteのようなComfyコア標準の「UI専用でバックエンドに実体がないノード」は他にも存在しうるため、同様のエラー(missing_node_type)に遭遇したら_resolveBypassSourceの仕組みを拡張する方針で対応する。

v0.3.80

追加機能: Comic Creator「半自動マンガ作成」向けブリッジ(LLMプロンプト下書き・バッチ画像生成)

別カスタムノードcomfyui-comic-creatorが実装中の「半自動マンガ作成」機能(スクリプトのコマ割り・セリフからコマ単位でフキダシ・画像を自動生成するPhase 3)から、iframe越しに呼び出される新規ブリッジ2つをgallery-tab.jsに追加。既存の_wfmReceiveImageForI2I/_wfmReceiveInpaintRequest/_wfmReceiveI2IRunRequestと同じ「window._wfmReceiveXxxをwindowへ直接代入し、Comic Creator側からiframe越しに呼ぶ」配置パターンを踏襲。

  • _wfmReceiveLLMPromptRequest(context): シーン・登場人物・セリフ・現在のプロンプト下書きをコンテキストとして受け取り、AIタブの翻訳機能が既に持つLLM接続ロジック(ai-tab.jsのcallLLM/loadAiSettings/isValidBackendUrl、Ollama/LM Studio対応、設定タブの接続設定をそのまま再利用)で画像生成プロンプト案を1本生成して返す。これらの関数は元々ai-tab.js内に閉じていたため、exportに追加してモジュール間で再利用できるようにした。
  • _wfmReceiveGenerateRequest(prompt, width, height): 現在Generate UIタブにロード中のワークフローでtxt2img生成を実行し、結果画像のURLを返す。専用ワークフロー選択(I2I/Inpaint連携にあるworkflowData引数)は今回は非対応。幅・高さの指定は、設定タブの「Latent Image」パネルがanalysis.latent_nodes[0](EmptyLatentImage/EmptySD3LatentImageノードを検出)を使ってworkflow[id].inputs.width/heightを直接書き換えているのと同じ手法をそのまま踏襲。結果URLの取得は_runInpaintWithImages等と同じ「window._wfmGenerateTab.generate()実行後に#wfm-gen-result-imgのsrcを読む」パターンを採用。

検証: Comic Creator側からKaptureで実機E2E確認済み(Ollama接続でのLLM呼び出し、t2iワークフローでの画像生成・結果URL取得とも成功)。詳細はComic Creator側DEVLOG「半自動マンガ作成 Phase 3」の項を参照。


v0.3.79

追加機能: サイドパネル(Workflow Studio Library)IタブのMage-Flow等サブグラフワークフロー対応

v0.3.78でMetadataタブ(metadata-tab.js)に行ったMage-Flowサブグラフ対応と同様の対応を、「同様にサイドパネル(ComfyUIキャンバス上のIタブ)も更新したい」という依頼を受けてnode_sets_menu.jsにも適用。調査したところ、node_sets_menu.jsのIタブはmetadata-tab.jsとほぼ同一の抽出関数群(_extractCheckpoints/_extractPrompts/_collectAllNodes等)を独立に重複実装しており、web/comfyui/はstatic/js/と配信URLが異なるためESモジュールをimportできない(既存のAI backend URL解決等と同じ制約)。そのためMetadataタブ側の対応がそのまま反映されない状態だった。

  • comfyui-workflow.jsのconvertUiToApi()相当ロジック一式(_flattenSubgraphsによる外側ウィジェット値の内部ノードへの注入、_getWidgetInputNames/_getWidgetInputTypes/_getDynamicComboSubNamesによるobject_infoベースのwidgetマッピング、DynamicCombo型対応を含む、約450行)を_wfm接頭辞を付けてnode_sets_menu.jsにローカル複製。GenerateUIタブと異なりIタブは読み取り専用表示のため、Checkpoint代替やBypass/Muteノードの記録機能(_lastCheckpointSubstitutions等)は複製せず省略。
  • fromWorkflowをasync化し、サブグラフ(definitions.subgraphs)を検出した場合は複製版_wfmConvertUiToApi()でAPI形式に正規化してから抽出するよう変更(変換失敗時は元のUI形式のまま抽出する既存ロジックへフォールバック)。
  • _extractPromptsFromNodeSet/_extractPromptsAPIにTextEncodeMageFlowEdit(1ノードでprompt/negative_prompt両方を直接持つ複合ノード)専用の分岐を追加。既存のtextMap(1ノード1テキストしか保持できない)構造では両ロールを区別できず、同じテキストが両方に登録されてしまうバグの修正を複製。

検証: node_sets_menu.jsは../../scripts/app.jsをトップレベルでimportしモジュール自体をNode.jsで直接実行できないため、複製したロジックブロック(_sanitizeJSON〜_extractAllMetadataの自己完結範囲)をsedで抜き出しNode.jsでテスト。mageflow_1.jsonでmetadata-tab.js側と完全に一致する結果(モデル名・プロンプトとも外側の実際の値)を確認。既存の他サブグラフワークフロー(Flux2/Qwen/Ernie/Krea2等)8件でもエラーなく動作しリグレッションがないことを確認。

How to apply: web/comfyui/はimport不可のためローカル複製パターンを踏襲する場合、複製元のロジックが今後も更新される可能性があるものは、複製箇所に「変更する場合は元ファイル側も同期すること」という注意コメントを残す。複製したコードの検証は、importが困難なファイルでもsed等で自己完結したコードブロックを抜き出しNode.jsで実行すれば、ブラウザなしでも複製元と出力を突き合わせられる。

追加機能: AI TOOL Chatにスキル機能を追加・SVG生成品質改善・ヘルプのカード形式再設計

「Chatで生成されるSVGの質を上げたい」という依頼から調査を開始したところ、Chat機能(ai-tab.js)にはsystem promptの仕組みが一切なく、SVG生成は「モデルが会話の流れで偶然SVGコードを書いた場合に正規表現で抽出するだけ」という後付け設計だったことが判明した(_extractSvgCode/_appendSvgPreview)。これを踏まえ、単発のSVG改善ではなく汎用的な「AIスキル」機構(選択式system promptライブラリ)を新設し、SVG生成品質改善をその第一弾スキルとして実装する方針に変更した。

  • AIスキル機構: .mdファイル(frontmatterでname/descriptionを定義)をuser/default/Workflow-Studio/ai_skills/に保存し、Chatペイン上部のドロップダウンで選択すると、その内容がsystem promptとして会話履歴の先頭に(表示上のチャット履歴には含めず)注入される。バックエンドは既存のwildcard_service.py/wildcard_routes.pyパターンを踏襲したskill_service.py/skill_routes.py(GET/POST /api/wfm/skills系)を新設。管理UI(一覧・追加・編集・削除)はワイルドカードエディタのUIパターンを再利用してChatペインに内蔵。
  • SVG Icon Generatorスキル: アイコンライブラリ(Feather/Heroicons等)の設計慣習(viewBox+currentColor+stroke-width)とSVGのXSS対策(<script>/イベント属性/<foreignObject>禁止)を組み込んだ初期スキルとして同梱。実機検証(gemma3系モデル等)でカモメ/ペンギン等の生成結果が「落書き」状態だったため追加調査したところ、LLMのSVG生成研究(Chat2SVG等)で言われる「Shape Decomposition」(対象を部位に分解してから図形を組み立てる)を明示的に指示していないことが原因と判明。「Composition process」セクション(部位分解→形状割当→組み立ての手順を明示指示)を追加し、有機的な輪郭には短い<path>曲線を許可するよう緩和した結果、体感で大幅に改善(ユーザー確認済み)。
  • Skill Creator(対話式スキル作成): 「作りたいスキルを対話形式で聞き出し、完成品を返す」ためのメタスキルを第二弾として同梱。1問ずつ質問し、十分な情報が集まったら```skill```フェンスでfrontmatter付きの完成スキルを出力する設計。フロント側に```skill```ブロック検出+「スキルとして保存」ボタン(クリックでスキル管理パネルを開きfrontmatterのnameから自動生成したファイル名とともにエディタへプリフィル)を追加し、実際にqwen3:4bで対話→保存までのフローを検証済み。SVGプレビュー機能と同じ理由でSPA(ai-tab.js)側のみの実装とし、サイドパネル(node_sets_menu.js)はスキル選択・system prompt注入のみ対応。
  • ヘルプのカード形式再設計: 別件で導入中のcomfyui-comic-creatorのヘルプUIが「セクションをカード形式で表示(workflow studio のヘルプ形式)」というコメント付きで実装されており、調査したところWFS自身のヘルプUI(サイドバーナビ+検索)を参考に作られたものだったが、1トピック=1カードという構造だけはWFS側になかったことが判明。AI TOOLページを皮切りに全16ヘルプページを「ページ見出し+タブへのジャンプボタン」+「トピックごとの複数カード(見出し+<ul>)」の構造に再構成した。個々の項目テキスト・id・i18n翻訳キーは変更せず、既存の<li>要素をどのカードの<ul>に含めるかという入れ物の再編成のみで実施(新規に追加したのはカード見出し用のキーのみ、約50個×3言語)。再構成の過程で、AI TOOLページのhelpAi5(Settingsペインの説明)がhelpAi6に完全に包含される重複項目だったこと、Taggerページと一部のTrouble項目がindex.html上は空の<li>(実テキストはi18n.js側にのみ存在)だったことも発見し、あわせて修正・記入した。

検証: 新規追加した/api/wfm/skills系エンドポイントをcurlで確認。ブラウザでSVGスキル・Skill Creatorスキルの選択→送信→system prompt注入をfetchフックで実データ検証、手書きの「Bird」few-shot例は実際にレンダリングして認識可能な形状であることを確認。ヘルプの再設計は全16ページを実ブラウザでクリックし、カード表示・ジャンプボタンの遷移・翻訳適用を確認。

How to apply: 新しい「選択式system promptライブラリ」的な機能を追加する際は、skill_service.pyのfrontmatterパース(_FRONTMATTER_RE)とファイル一覧/読み書きAPIパターンがそのまま再利用できる。ヘルプページを新設・改修する際は、1ページ=1個の巨大な<ul>ではなく、トピックごとに.wfm-help-cardで分割し、新規カード見出しはid+i18n 3言語キー+app.jsのhelpIdMapの3点セットで追加すること(project_v0336_conventionsのヘルプ更新規約を参照)。index.html上のテキストはapplyI18nToHtml()がi18n.jsの内容で必ず上書きするため、実際に表示される文言はi18n.js側が正であり、index.html側は空にしたり古いままにしたりせず同期を保つこと。

v0.3.78

追加機能: Eagle自動保存のSVG対応

「画像管理ソフトEagleとの連携機能(Eagleへ自動保存)をSVGに対応させたい」という依頼を受けて実装。既存のEagle自動保存(generate-tab.jsのEagle送信ループ)はhistory.outputsのimagesキーのみを走査しており、SVG出力ノードには2系統あってそれぞれ挙動が異なることが判明した。

  • 標準SaveSVGNode(ComfyUI本体): 通常のimagesキー({filename, subfolder, type})で結果を返すため既存の収集ロジックに拾われるが、ComfyUIの/viewエンドポイントはSVGをXSS対策としてContent-Type: application/octet-stream + Content-Disposition: attachmentで強制返却するため、<img>表示にもEagleのaddFromURLにも正しく使えない。comfyui-client.jsのgetImageBlob()で拡張子.svgを検出しblob.slice(0, blob.size, "image/svg+xml")でMIMEタイプを付け替えることで<img>表示を解決し、Eagle送信はeagle_routes.py側でローカルファイルパスを解決してaddFromPathを使うルートに切り替えた。
  • comfyui-tosvgのSave SVG Stringノード: imagesキーではなく独自のsaved_svg(ファイル名)/path(絶対パス)キーで返す。さらに、ComfyUIの実行エンジン(execution.pyのget_output_from_returns)はui辞書の各値をイテラブルとして連結する仕様のため、このノードが単一文字列を返すと1文字ずつ分解された配列になる("a.svg" → ["a",".","s","v","g"])。comfyui-client.js側でこの文字配列を検出しjoin("")で復元する対応を追加。絶対パスが既知なため、Eagle送信は_resolve_absolute_svg_path()(output/temp/inputディレクトリ配下限定の検証つき)経由で直接addFromPathする。
  • プレビュー用一時画像の除外: SVG String PreviewやPreviewImageなどtype: "temp"の画像は、Eagle自動保存からもGenerateUI結果表示からも除外(outputImages = images.filter(img => img.type !== "temp")に統一)。SVG出力があるワークフローでは、GenerateUIの結果プレビューを実際に保存されたSVGファイルに切り替え、/viewが使えないためGalleryタブの配信エンドポイント(/wfm/gallery/image/serve?path=...、絶対パス指定・正しいContent-Type)を再利用した。

セキュリティ: _resolve_absolute_svg_path()は任意の絶対パスをそのまま信頼せず、ComfyUIのoutput/temp/inputディレクトリ配下の実在するファイルのみ許可する(外部からこのAPIを叩かれてローカル任意ファイルを送信させられるリスクの回避)。Windowsのパス比較は大文字小文字を区別しないためos.path.normcaseで正規化してから比較。

検証: Node.jsスクリプトでcomfyui-client.js/eagle_routes.pyのロジックを直接テストし、両ノードタイプでファイルが正しい形式でEagleへ送信されることを確認。ユーザーが実際にComfyUI上でsvg_1.json/svg_5.json/svg_7.jsonの3種類のワークフローを実行し、Eagle保存・プレビュー表示それぞれで動作確認。

How to apply: project_v0342_gallery_feederのSVG関連対応と合わせ、SVGはComfyUI本体がXSS対策で/viewから生配信しない前提で設計する必要がある。新しいSVG出力ノードに対応する際は、まずui辞書がどのキー名で結果を返すか(images vs 独自キー)、絶対パスが取得できるか、を確認してから送信経路(addFromURL vs addFromPath)を選ぶ。

追加機能: GenerateUI/MetadataタブでComfyUIサブグラフワークフロー対応(Mage-Flow等)

「mageflow_1.jsonをGenerateUIタブ、メタデータタブに対応させたい」という依頼を受けて調査したところ、comfyui-workflow.jsのdetectFormat()がdefinitions.subgraphsを持つワークフロー全般を一律「App形式」と判定し、GenerateUIタブが読み込みを拒否する設計になっていたことが判明した(generate-tab.jsのloadWorkflowIntoEditor()がformat === "app"で即エラー)。サブグラフ展開自体は_flattenSubgraphs()に既に実装済みでconvertUiToApi()から呼ばれていたが、GenerateUIの通常フローには到達しない状態だった。

  • detectFormat()の判定基準を訂正: 「サブグラフを含む」ことと「真のApp形式(ComfyUIの簡略化Webアプリ風UI)」は別軸であるべきところ、後者の判定条件に前者が混入していた。extra.linearMode === trueまたは.app.json拡張子の場合のみ"app"、それ以外(サブグラフを含むだけの通常ノードグラフ)は"ui"として扱うよう修正。workflow_analyzer.py(Python側、サイドパネルのAPI/APPバッジ表示に使われる)にも同じ判定基準の食い違いがあり、揃えて修正。
  • サブグラフ外側ウィジェット値の内部ノードへの注入(_flattenSubgraphs()): 最大の落とし穴だった。サブグラフの「外側ノード」(折りたたみ表示、ユーザーが実際に編集する値)のwidgets_valuesと、definitions.subgraphs[].nodes内の「内部ノード」(サブグラフ編集画面を開いた時のテンプレート初期値)のwidgets_valuesは、ComfyUIが保存時に同期しない場合があり、実データで検証すると別内容のプロンプトが入っていた。既存の_flattenSubgraphs()はリンクのリダイレクトのみ行い、ウィジェット値の注入は一切していなかったため、そのままではGenerateUIが古いテンプレートのデフォルト値で生成してしまう。外側ノードのinputs配列(widgetかつlinkなしの要素)を走査し、サブグラフ入力ポート経由で対応する内部ノードのwidgets_values配列へ値を上書きするロジックを新設。内部ノードのwidgets_values配列がリンク済み入力の分もエントリを持つ「legacy full形式」の場合があり、convertUiToApi()本体が前提とする「リンク済みwidgetにはエントリがない」形式との食い違いでインデックスがずれるバグも合わせて修正(リダイレクトされた実リンク先スロットのみをエントリから除去する形に変更)。
  • analyzeWorkflow()にTextEncodeMageFlowEdit対応: 1ノードでprompt/negative_prompt両方を直接持つ複合ノードのため、SDXLPromptStylerパターンを参考にrole固定(positive/negative)で個別登録。width/height/batch_sizeも内包しているためlatent_nodesにも登録(EmptyLatentImage相当)。
  • DynamicCombo型(COMFY_DYNAMICCOMBO_V3)対応: SaveImageAdvancedのformat入力(選択値に応じてbit_depth/input_color_space等のサブフィールドが動的に変わる新しいComfyUI V3 API機能)が未対応で、inputsから丸ごと欠落しTypeError: missing 1 required positional argument: 'format'が発生した。最初は{"format": "png", "bit_depth": ..., ...}という単一辞書にまとめて渡す実装を試みたが実機で同じエラーが再現し、ComfyUI本体のcomfy_api/latest/_io.pyのDynamicCombo._expand_schema_for_dynamicを読んだところ、バックエンドは"format"/"format.bit_depth"のようなフラットなドット区切りキーをinputs辞書に個別に期待しており、実行エンジン自身(build_nested_inputs())が実行時にネストした辞書へ自動組み立てる設計だったため、辞書化をやめてフラットキーをそのままinputsへ設定する実装に修正。
  • metadata-tab.jsのサブグラフ対応強化: 既存のcollectAllNodes()ベースの抽出(トップレベル+サブグラフ内nodesを単純結合)はモデル名・プロンプンとも「内部テンプレートの古い値」を拾ってしまう同じ問題を抱えていたため、サブグラフを検出した場合はcomfyWorkflow.convertUiToApi()を経由してAPI形式(外側ウィジェット値が注入済み)に正規化してから抽出するよう変更。extractPromptsAPI/extractPromptsFromNodeSet双方にTextEncodeMageFlowEditのpositive/negative個別抽出(1ノードにつき2つのwidgets_valuesスロット)対応を追加。

検証: Node.jsでcomfyui-workflow.jsをブラウザ実行と同一ロジックのままimportし(/object_infoはローカルComfyUIサーバーへのfetchで取得)、mageflow_1.jsonの変換結果を全項目突き合わせ。外側と内部でテキストが異なる実データを使い、正しく外側の値が採用されることを確認。既存のFlux2/Qwen/Ernie/Krea2等、サブグラフを持つ8ワークフローでgit stashによるビフォーアフター比較を実施し回帰なしを確認(変更前は全て"app"判定で一律ブロックされていたため、影響は純増)。ユーザーが実機でGenerateUI実行・Eagle送信・Metadata抽出それぞれ確認。

How to apply: ComfyUIのサブグラフは「外側の折りたたみ表示ウィジェット値」と「内部テンプレート定義のwidgets_values」が別データであり、保存時に同期される保証がないという前提を忘れないこと。サブグラフを扱うロジックを新設・修正する際は、実データ(必ずしもテンプレートそのままでない、ユーザーが値を変更済みのワークフローファイル)で外側/内部の値を突き合わせて検証する。またComfyUIの新しいV3 API機能(DynamicCombo等)に遭遇したら、まずcomfy_api/latest/_io.pyの該当実装を読んでバックエンドが実際に何を期待しているかを確認してから実装する方が、推測で実装してハマるより早い。

改善: Workflowタブ Summarize機能を構造化データベースに変更

上記のサブグラフ対応の流れで、「ワークフロータブの要約、解析ができるようにしたい」という依頼を受けた。調査の結果、既存の「AI要約」ボタン(workflow-tab.js)は生ワークフローJSON全体をJSON.stringify(wf).substring(0, 4000)で先頭4000文字に切り詰めてLLM(Ollama)へ渡すだけの実装で、MarkdownNoteの長文説明やUI座標情報が先頭を占有し、複雑なワークフロー(特にサブグラフ入り)では肝心のノード構成情報が切り捨てられて有効な要約ができない状態だった。「要約機能の修正」「解析結果表示の新規UI追加」のどちらを優先するか確認し、前者を選択。

  • _buildSummarySourceText()を新設。既存のcomfyWorkflow.detectFormat()/convertUiToApi()/analyzeWorkflow()(上記のMage-Flow対応で強化済み)を再利用し、Checkpoint/Diffusion Model/Text Encoder/VAE/LoRA/ControlNet/Hypernetwork/Positive・Negative Prompt/Sampler設定/使用ノード種別一覧をコンパクトなテキストに整形してからLLMへ渡すよう変更。変換・解析に失敗した場合は従来の生JSON切り詰め方式へ自動フォールバック。

検証: Node.jsで_buildStructuredSummaryText()相当のロジックをanalyzeWorkflow()の実出力に対して実行し、mageflow_1.json(1183文字)・qwen-image-edit_controlnet.json(694文字)など、旧実装の4000文字切り詰めより大幅にコンパクトかつ正確な出力になることを確認。サブグラフを持たない通常ワークフロー3件でも正しく動作することを確認。

How to apply: LLMへワークフロー情報を渡す用途では、生JSONダンプではなく既存の解析結果(analyzeWorkflow())を再利用してテキスト化する方が、トークン数を抑えつつ精度も上がる。生JSONはMarkdownNoteやUI座標などLLMにとってノイズになる情報を大量に含む。

バグ修正: Workflowタブ詳細パネルのP:/I:/→カウントとフォーマットバッジ

上記のMage-Flow対応の実機確認中、Workflowタブのサイドパネルで新しいノードタイプを使うワークフローの入出力カウントが「P:0 I:0 → 0」と表示される問題が見つかった。原因はworkflow_analyzer.pyのanalyze_workflow()がノードタイプ名の完全一致(ntype == "CLIPTextEncode"、ntype == "SaveImage")でカウントしており、TextEncodeMageFlowEdit(プロンプト)やSaveImageAdvanced(画像出力)のような新しいノードタイプを認識できていなかったため。

  • プロンプト検出を_PROMPT_NODE_TYPES集合(TextEncodeMageFlowEdit/TextEncodeQwenImageEditPlus/SDXLPromptStyler等)に、出力画像検出を_OUTPUT_IMAGE_NODE_TYPES集合(SaveImageAdvanced追加)にそれぞれ拡張。
  • 副次的に、この関数のフォーマット判定が上記で修正したJS側detectFormat()と食い違っていること("definitions" in workflow_dataだけで"app"と判定する古い基準のまま)を発見し、同じ基準(linearModeのみ"app")に統一。これはComfyUIキャンバスのサイドパネル(node_sets_menu.js)のAPI/APPバッジ表示に使われており、放置するとMage-Flow系ワークフローに誤って「APP」バッジが付き続ける状態だった。

検証: Pythonでanalyze_workflow()を直接呼び出し、mageflow_1.jsonでprompts: 1/outputs.images: 1/format: "ui"が正しく出ることを確認。ユーザーからの追加確認依頼を受け、真にlinearModeを持つワークフロー(wf_20260322113657.json等、サブグラフを含むケースも含む)では引き続き"app"、Mage-Flow等のサブグラフのみのワークフローは"ui"と、両者が正しく区別されることをJS/Python両実装で突き合わせ検証。既存の複数ワークフローでリグレッションがないことも確認。

改善: Send to Canvasトーストメッセージの区別

「ワークフロータブでUI形式のワークフローをキャンバスへ送るボタン押下時、『ワークフロー準備完了 タイトルをキャンバスにドラッグ』のトーストは現在ドラッグの必要がないためメッセージを変えたい」という依頼。workflow-tab.js/gallery-tab.jsの「キャンバスへ送る」処理は、window.opener経由で直接ロードされる場合(ドラッグ不要)と、localStorage+タイトルドラッグのフォールバック(ドラッグ必要)の2経路があるが、両方が同じworkflowSentToCanvas(「タイトルをドラッグ」)トーストを共有していたため、直接ロードされた場合にも実際には不要なドラッグ手順が案内されていた。

  • 新しいi18nキーworkflowSentToCanvasDirect(「ワークフローをキャンバスへ送信しました」)を英日中3言語で追加し、window.opener経由の成功時のみこちらを使うよう変更。ドラッグが実際に必要なフォールバック側は既存メッセージのまま維持。

検証: node --checkで構文チェック。ユーザーがComfyUIツールバーから開いた状態で実機確認。

v0.3.76

追加機能: Comic Creator向けI2I実行ブリッジ_wfmReceiveI2IRunRequest

Comic Creator(comfyui-comic-creator、本アプリをiframe埋め込みで連携する別アプリ)から、「マスク不要のI2I実行をその場で行い結果を受け取りたい」という依頼を受けて追加。既存のInpaint連携ブリッジ(gallery-tab.jsの_wfmReceiveInpaintRequest → image-edit-tab.jsのrunInpaintExternal/_runInpaintWithImages)と対称的な、マスク版の無いI2I専用ルートを新設した。

  • image-edit-tab.js: _runI2IWithImage(imageBlob, {positive, negative, denoise, overrideOpts, onStatus})を新規実装。comfyEditor.applyImageToSlot(file, 0)(マスクなし版アップロード)→comfyEditor.setPromptText("positive"/"negative", ...)→comfyEditor.setInpaintParams({denoise, ...overrideOpts})→window._wfmGenerateTab.generate()→#wfm-gen-result-imgのsrcを返す、という_runInpaintWithImages()と同型の流れ。setInpaintParams()はgrowMaskBy未指定時に自動でスキップする既存実装だったため、denoise単体用の新規セッターは追加せずそのまま流用できた。外部エントリポイントrunI2IExternal(imageBlob, {positive, negative, denoise})(多重実行ガード_i2iExternalRunning付き)を追加。
  • gallery-tab.js: window._wfmReceiveI2IRunRequest = async (imageBlob, params, workflowData, workflowFilename) => {...}を追加。_wfmReceiveInpaintRequestと同じく、workflowDataが渡されれば実行前にloadWorkflowIntoEditor()でロードしてからwindow._wfmImageEditTab.runI2IExternal(imageBlob, params)を呼び、結果{ok, url}をそのまま返す。既存の_wfmReceiveImageForI2I(画像をI2Iスロットへセットするだけ、Comic Creatorのレイアウトタブが使用)は無改造。

検証: 変更した2ファイルをnode --checkで構文チェック。Comic Creator側(Imageタブの新規「Select I2I」パネル)からKapture経由で実際に呼び出し、Positive/Negative Prompt・Denoiseがワークフローへ反映された上でキュー実行され、結果画像URLが返ることを確認(Comic Creator側DEVLOG参照)。

How to apply: 「画像をComfyUIのワークフローに自動セットしてキュー実行し、結果を返す」という外部連携パターンを新設する際は、comfyEditor.applyImageToSlot/setPromptText/setInpaintParams/window._wfmGenerateTab.generateが既にマスクなし用途にも流用できる設計になっている(オプション引数は未指定なら書き込みをスキップする)。Inpaint版と重複する新規プリミティブをWorkflow Studio側に増やす必要は薄く、_runInpaintWithImagesと同型の薄いラッパーを追加するだけで足りる。

v0.3.77

追加機能: GalleryタブでSVGファイルの表示・管理に対応

「ギャラリータブでSVGファイル表示対応させたい」という依頼を受けて調査したところ、gallery_service.pyのIMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp", ".gif"}にSVGが含まれておらず、フォルダスキャン・一覧取得・削除・移動・配信のすべてで一律に除外されていたことが原因だった。フロントエンド(gallery-tab.js)側は拡張子フィルタを一切持たず、サムネイル・詳細プレビュー・ライトボックスすべて<img>タグで描画する設計だったため、バックエンドの許可リストにSVGを追加するだけで表示系は無改造で対応できた。

  • gallery_service.py: IMAGE_EXTENSIONSに.svgを追加。serve_thumbnail()にSVG分岐を新設し、GIF(アニメーション保持のため元ファイルを返す既存分岐)と同じ扱いで元ファイルをそのまま返すようにした。SVGはベクター画像でPillowがImage.open()できないため、そもそもラスタライズ縮小の意味がなく、<img>タグ側でブラウザが任意サイズにスケーリングして表示する前提。
  • gallery_routes.py: serve_image/serve_thumbのContent-Type判定は変更不要だった。Pythonのmimetypes.guess_type()が標準で.svg→image/svg+xmlを解決するため、既存のmimetypes.guess_type()フォールバック分岐がそのまま機能する。

セキュリティ: <img src="...">によるSVG表示は、ブラウザが<script>要素の実行や外部リソース読み込みを無効化する仕様のため、悪意あるSVG(埋め込みJS等)が混入していてもXSSリスクはない。<object>/<iframe>/iframe.srcdocのような埋め込み方式(スクリプトが実行され得る)は使っていない。

検証: 変更したgallery_service.pyをast.parse()で構文チェック。開発リポジトリと実行時custom_nodesフォルダへ同期し__pycache__をクリアした上でComfyUIを再起動し、実際にoutputフォルダへ配置したSVGファイルがGalleryタブでサムネイル・詳細プレビューとも正しく表示されることをユーザーが確認。

How to apply: project_next_release_docsのような「フォーマット追加」系の変更は、フロントエンドがフォーマット非依存の<img>/<video>等で描画する設計になっている場合、バックエンドの許可拡張子リスト(IMAGE_EXTENSIONS)に追加するだけで大半のUIパスに波及する。逆に言えば、新形式を追加する際はこのリストが唯一のゲートになっている前提を崩さないよう注意する。

追加機能: AI TOOL Chatでワークフロー不要のSVG生成・表示・Gallery保存

Gallery SVG対応の直後、「AI TOOLのチャット機能でワークフローを使用せずにSVGを作成することは可能か」と質問された。既存のChat画像生成(generate_imageツールコール→ComfyUIのT2I/I2Iワークフロー実行)とは別に、SVGはXMLテキストに過ぎずLLMのテキスト生成だけで完結するため、ComfyUIワークフローもTool Calling機能も不要である旨を回答した上で、「実装する(表示+Gallery保存)」の合意を得て実装した。

  • ai-tab.js: _extractSvgCode(text)を新設。アシスタント応答からマークダウンのコードフェンスで囲まれたSVGコードブロック、または生の<svg>...</svg>タグを正規表現で抽出する。appendChatBubble()のrole === "assistant"分岐に組み込み、検出時は_appendSvgPreview()を呼ぶ。抽出ロジックはreply.content(プレーンテキスト)のみを見ており、reply.toolCallsは一切参照しないため、モデルがtool calling(function calling)に対応していなくても動作する——「画像生成を許可する」トグルがONの場合のみ送信されるtoolsパラメータとは完全に独立した経路。
  • _appendSvgPreview(): 抽出したSVGコードをbtoa(unescape(encodeURIComponent(svgCode)))でBase64化し、<img src="data:image/svg+xml;base64,...">としてプレビュー表示(<img>経由のためスクリプト実行はブラウザ側で無効化され安全)。「Galleryに保存」ボタンを併設し、クリックで/wfm/gallery/image/saveへ{filename: "chat_svg_<timestamp>.svg", imageData: dataUrl}をPOSTする。
  • gallery_routes.pyのsave_image_to_gallery: 従来はimageDataを無条件にPNGとして扱い拡張子.pngを強制していたが、data URLのヘッダ(data:<mime>;base64,)からMIMEタイプを判定し_SAVE_EXT_BY_MIME(png/jpeg/webp/gif/svg+xml)で拡張子を決定する汎用処理に変更。Image Edit Tabの既存呼び出し(canvas.toDataURL("image/png"))は引き続き.pngとして保存され後方互換。
  • CSS(main.css)に.wfm-ai-chat-svg-wrapper/.wfm-ai-chat-svg-imgを追加(白背景パディング付きプレビュー枠、透過SVGでも視認できるように)。
  • i18n(i18n.js)にaiChatSaveSvgToGallery/aiChatSvgSaved/aiChatSvgSaveFailedを英日中3言語で追加。
  • ヘルプ文言3点セット(templates/index.htmlのwfm-help-ai-7、i18n.jsのhelpAi7英日中3言語、app.jsのhelpIdMap)でこの機能を追記。

検証: 変更したai-tab.js/i18n.js/app.jsをnode --checkで構文チェック、gallery_routes.pyをast.parse()で構文チェック。開発リポジトリと実行時custom_nodesフォルダへ全ファイル同期し__pycache__をクリア。ユーザーがComfyUI再起動後、Chatで実際にSVGを生成させてプレビュー表示とGallery保存を確認済み。

How to apply: LLMチャット機能に新しい出力形式(SVG、Mermaid図など、テキストとして表現できるフォーマット)を追加検討する際は、まず「ラスター画像生成のようにComfyUIワークフロー実行が本当に必要か、それともLLMのテキスト生成だけで完結するか」を切り分ける。後者であればreply.contentに対する検出・抽出ロジックのみで実装でき、Tool Calling対応可否に依存しない機能として提供できる。

v0.3.75

Lemonadeバックエンド追加(3つ目のAIバックエンド)

「LLMのバックエンドにlemonadeも追加したい」という依頼を受け、lemonade-server.ai のAPIドキュメント(OpenAI互換API・Ollama互換API等)を確認した上で実装。既存のバックエンド切替はai-tab.js(SPA AI TOOLタブ)・prompt-tab.js/settings-tab.js(Promptタブ設定)・node_sets_menu.js(サイドパネル)の3箇所に独立実装されており、いずれもif (backend === "ollama") {...} else {...}という2値決め打ちで、elseがLM Studio(OpenAI互換API: /v1/models, /v1/chat/completions)を指す構造だった。Lemonadeのデフォルト(OpenAI互換モード)はhttp://localhost:13305でLM Studioと全く同じエンドポイント形状のため、fetch系関数(fetchModels/callLLM/callChat/callVLM等)は変更不要で、ラジオボタンの選択肢追加とデフォルトURL解決部分の3値対応のみで済んだ。

対象範囲・接続方式は事前にユーザーに確認: 3箇所すべてに追加するか/SPA AI TOOLタブのみ先行導入するか、接続方式はOpenAI互換モード既定かOllama互換モードも選択肢に含めるかを質問し、「3箇所すべて」「OpenAI互換モード既定」で合意して実装した。

デフォルトURL解決の共通化:

  • static/js/util.jsにAI_BACKEND_DEFAULT_URLS(ollama/lmstudio/lemonade)とgetAiBackendDefaultUrl(backend)を新設し、ai-tab.js・prompt-tab.js・settings-tab.js(いずれもESモジュールでutil.jsをimport済み)の計8箇所の三項演算子を置き換え
  • web/comfyui/node_sets_menu.js(ComfyUI拡張側、static/jsとは別スコープのESモジュールでimport不可)は同名のヘルパーをローカルに複製(既存の重複実装パターンを踏襲)

UI・ヘルプ更新:

  • ラジオボタンvalue="lemonade"をtemplates/index.html(SPA)・static/js/settings-tab.js(Promptタブ設定)・web/comfyui/node_sets_menu.js(サイドパネル)の3箇所に追加
  • ヘルプ文言3点セット(index.html/i18n.jsのEN・JA・ZH/app.jsのhelpIdMap)のうち、helpIdMapは既存キーへの追記のみで変更不要。「Ollama or/または/或 LM Studio」を列挙している9キー(helpPrompt1, helpSettings5, helpSidepanel16, helpAi2, helpAi5, helpAi6, helpTrouble2 の各ロケール)すべてにLemonadeを追記
  • README.mdの現行機能セクション(Changelogではなくpresent-tense説明部分)とRequirementsセクションにLemonade関連の記載を追加。過去バージョンのChangelogエントリ(v0.3.40〜v0.3.74)は当時の実装を正しく記述しているため変更していない

検証: 変更した5つのJSファイル(util.js, ai-tab.js, prompt-tab.js, settings-tab.js, node_sets_menu.js)をnode --checkで構文チェック(全て成功)。実行中のComfyUIインスタンス(Kapture経由)で3箇所すべてを確認 — SPA AI TOOLタブSettings・SPA 設定タブ(Prompt tab AI Assistant)・サイドパネルAIタブのBackendラジオがいずれも["ollama","lmstudio","lemonade"]の3値で表示されることを確認、getAiBackendDefaultUrl()をブラウザ上で直接呼び出しollama/lmstudio/lemonade/未知の値それぞれで正しいデフォルトURL(lemonade→http://localhost:13305)を返すことを確認した。

追加修正: バックエンド切替時にURLを常にデフォルト値へ切り替え

上記の初回実装では、URL欄は「未保存の場合のみ」デフォルト値を補完する仕様(ラジオ切替時にif (!saved.backendUrl) urlInput.value = defaultUrl)だったため、一度どれかのURLを保存済みのユーザーがバックエンドを切り替えても欄が変わらなかった。「LMStudio、Lemonadeに切り替えた際それぞれデフォルトのURLに変更したい」というフィードバックを受け、3箇所すべてで無条件にデフォルトURLへ切り替える仕様に変更:

  • ai-tab.js: changeハンドラのif (!saved.backendUrl)ガードを削除し、常にurlInput.value = getAiBackendDefaultUrl(r.value)
  • settings-tab.js: 従来change時にplaceholderのみ更新していた(欄に既存の値があると効果が見えない実質バグ)のを、valueを直接書き換えるよう修正
  • node_sets_menu.js: changeリスナー自体が存在しなかった(バックエンド切替時にURLを更新する仕組みがそもそも未実装)ため新規追加

いずれもDOM上の表示値を書き換えるのみで、実際の永続化は各画面の「Save」ボタン押下時のまま変更なし。Kaptureで3箇所(SPA AI TOOLタブ・SPA 設定タブ・サイドパネル)すべて、ollama⇄lmstudio⇄lemonadeの切替でURL欄がそれぞれhttp://localhost:11434/http://localhost:1234/http://localhost:13305に変わることを確認。テスト中に実際の保存設定(wfm_ai_settings)を上書きしないよう、Saveボタンは押さずページリロードで検証用の一時入力を破棄した。

検討事項: Lemonadeの画像生成・音声機能への対応可否(質問への回答のみ、実装保留)

ユーザーから「lemonadeでは画像生成や音声出力、テキスト読み上げなどに対応しているが、これに対応させることは可能か」と質問(実装依頼ではなく質問のみと明言)された。Lemonadeは/v1/images/generations(画像生成)・/v1/audio/speech(読み上げ)・/v1/audio/transcriptions(音声認識)をOpenAI互換の独立エンドポイントとして提供しており技術的には対応可能だが、以下の理由で「既存バックエンド切替の自然な延長」ではなく別枠の機能追加になる旨を回答した:

  • 画像生成: 本プラグインの画像生成はChatのTool Calling(generate_image)からComfyUIワークフロー自体を実行する設計であり、Lemonade側の画像生成APIを追加すると生成経路が2系統になり紛らわしい
  • 音声読み上げ/文字起こし: AI TOOLタブに該当UI(再生プレイヤー、マイク入力等)が現状一切なく、既存キーの拡張ではなく新規機能追加になる

ユーザーは「検討したいと思います」として今回は実装せず保留。README.mdの「Backend support」直後に「Lemonade's other endpoints (not integrated)」として上記を明記し、将来検討時の起点として残した。

バグ修正: Bypass(バイパス)ノードを含むワークフローの生成失敗

ユーザーがミュートしているノードを含むワークフロー(chat_i2i_anima.json、ComfyUI保存済み)をGenerateUIタブで読み込んで生成したところ、以下のエラーで失敗した:

[ERROR] Failed to validate prompt for output 46:
[ERROR] * CLIPTextEncode 73:
[ERROR]   - Exception when validating inner node: '74'
[ERROR] * KSampler 66:
[ERROR]   - Exception when validating inner node: '74'

原因調査: 実際のワークフローjsonを確認したところ、ノード74(LoraLoader)はmode: 4(ComfyUI独自のBypassモード。litegraph標準のmode: 2=Never/Muteとは別物)だった。static/js/comfyui-workflow.jsのconvertUiToApi()はif (node.mode === 4) continue;でBypassノードをAPI出力から単純に除外するだけで、そのノードを経由していた配線(CLIPLoader→LoraLoader.clip→CLIPTextEncode(73).clip、UNETLoader→LoraLoader.model→KSampler(66).model)を繋ぎ直しておらず、73番・66番が存在しないノード74を参照したまま出力されてバリデーションエラーになっていた。ComfyUI本体はBypassノードを「入力と出力を同じ型のスロット同士でパススルー接続してから除去」する仕様のため、この動作の再現が必要と判断。

実装:

  • _resolveBypassSource(nodeById, linkMap, nodeId, slot, visited)(新規ヘルパー)を追加。リンクの参照先がBypassノードの場合、その出力スロットと同じ型の入力スロットを探し(同インデックス優先、なければ型一致する最初の入力)、その入力の配線元を再帰的に辿る(連鎖バイパスにも対応、循環ガード付き)。マッチする入力が無ければnullを返しリンク無し扱いにする
  • convertUiToApi()内の全リンク解決箇所をこの関数経由に変更。バイパスされていないノードが参照先の場合は即座にそのまま返るため、既存の非バイパス経路への影響はない
  • ついでにmode: 2(真のMute)も除外対象に追加(従来は一切スキップされておらず、ミュートしても実行時は普通に動いていた)。ただしMuteはパススルーせず単純除外 — ComfyUI本体でも「必須の下流依存があるノードをMuteすると壊れる」のが仕様通りの挙動のため、あえて救済しない

検証: chat_i2i_anima.jsonを実際のComfyUIサーバー(/object_info)に接続したNodeスクリプトでconvertUiToApi()に直接通し、KSampler(66).model→["68",0](UNETLoader直結)、CLIPTextEncode(73).clip→["61",0](CLIPLoader直結)、ノード74自体は出力に存在しない、ことを確認。さらに変換結果を実際に/promptへPOSTしnode_errors: {}(バリデーション成功)を確認。キューに入った時点で/interruptにより即座に中断し、無駄なGPU生成は行っていない。

追加機能: GenerateUIのRaw JSON上部にBypass/Muteノード表示

ユーザーから「生成UIタブのInput、Model、SettingsのRAW JSONペインの上部にバイパスノードの存在を示す表示を行いたい」と依頼。API形式のJSONにはmode情報が残らないため、Raw JSON表示だけではBypass/Muteの有無が分からなくなる点への対応。

  • comfyui-workflow.jsに_lastBypassedNodes/_lastMutedNodes(モジュールスコープ、_lastCheckpointSubstitutionsと同じパターン)を追加し、convertUiToApi()実行中にBypass/Muteで除外したノードの{nodeId, title}を記録。getLastBypassedNodes()/getLastMutedNodes()で呼び出し元から取得可能にした
  • templates/index.htmlの共有Raw JSONウィジェット(#wfm-gen-rawjson-widget、Input/Model/Settingsタブ間をJSで移動する仕組み — 既存のsearch barと同じ場所)に#wfm-gen-rawjson-bypass-noteを追加。3タブ共通のヘッダーに置くことで「Input/Model/Settingsそれぞれの上部」という要望を1箇所の実装で満たせる
  • generate-tab.jsのloadWorkflowIntoEditor()で、UI形式からの変換時(format === "ui")のみ上記2つのgetterを読み、該当ノードがあればt("rawJsonBypassNote", list)/t("rawJsonMutedNote", list)(新規i18nキー、EN/JA/ZH追加)をescapeHtml経由でnote要素に表示、無ければdisplay:none
  • CSS: --wfm-warning色のアンバー背景バーとしてmain.cssに追加
  • ヘルプ: helpGen7(index.html + i18n.js全ロケール、既存キーへの追記のためapp.jsのhelpIdMapは変更不要)にBypass/Mute注記の説明を追加

検証(Kapture、実機ブラウザ): Workflowタブからchat_i2i_animaを検索→「生成UIに読み込み」ボタンで読み込み、Input/Model/Settingsいずれのサブタブでも#wfm-gen-rawjson-bypass-noteに「バイパス中(除外・配線は再接続済み): LoraLoader (74)」が表示されることを確認。Bypass/Muteノードを含まない別ワークフロー(04031-lora.json)を読み込んだ場合はdisplay: noneで非表示になることも確認。

v0.3.74

AI TOOL Chatペインの画像添付対応 + I2I連携

v0.3.73でChatペインのT2I画像生成(Tool Calling)を実装した直後の依頼。「TOOLSのファイルドロップエリアを使ってChatに画像添付対応させたい、I2Iワークフローとの連携(画像の差し替え)も行いたい」という要望を受けて着手。実装前に設計上の分岐点(添付画像をLLMにも見せるか/I2I用ワークフローをT2Iと共用するか/添付の持続性)をユーザーに確認し、それぞれ「LLMにも見せる(マルチモーダル)」「T2I専用とは別にI2I専用ワークフローを新設」「明示的にクリアするまで保持」で合意して実装した。

対象ファイル: static/js/ai-tab.js, static/js/comfyui-editor.js, static/css/main.css, static/js/i18n.js, static/js/app.js, templates/index.html

TOOLS↔Chat 共有の添付画像:

  • 新しいドロップゾーンを作らず、TOOLSペインの既存ドロップエリア(wfm-ai-vlm-drop、VLMタスク用)をChatの添付機構としても共用する設計。モジュールレベルの共有state _toolsImage = { file, base64, mimeType } を導入し、initVlmTab()のローカル変数vlmImageを置き換えた
  • _syncAttachmentUI()(新規): TOOLS側プレビューの表示/非表示とChat側の添付インジケーター(サムネイル+ラベル+✕)を1箇所で同期。document.getElementByIdによる都度参照のため、initVlmTab/initChatTabどちらが先に呼ばれても安全
  • クリアは共通の_clearToolsImage()。TOOLSプレビュー上の✕ボタン、Chat添付インジケーターの✕ボタンのどちらからでも同じ状態を更新する
  • 添付は合意事項通り、送信後も自動クリアされず明示的にクリアするまで保持される

マルチモーダルチャット送信:

  • chatHistoryのエントリを内部共通形式 {role, content, images?: [{base64, mimeType}]} に拡張
  • callChat()に_formatMessagesForBackend(messages, backend)を追加し、送信直前にバックエンド別のワイヤーフォーマットへ変換(Ollama: images:[base64...]、LM Studio: contentをテキスト+image_urlのコンテンツブロック配列に変換)。callVLM()で既に使われている規約を踏襲
  • generate_imageツールの説明文を更新し、画像添付時はT2IでなくI2Iとして動作することをLLMに伝えるようにした(引数スキーマ自体はprompt/negative_promptのまま変更なし)

I2I生成ブリッジ:

  • comfyui-editor.jsのapplyImageToSlot(file, slotIndex, opts)にopts.workflow/opts.analysis対応を追加(applyImageAndMaskToSlotと同一パターン)。渡された場合はDOM更新を行わずアップロード後のファイル名のみ返す非破壊的変更で、既存呼び出し元(GenerateUI Imageタブ)への影響なし
  • generateImageFromChat(prompt, negativePrompt)を拡張し、_toolsImageがセットされていればI2I分岐へ:対象ワークフロー(GenerateUIの読み込み中ワークフロー、またはI2I専用ワークフロー)にload_image_nodesが無ければ明確なエラーを投げ、あればslot 0のLoadImageノードへ添付画像を差し替えてから通常のプロンプト設定・生成へ進む

SETTINGSペイン — Chat I2I Generation:

  • 既存の「Chat Image Generation」(T2I専用ワークフロー)セクションと全く同じ構造で「Chat I2I Generation」セクションを新設。chatGenI2IDedicatedEnabled/chatGenI2IDedicatedFilenameとしてwfm_ai_settingsに独立して永続化。ワークフロー一覧の取得(/api/wfm/workflows)はT2I側のfetchを流用し2つのselectへ同時反映

表示調整(ユーザーフィードバックにより追加修正):

  • Chatバブル内の添付画像エコー(送信したユーザーメッセージに表示される添付プレビュー)が大きすぎたため、appendChatBubbleにopts.attachThumb(新設、max 120px)を追加し、生成結果画像用のopts.imageUrl(.wfm-ai-chat-img)とスタイルを分離
  • 生成結果画像自体も表示が大きすぎるとのフィードバックを受け、.wfm-ai-chat-imgのmax-widthを100%→20%に変更(約80%サイズダウン)

推奨モデル: 動作確認の結果、Tool CallingとVision機能の両方が良好に動作したモデルとして Gemma 4:e4b、Qwen 3.5:9b、Ministral 3:3b(Ollama)を確認。ヘルプ(helpAi3)とREADMEに記載

ヘルプ更新(3点セット): templates/index.html/static/js/i18n.js(EN/JA/ZH)/static/js/app.jsのhelpIdMap — helpAi3(添付画像・I2I・推奨モデルの追記)、helpAi4(TOOLSドロップ画像がChatとも共有される旨)、helpAi6(Chat I2I Generation設定セクションの追記)。いずれも既存キーへの追記のためhelpIdMap自体の変更は不要

検証(Kapture、実機ブラウザ、Ollama): TOOLSへの画像ドロップ→Chat添付インジケーター表示、Chatでの画像編集依頼→I2I生成の実行、TOOLS/Chat双方の✕による添付クリア、Chatバブルの画像サイズ調整(添付エコー120px、生成結果max-width:20%)を確認。ブラウザ接続(Kapture)が一時的に切断される場面があり、再接続後に検証を継続した。

v0.3.73

AI TOOL Chatペインからの対話式画像生成(Tool Calling)+ Checkpoint関連の安全策

ユーザーからの「OpenWebUIのような対話式画像生成をこのプラグインで実現できないか」という相談から着手。既存のOllama/LM Studioチャット機能(static/js/ai-tab.js)とGenerateUIタブの生成機能(static/js/generate-tab.js)は別々に存在していたが、両者をLLM Tool Calling経由で接続した。実装・検証の過程で、GenerateUIのModelタブに以前から存在した「保存済みワークフローのCheckpointが実在しない場合、無警告で別モデルに差し替わる」という表示バグも発見・修正した。

対象ファイル: static/js/ai-tab.js, static/js/generate-tab.js, static/js/settings-tab.js, static/js/comfyui-editor.js, static/js/comfyui-workflow.js, static/css/main.css, static/js/i18n.js, static/js/app.js, templates/index.html

Chatペイン画像生成(Tool Calling):

  • generate_imageツール(prompt/negative_prompt引数)をOllama/LM Studio双方のchat APIに渡し、呼び出すかどうかはLLM自身が判断する設計(合意事項: 画像生成後、結果はLLMに送り返さず会話は一旦区切る/SPA版のみ対応、サイドパネル版は対象外)
  • callChat()の戻り値をstringから{content, toolCalls}へ変更。Ollamaのtool_calls[].function.argumentsはオブジェクト、LM Studio(OpenAI互換)はJSON文字列という差異は_parseToolArgs()で吸収
  • 生成処理は既存のwindow._wfmGenerateTab.generate(workflowOverride)(Image Edit「Inpaint」の外部呼び出しと同じ橋渡しパターン)をそのまま再利用。ワークフローはディープクローンしてからcomfyEditor.setPromptText()で書き込むため、GenerateUIタブの表示状態は一切汚染しない
  • 既存コードの欠落を修正: _coreGenerate/handleGenerate(generate-tab.js)が生成結果{images, seed}を計算していながらreturnしていなかったため、戻り値を追加(既存呼び出し元は戻り値を使っていないため影響なし)
  • Chatペイン下部(Send/Clearボタンの隣)に「画像生成を許可する」チェックボックス(デフォルトON、wfm_ai_settingsに永続化)。OFF時はcallChat()にtoolsを渡さずLLMには一切提示しない

SETTINGSペイン — Chat画像生成の専用ワークフロー:

  • Image Edit「Inpaint」の「Use dedicated workflow」と同一パターンを踏襲。OFF(デフォルト)はGenerateUIの読み込み中ワークフロー、ONは選択した保存済みワークフローを/api/wfm/workflows/raw経由でその都度ロード・変換(comfyWorkflow.detectFormat/convertUiToApi/analyzeWorkflow)して使用

バグ修正: Checkpoint未一致時の無警告差し替え(comfyui-workflow.js):

  • convertUiToApi()のCOMBO値フォールバック処理(Impact Packのワイルドカード等、動的な選択肢がインスタンス間でズレて生成エラーになるのを防ぐため既存)が、ckpt_nameにも無差別に適用されており、保存されたCheckpointパスが現在の環境に存在しない場合、無警告でCombo選択肢の先頭(たまたまアルファベット順で最初のFluxモデル)に差し替えられていた。この製品の「なぜかいつもFluxが表示される」という混乱の直接的な原因だった
  • getLastCheckpointSubstitutions()を追加し、ckpt_nameの差し替えが発生した場合のみ元の値・ノードIDを記録。loadWorkflowIntoEditor(generate-tab.js)で検知し、通常の「読み込み完了」トーストの代わりに、差し替え前のモデル名を含む警告トースト(errorスタイル)を表示する
  • GenerateUI Modelタブ(comfyui-editor.jsのrenderModelTab)にも、保存値が現在のモデル一覧に存在しない場合に警告付きの選択肢を先頭表示する対応を追加(API形式で保存されconvertUiToApiを経由しないワークフローに対して有効。UI形式ワークフローは上記の差し替えにより「一致している」ように見えるため、この表示だけでは検知できないケースがある)

設定タブ — デフォルトCheckpoint:

  • チェックボックスで有効化すると、GenerateUIへのワークフロー読み込み時に全Checkpointローダー系ノード(CheckpointLoaderSimple/CheckpointLoader/ImageMetadataPromptLoader)のckpt_nameを指定モデルへ強制上書き。サーバー側/api/wfm/settingsに永続化(複数ブラウザ間で共有)
  • 実装中に判明した順序依存の不具合: 上書き処理をanalyzeWorkflow()実行後に行っていたため、Raw JSON上のデータは正しく上書きされてもModelタブの表示(analysis.checkpoint_nodes由来)が古い値のまま残る不具合があった。analyzeWorkflow()実行前に上書きする順序に修正

ヘルプ更新(3点セット): templates/index.html/static/js/i18n.js(EN/JA/ZH)/static/js/app.jsのhelpIdMap — helpAi3(Chat画像生成機能+許可トグルの説明を追記)、helpAi6(SETTINGSペインの専用ワークフロー設定を追記)、helpTrouble8(新規: 「ModelタブのCheckpointが想定と違う」症状の原因と対処法)

検証(Kapture、実機ブラウザ + Ollama qwen3.5:9b):

  • Chatペインから「〜の画像を生成して」でgenerate_imageが呼ばれ、実際に画像が生成されチャット内に表示されることを確認(初回は無関係のワークフロー設定に起因するVAEチャンネル数不一致エラーが発生したが、ワークフロー側の修正後に正常動作を確認)
  • SETTINGSペインの専用ワークフロー機能: 選択したワークフロー(UI形式、ws_sdxl_t2i_basic.json)が正しくロード・変換され、Chat経由の生成に使われることを確認
  • 「画像生成を許可する」チェックボックスの位置変更(ヘッダー→下部の操作行)を反映
  • デフォルトCheckpointのON/OFF切り替えでModelタブの表示が正しく変わることを確認(ON: 指定モデル、OFF: 元の壊れた表示に復帰)
  • 実際に不一致のあるワークフロー(op_sdxl_ill_lora.json、保存値anime\animozalyca_v10.safetensorsが未インストール)で、差し替え警告トーストが元のモデル名を含めて正しく表示されることを確認

v0.3.72

Comic Creator連携用の外部Inpaintエントリポイントを追加

Comic Creator(comfyui-comic-creator)のImageタブから、既存のI2I連携(window._wfmReceiveImageForI2I、iframe越しの直接関数呼び出し)と同じ方式でInpaintも呼び出せるようにする依頼を受けて対応。

対象ファイル: static/js/image-edit-tab.js, static/js/gallery-tab.js, static/js/comfyui-editor.js

  • image-edit-tab.js: _runInpaint()(UI駆動)から共通処理(LoadImage/MaskEditorOneノードへの反映〜Generate実行〜結果URL取得)を_runInpaintWithImages()として切り出し、外部呼び出し専用のrunInpaintExternal(imageBlob, maskBlob, params)を新設(専用ワークフロー選択は扱わず、常にGenerateUIの現在ロード中ワークフローが対象)。マスクレイヤー選択に.visibleチェックを追加し、非表示のマスクレイヤーはUI駆動・外部呼び出し双方で対象から除外されるよう修正。
  • gallery-tab.js: window._wfmReceiveImageForI2Iと同じ場所にwindow._wfmReceiveInpaintRequest(imageBlob, maskBlob, params, workflowData?, workflowFilename?)を新設。workflowDataが渡されればI2Iと同じ手順(接続確認→モデルリスト確保→loadWorkflowIntoEditor)でロードしてからrunInpaintExternalを呼ぶ。
  • バグ修正(comfyui-editor.js): _loadImageElement()内のsrc instanceof Blob判定が、別ウィンドウ(Comic Creator)で生成されたBlob/Fileに対しては別レルムのコンストラクタ参照になるため常にfalseを返し、img.srcにBlobオブジェクトがそのまま代入されて[object Blob]という文字列がURLとして解釈され404になるバグを発見・修正(typeof src !== "string"による実体判定に変更)。Blob自体はURL.createObjectURL等でレルムをまたいで問題なく使えるため、チェック方法のみが原因だった。同一オリジンiframe越しに他ウィンドウ生成のBlob/Fileを直接受け渡す連携を実装する際は、受け取り側でinstanceofベースの判定を避けること。

検証(Kapture、Comic Creator実機 http://127.0.0.1:8189/ccc): Comic Creator側からInpaint実行→本ノードのInpaintが正常に動作し結果が返ることを確認。非表示マスクレイヤーの除外もComic Creator側UIから確認。

v0.3.71

Inpaint機能の追加(GenerateUI Imageタブ + Image Edit「Inpaint」タブ)

ユーザー要望「インペイント機能を追加したい」。参考ワークフロー(SDXL + VAEEncodeForInpaint構成)の解析から着手し、複数セッションにわたり段階的に実装。

対象ファイル: static/js/comfyui-workflow.js, static/js/comfyui-editor.js, static/js/image-edit-tab.js, static/js/generate-tab.js, static/js/i18n.js, static/css/main.css, templates/index.html, static/js/app.js

ワークフロー解析(analyzeWorkflow):

  • load_image_nodes に mask_used フラグを追加。LoadImage ノードのMASK出力(output slot 1)が他ノードの入力(["<id>", 1] 参照)として使われているかを走査して判定。MaskEditorNode 等の中継ノードを経由していても、参照元のLoadImageまで正しく特定できる
  • inpaint_encode_nodes(VAEEncodeForInpaint の grow_mask_by)、mask_editor_one_nodes(MaskEditorOne ノード)を新規検出

生成UI Input/Imageタブ — マスクスロット:

  • mask_used なLoadImageスロットにのみ「Mask」ドロップゾーンを追加表示
  • Apply時、マスク指定があれば画像+マスクをRGBA合成(マスク白領域→アルファ0)して1枚のPNGとしてアップロード。ComfyUIネイティブの mask = 1 - alpha 抽出にそのまま準拠するため追加ノード不要
  • Mask Editor One ノード用の専用スロットも追加(画像・マスクの両方が揃って初めてApply可能)

Image Editタブ — Inpaintツール(🩹):

  • プロパティパネルにポジ/ネガプロンプト、Grow Mask By、Denoise、Runボタン、結果画像プレビューを追加
  • Run押下で: 非マスクレイヤー合成画像 + アクティブなマスクレイヤーを書き出し → 生成UIのインペイント対応スロットへRGBA合成アップロード → プロンプト/grow_mask_by/denoiseを反映 → GenerateUIの生成を呼び出し(画面はImage Editタブに留まる)→ 完了後の結果画像をプロパティパネルに表示
  • バグ修正: ツールバーの <button data-tool="inpaint"> を templates/index.html に追加し忘れており、TOOL_DEFS にエントリを追加しただけではボタンが表示されない不具合があった(ツールバーは静的HTML、TOOL_DEFS は ready 判定とラベル参照専用)

Mask Editor One(comfyui-mask-editor-one)対応:

  • 実装(nodes.py/server.py)を調査した結果、この custom node は LoadImage を介さず、画像はサーバー側インメモリキャッシュ(node_id キー、bg_image_b64、/mask_editor/store_image でPOST)から、マスクは layer_data(JSON文字列ウィジェット)の各レイヤーのアルファチャンネルから合成されると判明(LoadImage の 1 - alpha とは逆で、alpha値がそのままmask値になる規約)
  • comfyEditor.applyImageAndMaskToMaskEditorOneNode() を新規実装し、この2つの経路(画像キャッシュPOST + layer_data書き込み)を実装
  • Inpaint Runは mask_used なLoadImageスロットを優先し、無ければMask Editor Oneノードへ自動フォールバック

インペイント専用ワークフロー:

  • Inpaintプロパティ上部に「Use dedicated workflow」チェックボックス + 保存済みワークフロー選択ドロップダウンを追加。OFF時は従来通りGenerateUIの読み込み中ワークフローを使用、ON時は選択したワークフローをその場でロード・解析し、GenerateUIタブの表示状態には一切触れずに画像/マスク/プロンプト/パラメータを適用して生成
  • generate-tab.js: _coreGenerate/handleGenerate に workflowOverride 引数を追加(省略時は従来通り comfyUI.currentWorkflow を使用)。オーバーライド時はバッチモードの影響を受けない単発生成に固定し、GenerateUI側のプロンプト同期(syncToWorkflow)もスキップ
  • comfyui-editor.js: applyImageAndMaskToSlot / applyImageAndMaskToMaskEditorOneNode / setPromptText / setInpaintParams に {workflow, analysis} オプションを追加し、指定時はそちらへ直接書き込む方式に統一

サンプルワークフロー追加: workflows/ws_inpaint_basic.json(LoadImage直結)、workflows/ws_inpaint_mask_editor_one.json(Mask Editor One使用)とプレビュー画像

ヘルプ更新(3点セット): templates/index.html、static/js/i18n.js(EN/JA/ZH helpImageEdit7e 新規)、static/js/app.js(helpIdMap に wfm-help-imageedit-7e 追加)

既知の簡略化点: Grow Mask By / Denoiseのプロパティパネル初期表示値は、専用ワークフロー使用時も常にGenerateUI側の読み込み中ワークフローから算出される(Run実行時に書き込まれる値自体は専用ワークフロー側に正しく反映される)

v0.3.70

Workflow AI要約: Ollama 404エラー修正

ユーザー報告「ワークフロー詳細モーダルの要約機能でエラー [ERROR] Ollama chat error: HTTP Error 404: Not Found」の調査から発生。

原因: static/js/workflow-tab.js の要約ボタンはモデル名を指定せずサーバー側 /api/wfm/ollama/chat を呼んでおり、サーバー側デフォルト(py/routes/ollama_routes.py の OLLAMA_DEFAULTS.model = "llava"、タグ無し)が使われていた。実環境には llava:7b のようにタグ付きの名前でしか入っておらず、Ollamaは完全一致でしかモデルを解決しないため404になっていた。加えて、設定タブの「AI Assistant Settings (Prompt Tab)」(localStorage の wfm_prompt_ai_settings)にOllamaのURL・モデル選択UIがすでに存在していたが、Workflowタブの要約機能はこれを一切参照していなかった。

対象ファイル: static/js/workflow-tab.js, py/routes/ollama_routes.py, static/js/i18n.js

変更内容:

  • workflow-tab.js: 要約ボタンが readJsonStorage("wfm_prompt_ai_settings", {}) で設定タブ保存済みのbackend/backendUrl/modelを読み込み、/api/wfm/ollama/chat へ url / model を渡すように変更。backendが ollama 以外(LM Studio)の場合は明示的にエラートースト
  • ollama_routes.py: handle_chat がリクエストbodyの url も受け付けるように対応(従来は model のみ)
  • i18n: summarizeOllamaOnly をEN/JA/ZHに追加

Image Edit: New button で Layer 1 自動追加

ユーザー要望「New button 画像作成時に自動でレイヤー1を追加してほしい」。既存の画像ロード時(hasLayers が偽の新規キャンバス経路)は既に「Layer 1」を自動生成していたが、_newCanvas()(Newボタン)だけは空の LayerManager のままだった。

対象ファイル: static/js/image-edit-tab.js

変更内容: _newCanvas() で this._layerMgr.addLayer("draw", "Layer 1") を追加し、アクティブレイヤー・SelectToolに設定。

Image Edit: マスクレイヤーのオーバーレイをアクティブなマスクのみに表示

ユーザー要望「マスクレイヤーのオーバーレイ表示をマスクレイヤー以外は非表示にしたい」。従来は layer.visible のみを条件にオーバーレイ(半透明の色付き表示)を描画しており、非アクティブなマスクレイヤーも常に表示され続けていた。

対象ファイル: static/js/image-edit-tab.js

変更内容:

  • _renderMaskLayerOverlay(ctx, maskLayer) の先頭に if (this._layerMgr?.activeLayer !== maskLayer) return; を追加。全ての呼び出し経路(単独マスク/マスクグループ)を1箇所でカバー
  • 副次修正: レイヤーパネルでSelectツール使用中に通常レイヤーへ切り替えた際に _updateCompositeView() が呼ばれていない分岐があり、上記の変更後は「マスク選択→通常レイヤー選択」でオーバーレイが画面に残ったままになる不具合が顕在化するため、当該分岐にも再描画呼び出しを追加

GenerateUI Input: プロンプトの「戻す」ボタン + 強調weight編集(Ctrl+↑/↓)

ユーザー要望2件を同じセッションでまとめて実装。

対象ファイル: static/js/comfyui-editor.js, static/js/i18n.js

変更内容:

  • 戻すボタン(↺) — Positive/Negative各Applyボタンの左隣に追加。クリックで、選択中ノードIDに対応するワークフロー上の現在値(=直前にApply/ロードされた値)をテキストエリアへ再読み込みする、Applyの逆操作。ノード選択セレクトに min-width:0 を追加し、ボタン増加によるレイアウト圧迫に対応
  • 強調weight編集(Ctrl+↑/↓) — A1111 / ComfyUIネイティブのCLIP Text Encodeと同様の操作感。選択テキスト(無ければカーソル位置の括弧ブロック、次点で単語)を (text:weight) 構文に変換し、0.05刻みで増減。weightがちょうど1.0に戻ったら括弧を自動除去。汎用ヘルパー _attachPromptWeightControl / _selectSurroundingParenBlock / _selectSurroundingWord を新規追加し、Positive/Negative両テキストエリアにアタッチ
  • バグ修正(同セッション内) — weightがマイナスに達した状態(例: (watermark:-0.05))でさらにCtrl+↓を押すと ((watermark:-0.05):0.95) のような二重括弧になる不具合を確認。原因は数値抽出の正規表現 ([\d.]+) が負符号にマッチせず、マッチ全体が失敗して選択テキスト全体を新しい単語として扱っていたため。(-?[\d.]+) に修正。下限は設けない仕様(ComfyUIネイティブのCLIP Text Encodeも同様に無制限のため)
  • i18n: revertPromptTitle をEN/JA/ZHに追加

ヘルプ更新(3点セット):

  • templates/index.html: wfm-help-imageedit-3(New button説明にLayer 1自動追加を追記)、wfm-help-imageedit-mask-3(アクティブなマスクのみオーバーレイ表示である旨を追記)、wfm-help-gen-4(戻すボタン・Ctrl+↑/↓ weight編集の説明を追記)
  • static/js/i18n.js: helpImageEdit3 / helpImageEditMask3 / helpGen4 をEN/JA/ZHで更新(新規キー追加ではなく既存キーへの追記のため app.js の helpIdMap は変更不要)

教訓(デプロイ同期、再掲): 開発リポジトリと custom_nodes/comfyui-workflow-studio(StabilityMatrix配下の実行時ディレクトリ)は別実体のコピーであり自動同期しない。今回変更した全ファイル(workflow-tab.js, ollama_routes.py, image-edit-tab.js, comfyui-editor.js, i18n.js, templates/index.html)を都度両方へ適用して対応した。

v0.3.69

ComfyUI Comic Creator(別カスタムノード、連携先名称は「Comic Creater」から改称。/wfm を同一オリジンのiframeで埋め込み使用)から、Workflow Studio の Gallery タブで選択した画像を直接送り込めるようにする連携機能。

対象ファイル: templates/index.html, static/js/gallery-tab.js, static/js/i18n.js

変更内容:

  • Gallery ツールバーの「Send GenUI Image」ボタン右隣に「Send CC」ボタンを追加(wfm-gallery-send-cc-btn)。初期状態は非表示
  • gallery-tab.js の bindEvents() で、window.parent !== window(iframe埋め込み判定)の場合のみボタンを表示
  • クリック時: state.selectedImage 未選択なら案内トースト。埋め込み側であれば window.parent.insertImageFromUrl(url)(ComfyUI Comic Creator 側の既存グローバル関数、main.js が非モジュールスクリプトのため window 直下に生えている)を呼び出し、選択中のコマ/オーバーレイへ画像を挿入する。関数が存在しない(ComfyUI Comic Creator以外からの埋め込み等)場合は案内トーストを表示
  • window.parent へのアクセスはtry/catchで保護(クロスオリジン埋め込み時の例外対策)
  • i18n: galleryCCNotAvailable / gallerySentCC を EN/JA/ZH に追加

ComfyUI Comic Creator 側は無改修。 既存の insertImageFromUrl(url)(コマ・オーバーレイ判定込みの画像挿入共通ヘルパー)をそのまま呼び出す設計。

ハマりどころ(デプロイ同期忘れ): 本リポジトリは開発用ディレクトリと実行時の custom_nodes/comfyui-workflow-studio(StabilityMatrix配下)が別実体のコピーであり、symlinkではない。上記3ファイルを開発リポジトリのみ編集した状態でComfyUIを起動していたため、「Send CCボタンが表示されない」という報告を受けた。実行時ディレクトリへ3ファイルを手動コピーして解決(差分は今回の追加分のみで他の乖離なし)。今後この種の変更をした際は、都度 custom_nodes/ 側への同期を忘れないこと(本体のコミット前チェックリスト「全ファイルを custom_nodes/ に同期」を参照)。

I2I連携(Comic Creater送信画像の受信・デフォルトワークフロー自動読込)

ComfyUI Comic Creater の「I2Iへ送る」ボタンから、iframe越しに画像をGenerate UIのImage入力スロットへ直接セットする受信用グローバル関数window._wfmReceiveImageForI2I(blob, name, workflowData?, workflowFilename?)を新規追加。上記のSend CCと対になる、逆方向(Comic Creater→Workflow Studio)の連携。既存の「Send GenUI Image」(Galleryの選択画像を送る版)とロジックを共有。

対象ファイル: static/js/gallery-tab.js

変更内容:

  • window._wfmReceiveImageForI2Iをモジュールトップレベルに追加。comfyEditor.applyImageToSlot(file, 0)を呼び、Generate UI → Input → Imageタブへ自動切替した上で画像をLoadImageスロットにセットする
  • workflowData(ワークフローJSON)が渡された場合、画像をセットする前にloadWorkflowIntoEditor(workflowData, workflowFilename)(generate-tab.js既存export)でワークフローを差し替える。Comic Creater側の設定タブで「デフォルトI2Iワークフロー」が有効な場合のみ渡される(user/default/workflows/配下のファイルをComic Creater側がGET /api/wfm/workflows/raw?filename=Xで取得して渡す設計、本リポジトリのAPI自体は無改修)
  • 既存の「Send CC」ボタンのクリックハンドラを拡張し、window.parent._ccI2ITargetMode(Comic Creater側が送信元をレイアウトタブ/Imageタブのどちらか記録するフラグ)を参照して、window.parent.insertImageFromUrl(url)(レイアウトタブ挿入、従来通り)とwindow.parent._ccImageTab.loadFromUrl(url, name)(Imageタブ読込、新規)を分岐

ハマりどころ1(プレビューのURLキャッシュ): Comic Creater側が同一ファイル名で連続送信すると、applyImageToSlotがpreviewImg.srcに設定するURL文字列(/view?filename=...)が変わらないため、サーバー側は正しく上書きされているのにブラウザがキャッシュ画像を表示し続けた(「編集内容が反映されていないように見えるが、実際に生成すると反映されている」という診断が難しい症状として報告された)。対策はComic Creater側でファイル名をタイムスタンプ付きでユニーク化する方式とし、本リポジトリ側は無改修で解決。

ハマりどころ2(モデルリスト未取得): iframe埋め込み直後などcomfyEditor.models(Checkpoint等一覧、init()時に1回だけ非同期取得)が空のタイミングでloadWorkflowIntoEditorを呼ぶと、Model/生成UIタブのCheckpoint等ドロップダウンが空のままレンダリングされ、選択もできなくなる不具合が発生した(Comic Creater再起動でも再現するとの報告)。_wfmReceiveImageForI2I内でワークフローロード前にcomfyUI.checkConnection()→(comfyEditor.models.checkpointsが空なら)comfyEditor.loadModelLists()を保証するよう修正して解決。

ComfyUI Comic Creator 側の実装: レイアウトタブ・Imageタブの「I2Iへ送る」ボタン、送信共通関数sendImageToWorkflowStudioI2I、設定タブの「I2I設定」ブロック(デフォルトワークフローのON/OFF・ファイル名指定)はすべてComic Creater側に実装(本リポジトリからは見えない)。

教訓(デプロイ同期忘れ、再掲): 前回のSend CC実装時と同じ理由で、開発リポジトリとcustom_nodes/comfyui-workflow-studio(StabilityMatrix配下の実行時ディレクトリ)は別実体のコピーであり自動同期しない。今回はgallery-tab.jsの変更を都度両方へ適用して対応した。

Loraバッチ生成のバリデーションエラー修正 + プロンプト自動反映

ユーザー報告「生成UIタブのBatchタブでLoraのバッチ機能を使用してIllustriousグループ(128モデル)でバッチ生成を開始したがエラー(Prompt outputs failed validation)となった」の調査から一連の修正を実施。

原因: パス区切り文字の不一致 ComfyUI(Windows)はLoraのサブフォルダ区切りに\を使ってenumリストを返すが、Workflow Studio内部では/区切りで管理していたため、実ファイルが存在するにもかかわらずLoraLoaderのlora_nameバリデーションに失敗していた(当初「サーバー起動後のキャッシュ未反映」と誤診断したが、実際は表記の不一致が原因だった)。

対象ファイル: static/js/comfyui-editor.js, static/js/models-tab.js, static/js/generate-tab.js

変更内容:

  • comfyui-editor.jsのcomfyEditorに共通ヘルパーresolveLoraName(name)を追加 — 内部の/区切り表記を、comfyEditor.models.loras(ComfyUIから取得済みの実表記)と突き合わせて実際の表記に変換
  • Lora Single適用(_applyLoraToNode)、Stack Apply、models-tab.jsのGenUI Modelボタンの3箇所をresolveLoraName経由に統一
  • Loraバッチ実行(generate-tab.js)はcomfyUI.fetchLoras()を都度呼び出して同様の変換マップを構築(バッチ実行前チェックと共用)

Loraバッチ実行前チェック:

  • 通常のLoraLoaderノードを使うワークフローの場合、バッチ開始前に選択済みLoraをComfyUI側の最新一覧と突き合わせ、認識されていないものがあればbatchLoraMissing警告トーストを表示(ComfyUIのRefresh/再起動を促す)
  • Lora Managerノード使用時はenum制約がないため対象外

Loraバッチ実行時のプロンプト自動反映:

  • 各Loraアイテム適用時、Positiveプロンプト(テキストエリア+ワークフロー内の各positiveノード)にLora構文<lora:name:strength_model:strength_clip>とCivitAIトリガーワードを自動追加。常にバッチ開始前の元プロンプトを起点に再構築するため、Lora構文が蓄積されることはない
  • バッチ完了・停止(Stopボタン)・エラーいずれの場合もfinallyでプロンプトを開始前の状態に自動復元

モデル詳細モーダルに「閉じる」ボタン追加:

  • models-tab.jsのモデル詳細モーダル(Checkpoint/Lora/Embedding/Hypernetworkなど全サブタブ共通)で、「保存」「GenUI PP/NP」「削除」ボタンの並び右端に「閉じる」ボタンを追加。連続作業時にモーダル右上のXボタンまでマウス移動する手間を削減。既存のclose i18nキーを再利用

ヘルプ更新(3点セット):

  • templates/index.html: wfm-help-gen-19を新規追加
  • static/js/i18n.js: helpGen19をEN/JA/ZHで新規追加(Loraバッチのプロンプト自動反映・復元・事前チェックの説明)
  • static/js/app.jsのhelpIdMapに"wfm-help-gen-19": "helpGen19"を登録

その他: 連携先カスタムノードの名称変更(Comic Creater → ComfyUI Comic Creator)をUI文言・コメント(i18n.js, templates/index.html, gallery-tab.js)に反映。

v0.3.68

MASK EDITOR ONE セクション常時表示化

ユーザー報告: 「Image EditタブでABRブラシが表示できない」の原因調査から発生。実際にはSAM3・BiRefNetは正常動作しており(/mask_editor/sam3/status・/mask_editor/birefnet/statusともloaded: true)、原因は Mask Editor One の brushes/ フォルダが空(.abr未インポート)で、_abrAvailable = tree.length > 0 の判定によりセクション自体が非表示になっていたこと。

調査の結果を受け、「MASK EDITOR ONEセクションは常時表示し、色で利用可否を示す」という方針に変更。

対象ファイル: static/js/image-edit-tab.js

変更内容:

  • Mask Paint モード(_renderMaskProps)・Draw ツール(_renderDrawProps)双方で、MASK EDITOR ONE / MASK EDITOR ONE (COLOR) セクションを囲んでいた ${this._abrAvailable ? ... : ""} の外側条件分岐を削除し、常時レンダリングするように変更
  • セクション見出しの文字色を this._abrAvailable に応じて動的に切り替え: 利用可能時 var(--wfm-success)(緑)、未インポート時 var(--wfm-text-secondary)(既存のデフォルト色)
  • 見出しに title 属性でツールチップ("Mask Editor One: brushes available" / "no brushes imported yet")を追加
  • Select ボタンは this._abrAvailable が false の間 disabled 属性を付与(ブラシが1つもない状態でクリックしても意味がないため)
  • ブラシ画像が選択済み(t.brushImage)の場合の Spacing / Angle / Sz Jitter / Rot. Jitter 詳細行の表示条件は変更なし(t.brushImage の有無のみで判定、_abrAvailable とは独立)

ヘルプ更新(3点セット):

  • templates/index.html: wfm-help-imageedit-5(Draw)・wfm-help-imageedit-mask-4(Mask Paint)の文言を更新
  • static/js/i18n.js: helpImageEdit5 / helpImageEditMask4 を EN/JA/ZH 3言語で更新
  • static/js/app.js の helpIdMap は既存キーのままで対応不要(新規キー追加なし)

Why: _abrAvailable はブラシ登録数のみで決まり、Mask Editor One のインストール有無とは無関係だった。旧仕様(ブラシ0件でセクション非表示)だと、ユーザーは機能の存在自体に気づけず「ABRブラシが使えない」と誤認する。常時表示+色分けにすることで、機能の存在と現在の利用可否を両方伝える。

v0.3.67

Alt+Click Apply & Generate

GenerateUI タブの Apply ボタンを Alt+Click すると、Apply と Generate を1操作で実行できる機能を追加。 よく変更する箇所(Positive Prompt・Checkpoint・KSampler)でApplyボタンとGenerateボタンを往復する手間を省く。

対象ボタン(comfyui-editor.js):

  • Input タブ — Positive Prompt の Apply(wfm-prompt-pos-apply)
  • Model タブ — 全モデル Apply(wfm-model-apply: Checkpoint / VAE / Diffusion Model / ControlNet / Hypernetwork)
  • Settings タブ — KSampler の Apply(wfm-settings-sampler-apply)

動作の流れ:

  1. Alt+Click → Apply 処理を実行(ワークフローへの書き込み+Raw JSON同期)
  2. Apply が成功(対象ノードが存在)した場合のみ wfm:apply-and-generate カスタムイベントを dispatch
  3. generate-tab.js のリスナーがイベントを受け取り handleGenerate() を呼び出す

循環参照を避けるためカスタムイベント経由で連携(comfyui-editor.js は generate-tab.js を import しない設計を維持)。 対象ノードが存在しない場合はdispatchされないため、ワークフロー未読み込み時は何も起きない。

各 Apply ボタンに title="Apply (Alt+Click: Apply & Generate)" を追加してホバーで操作を確認できるようにした。

ヘルプ更新:

  • helpGen18 を EN/JA/ZH の3言語で追加
  • index.html に wfm-help-gen-18 の <li> 追加
  • app.js の helpIdMap に "wfm-help-gen-18": "helpGen18" 登録

計画: Image Edit 拡張 — ABRブラシ連携 + Mask Editor One 統合

方針: Mask Editor One(同作者)のインストールを前提に、WFS Image Edit タブに2つの拡張を加える。

フェーズ1: Draw ツール — ABRブラシ対応

概要

WFS の Draw ツール(DrawTool.js)に画像ブラシ(PNG スタンプ)描画機能を追加する。 ブラシデータは Mask Editor One の API エンドポイントを介して取得・管理する。

バックエンド(Mask Editor One API 共用)

Mask Editor One がインストールされていれば以下のエンドポイントが利用可能:

エンドポイント 用途
GET /mask_editor/brushes/list フォルダツリー + ブラシ一覧取得
GET /mask_editor/brushes/raw?path=… ブラシ PNG 画像をサーブ
POST /mask_editor/brushes/import PNG フォルダ一括インポート
POST /mask_editor/brushes/upload_abr ABR ファイルインポート(パーサーは Mask Editor One 側)

ブラシの格納先は Mask Editor One のフォルダ(comfyui-mask-editor-one/brushes/)を共用。 一度インポートすれば WFS / Mask Editor One の両方で同じブラシが使える。

フロントエンド変更

DrawTool.js

  • brushType: "soft" | "image" プロパティ追加
  • imageBrush: { canvas, width, height } — スタンプ用 OffscreenCanvas をキャッシュ
  • _stamp() を改修: "image" モード時は ctx.drawImage(stamp) で描画(Jitter は phase 2 以降)
  • サイズは brushSize を高さ基準に、アスペクト比を維持してスケーリング

Image Edit タブ (image-edit-tab.js)

  • Draw ツール Options パネルに「Brush Library」ボタンを追加
  • Mask Editor One 未検出時はボタンをグレーアウト + ツールチップ表示

ブラシライブラリ UI(新規モーダルまたはサイドパネル)

  • Mask Editor One API 未応答時は「Mask Editor One が必要です」メッセージ
  • 応答時: フォルダツリー + ブラシサムネイルグリッド表示
  • クリックでブラシを選択 → DrawTool に適用
  • Import Folder / Import ABR ボタン(Mask Editor One の import エンドポイントを呼ぶ)

Mask Editor One 検出ロジック

// /mask_editor/brushes/list に HEAD または GET を投げて 200 なら存在確認
async function detectMaskEditorOne() {
    try {
        const r = await fetch("/mask_editor/brushes/list");
        return r.ok;
    } catch { return false; }
}

フェーズ2: Mask Editor One 連携ボタン

概要

WFS Image Edit タブのツールバーまたは Mask ツールのオプションパネルに 「Mask Editor で開く」ボタンを追加する。 現在のキャンバスレイヤーを画像として Mask Editor One に渡し、編集結果を WFS に戻す。

実装ポイント

  • WFS のキャンバス合成画像 (_exportComposite()) を Blob として取得
  • Mask Editor One の /mask_editor/store_image に POST してキャッシュ
  • Mask Editor One のモーダルを JavaScript から起動(window.maskEditorOpen() など)
  • Apply 後のコールバックで Mask Editor One の結果マスク PNG を取得
  • WFS の mask レイヤーとして追加

Mask Editor One 未インストール時

  • ボタンを非表示(検出ロジックで判定)

変更ファイル予定

ファイル 変更内容
static/js/image-edit/DrawTool.js 画像ブラシ描画対応
static/js/image-edit-tab.js Brush Library ボタン・Mask Editor 連携ボタン
static/js/image-edit/BrushLibrary.js 新規: ブラシライブラリモーダル UI
py/routes/ 不要(Mask Editor One API を直接利用)
static/css/main.css ブラシライブラリ UI スタイル
static/js/i18n.js ブラシ関連 i18n キー追加
templates/index.html 不要(動的生成)

実装順序

  1. DrawTool.js に画像ブラシ描画機能を追加(スタンプモード)
  2. Mask Editor One 検出ロジックを実装
  3. BrushLibrary.js 新規作成(ライブラリモーダル UI)
  4. Draw ツール Options パネルに Brush Library ボタンを接続
  5. フェーズ2: Mask Editor 連携ボタンを追加

2026-06-29: v0.3.66 — Mask ツール 5サブツール追加(Color / Alpha / Text / Vector / Shape)・Support リンク追加

変更ファイル: static/js/image-edit/MaskEditorOneTools.js(新規), static/js/image-edit-tab.js, templates/index.html, static/js/i18n.js, static/js/app.js

概要

Image Edit タブの Mask ツールに Color / Alpha / Text / Vector / Shape の5つのサブツールを追加。いずれも Mask Editor One に依存せず単独で動作する。また Help の Support ページに comfyui-image-feeder と comfyui-mask-editor-one のリポジトリリンクを追加。


MaskEditorOneTools.js(新規)

5つのマスクサブツールクラスを 1 ファイルにまとめた実装。各クラスは setCanvas() / activate() / deactivate() / onMouseDown() / onMouseMove() / onMouseUp() / onMouseLeave() インターフェースを実装し、image-edit-tab.js からルーティングされる。

MaskColorTool

  • クリック位置の色に近いピクセルをマスクとして選択(フラッドセレクト)
  • マスクレイヤーを除いた全レイヤーを合成した bgCanvas をピクセルソースとして使用
  • tolerance(0–255): RGB ユークリッド距離による色類似度しきい値
  • feather(0–100%): tolerance 範囲内で距離に応じた不透明度グラデーション
  • mode: "add" → white 書き込み、"subtract" → destination-out で消去

MaskAlphaTool

  • アクティブレイヤーのアルファチャンネルをマスクとして抽出
  • threshold(0–255): Alpha ≥ threshold のピクセルのみ完全マスク化
  • invert: 抽出結果を反転(前景除去用)
  • Extract Alpha ボタン(onMouseDown ではなく手動実行)で適用

MaskTextTool

  • キャンバスクリック → overlay パネル表示(テキスト入力・フォント/サイズ/スタイル設定)
  • _showOverlay(x, y): .ie-mask-text-overlay 要素をキャンバス座標に配置
  • _stamp(x, y): ctx.fillText で white スタンプ(mode=erase 時は destination-out)
  • 複数行対応(textarea 内改行 → 行ごとに fillText)
  • Ctrl+Enter / Stamp ボタンで確定、Escape / Cancel でキャンセル
  • フォントファミリーは MASK_TEXT_FONTS 定数としてエクスポート(14種)

MaskVectorTool

  • クリックで Catmull-Rom スプライン制御点を追加、プレビューキャンバス(ie-canvas-overlay)にリアルタイム描画
  • 最初の点をクリックまたは Enter キーで閉じてポリゴン塗りつぶし確定
  • Backspace / Delete で最後の点を取り消し、Esc でリセット
  • _buildSpline(ctx, pts, closed): Catmull-Rom → Bézier 変換して bezierCurveTo で描画
  • onBeforeCommit コールバック: コミット直前に Undo スナップショットを取得
  • Keyboard イベントは _setupKeyboard() で登録・クリーンアップ

MaskShapeTool

  • ドラッグで矩形または楕円のマスクを描画
  • _drawPreview(x2, y2, shift): ie-canvas-overlay に青の点線プレビュー
  • Shift 押下で正方形/正円(短辺で揃え)
  • ストロークバッファ問題対応: _curX / _curY / _curShift を onMouseMove 時に記録し、onMouseUp(x, y, e) で x ?? this._curX を使用(_onToolMouseUp が refCanvas 座標を持たないため)
  • mode: "add" / "erase"、shape: "rect" / "ellipse"

image-edit-tab.js 変更

インポート追加

import { MaskColorTool, MaskAlphaTool, MaskTextTool, MaskVectorTool, MaskShapeTool, MASK_TEXT_FONTS }
    from "./image-edit/MaskEditorOneTools.js";

コンストラクタ

  • this._maskColorTool / _maskAlphaTool / _maskTextTool / _maskVectorTool / _maskShapeTool = null を追加

_initMaskEditorOneTools()(新規)

  • Mask ツール初回選択時に一度だけ実行(if (this._maskColorTool) return;)
  • 5ツールを生成し onChange / onBeforeCommit コールバックを設定
  • VectorTool / ShapeTool には setPreviewCanvas(overlayCanvas) を渡す

_deactivateMaskSubtool() / _activateMaskSubtool()(新規)

  • 現在のサブツールに応じて deactivate() / activate() を呼び分け
  • _setActiveTool での this._maskTool?.deactivate() をこのメソッドに置換

_switchMaskSubtool(sub)(新規)

  • deactivate → this._maskSubtool = sub → _renderToolOptions("mask") → activate の一連フロー

ツールオプションバー

  • Paint / SAM3 の2ボタンから Paint / Color / Alpha / Text / Vector / Shape / SAM3 の7ボタンへ拡張
  • 各ボタンのクリックリスナーを _switchMaskSubtool() ベースに変更

_renderMaskProps(sub)(新規ブランチ追加)

  • color / alpha / text / vector / shape の各サブツール用 UI を生成
  • Color: Mode セレクト・Tolerance スライダー・Feather スライダー
  • Alpha: Threshold スライダー・Invert チェック・Extract Alpha ボタン
  • Text: Font セレクト・Size 入力・Bold/Italic/Align・Mode セレクト
  • Vector: 点数表示・Mode セレクト・Undo ヒント
  • Shape: Mode セレクト・Shape セレクト(Rect / Ellipse)

マウスイベントルーティング

  • _onToolMouseDown: color → colorTool.onMouseDown(x, y) + _buildBgCanvas() 渡し、alpha → alphaTool.extract()、text → textTool.onMouseDown(x, y)、vector → vectorTool.onMouseDown(x, y)、shape → shapeTool.onMouseDown(x, y)
  • _onToolMouseMove: vector → vectorTool.onMouseMove(x, y)、shape → shapeTool.onMouseMove(x, y, e)
  • _onToolMouseUp: shape → shapeTool.onMouseUp()(引数なし・内部の _curX/_curY を使用)
  • _onToolMouseLeave: vector → vectorTool.onMouseLeave()
  • _setupKeyboard: Vector の onKeyDown を追加(Escape / Enter / Backspace)

_buildBgCanvas()(新規)

  • マスクレイヤーを除いた全可視レイヤーを合成して返す OffscreenCanvas
  • ColorTool の click 時に生成・渡す(クリックごとに再生成して最新状態を反映)

ヘルプ更新(3ファイル)

templates/index.html

  • wfm-help-imageedit-7d: 6サブツール列挙の説明に更新
  • wfm-help-imageedit-mask-3: 7サブツール一覧説明に更新
  • wfm-help-imageedit-mask-9 〜 mask-13: 新規 <li> 5件追加(mask-8 の直後)
  • Support ページ: ComfyUI Image Feeder・Mask Editor One の GitHub リンクを Ko-fi の下に追加

static/js/i18n.js

  • 英語・日本語・中国語の3言語すべてで helpImageEdit7d・helpImageEditMask3 更新
  • helpImageEditMask9 〜 helpImageEditMask13 を3言語で追加

static/js/app.js

  • DATA_I18N_MAP に "wfm-help-imageedit-mask-9" 〜 "wfm-help-imageedit-mask-13" の5エントリを追加

2026-06-29: v0.3.65 — Mask Editor One 連携修正・トップバーアイコン修正・Mask ABRブラシ対応

変更ファイル: static/js/image-edit-tab.js, static/js/image-edit/MaskTool.js, web/comfyui/top_menu_extension.js, custom_nodes/comfyui-mask-editor-one/server.py(インストール済み)

Mask Editor One 対応バージョン: v0.1.9+(BiRefNet / SAM3 / ABRブラシライブラリ連携)

概要

Mask Editor One の仕様変更に伴う連携修正、プロパティパネル開閉によるトップバーアイコン消滅バグの修正、および Mask ツールへの ABR ブラシサポート追加。


Mask Editor One 連携修正(BiRefNet / SAM3)

問題

Mask Editor One の store_image API エンドポイントがフィールド名を image_b64 から bg_image_b64 に変更(仕様変更)したが、WFS 側が追随していなかったため、BiRefNet・SAM3 推論時に「no image available for this node」エラーが発生。

修正内容

  • image-edit-tab.js: SAM3・BiRefNet の store_image 呼び出しで image_b64 → bg_image_b64 に修正(2箇所)
  • server.py(インストール済み): store_image エンドポイントが bg_image_b64 と旧キー image_b64 の両方を受け付けるよう後方互換対応を追加
# Before
entry["bg_image_b64"] = data.get("bg_image_b64")

# After
img = data.get("bg_image_b64") or data.get("image_b64")
entry["bg_image_b64"] = img

Mask Editor One リポジトリ側: 開発元 server.py にも同修正を適用済み。PR 時に含めること。


トップバーアイコン消滅バグ修正(MutationObserver 追加)

問題

サブグラフノード選択時などにプロパティパネルを開くと、Vue がアクションバーを再レンダリングし、WFS・Snapshot・Node Sets のカスタム SVG アイコンが <i> タグにリセットされて消滅する(ComfyUI-Lora-Manager #996 と同根)。

修正内容(top_menu_extension.js)

  • replaceButtonIcons を applyIconToButton + pollUntilFound に分割
    • SVG が既存の場合はスキップ(btn.querySelector("svg") チェック)
  • MutationObserver を追加して Vue 再レンダリング後も即座に SVG を再適用
    • btn.querySelector("i") で <i> リセットを検知 → applyIconToButton 再実行
    • 監視ノード: [data-testid="action-bar-buttons"] → .actionbar-container → document.body(フォールバック順)
    • Observer 参照を window.__wfmIconObserver に保持
// 旧: 一度だけ適用、Vue 再レンダリングで消滅
const replaceButtonIcons = () => { ... btn.innerHTML = cfg.getIcon(); };

// 新: MutationObserver で監視・再適用
const applyIconToButton = (btn, cfg) => {
    if (btn.querySelector("svg")) return;  // 既存なら skip
    btn.innerHTML = cfg.getIcon();
    ...
};
observer.observe(watchNode, { childList: true, subtree: true });

Mask ツール — ABRブラシ対応(Mask Editor One オプション)

Mask Editor One がインストールされている場合のみ有効。未インストール時は従来の円形ブラシのみで動作(機能維持)。

MaskTool.js 変更

追加プロパティ 説明
brushImage / brushName ABR 画像ブラシ(null = デフォルト円形)
spacing (0.05–1.0) スタンプ間隔(ブラシサイズの倍率)
angle (0–359°) ブラシ固定回転角
sizeJitter / sizeJitterAmount スタンプごとのサイズランダム変動
rotationJitter スタンプごとの角度ランダム変動 (0–360°)
  • setImageBrush(img, name) / clearImageBrush() メソッド追加
  • ストロークバッファ: _initStrokeBuffer / _mergeStroke — PaintTool.js と同パターンで intra-stroke 蓄積を防止(lighten ブレンド)
  • _getImageStamp(): アルファチャンネル自動判定(ABR 透過 PNG 対応、グレースケール PNG は輝度から逆算)
  • _paintImageBrush(): angle 非ゼロ時に translate + rotate で描画
  • _paintToStroke(): Jitter 有効時は各スタンプに適用後、状態をリストア(PaintTool.js 同様)

image-edit-tab.js 変更

初期化

  • _abrAvailable、_abrBrushTree を追加
  • 起動時に _checkAbrAvailability() を非同期実行(/mask_editor/brushes/list が空でなければ有効)

プロパティパネル(Paint サブツール)

Mode     [Add] [Erase]
Size     ━━━━━━  30px
Hardness ━━━━━━  85%   ← ABRブラシ選択中は disabled
─── MASK EDITOR ONE ───
Brush    Circle   [Select] [✕]
 ↓ ブラシ選択後
Spacing  ━━━━━━  25%
Angle    ━━━━━━  0°
Sz Jitter━━━━━━  50%
Rot. Jitter □ On
  • Select ボタン: ABR ブラシライブラリピッカーモーダルを開く
  • ✕ ボタン: Circle(デフォルト)に戻す(ブラシ選択中のみ表示)
  • ABR セクションは Mask Editor One インストール済みの場合のみ表示

ABR ブラシライブラリピッカー (_openAbrBrushPicker())

  • フォルダツリー(左)+ブラシサムネイルグリッド(右)のモーダル
  • サムネイル生成: BrushLibrary.js と同ロジック(アルファ判定・輝度変換・タイトルトリム)
  • 「Round Brush (default)」ボタンでリセット
  • ブラシクリック → setImageBrush() 適用後モーダル閉じる

2026-06-28: v0.3.64 — Image Edit マスク Add/Subtract・BiRefNet BG Remove・SAM3 セグメンテーション・ブラシカーソルバグ修正

変更ファイル: static/js/image-edit-tab.js, static/js/image-edit/LayerManager.js

概要

Image Edit タブのマスク機能を強化。Mask Editor One インストール済み環境向けに BiRefNet 背景削除と SAM3 テキストプロンプトセグメンテーションを追加。また独立機能としてマスクレイヤーの合成モード(Add/Subtract)を実装。


マスクレイヤー Add/Subtract 合成モード

LayerManager.js

  • Layer クラスに operation: "add" プロパティ追加(maskApply 用)
  • toJSON() / fromJSON() で保存・復元対応
  • LayerManager.toggleOperation(id) メソッド追加(add ⇔ subtract トグル)

image-edit-tab.js — レイヤーパネル

  • マスクレイヤー行に A/S トグルボタン を追加(緑=Add、赤=Subtract)
  • クリックで toggleOperation() を呼び出し、合成ビューと一覧を再描画
  • マスクレイヤー選択時、operation に合わせて MaskTool の mode を自動同期(subtract → erase)

image-edit-tab.js — _updateCompositeView() 改修

  • 連続する maskApply: true マスクレイヤーをグループとして検出
  • グループを back→front 順で合成(Mask Editor One CanvasCompositor と同ロジック)
    • add → globalCompositeOperation = "lighten"
    • subtract → globalCompositeOperation = "destination-out"
  • 合成マスクでターゲットレイヤーを destination-in でクリップ
  • バグ修正: maskApply=true マスクが layers 末尾(ターゲットなし)の場合にオーバーレイ表示のみにフォールバック

BG Remove — BiRefNet(Mask Editor One 連携)

  • BG Remove ツール起動時に /mask_editor/birefnet/status を確認
  • Mask Editor One 未インストール時: BiRefNet オプションを disabled("Mask Editor One required" 表示)
  • インストール済み時: BiRefNet 選択可能
  • 実行フロー:
    1. store_image でアクティブレイヤー画像を Mask Editor One キャッシュに送信(node_id: "wfs_bgremove")
    2. birefnet/remove_bg で推論
    3. グレースケールマスク(白=前景)→ RGBA PNG に変換(_applyMaskToImage())
    4. 既存の BG Remove フロー(New Layer / 上書き)で結果を適用

SAM3 セグメンテーション(Mask Editor One 連携)

  • Mask ツールの上部メニューに [SAM3] ボタンを追加(Paint の隣)
  • 起動時に /mask_editor/sam3/status を確認して有効/無効を制御

SAM3 モードの上部メニュー

要素 説明
テキスト入力 セグメント対象のプロンプト("cat", "person" 等)
Max セレクト 最大マスク取得数(3/6/9/12)
Segment ボタン 推論実行

プロパティペイン(SAM3)

要素 説明
Mode: Add/Erase 適用モード切り替え
サムネイルグリッド 候補マスクをクリックで選択/解除(青ボーダー + ✓ マーク)
Apply Selected (N) 選択マスクをまとめて適用

実行フロー

  1. store_image でイメージレイヤーを送信(node_id: "wfs_sam3")
  2. sam3/segment で推論 → 複数マスク候補をプロパティペインに表示
  3. サムネイルをクリックして複数選択(新規 Segment 時に選択リセット)
  4. Apply Selected でまとめて合成適用
    • Add → source-over(既存マスクに重ね塗り)
    • Erase → destination-out(既存マスクから消去)
  5. アクティブレイヤーがマスクでない場合は新規マスクレイヤーを自動作成

2026-06-28: v0.3.63 — ノードタブ複数選択・全タブフィルタークリア・検索Xボタン

変更ファイル: static/js/nodes-tab.js, static/js/workflow-tab.js, static/js/models-tab.js, static/js/gallery-tab.js, static/js/util.js, static/js/i18n.js, static/css/main.css, templates/index.html

概要

ノードタブにギャラリー同様の複数選択+一括グループ・お気に入り操作を追加。ワークフロー/ノード/モデル/ギャラリーの4タブ全てに「✕ クリア」ボタンと検索入力オーバーレイXボタンを追加。


ノードタブ: 複数選択バルクメニュー

操作方法

操作 動作
Ctrl+クリック 個別選択/解除トグル
Shift+クリック アンカーから範囲一括選択
カードビュー・テーブルビュー 両対応

一括操作バー(選択時に自動表示)

  • グループに追加 — 既存グループに選択ノードを一括追加
  • グループから削除 — 既存グループから選択ノードを一括削除
  • 作成して追加 — 新規グループを作成して一括追加
  • ★ お気に入り追加(全件) — 選択ノードを全件お気に入り登録
  • ☆ お気に入り解除(全件) — 選択ノードを全件お気に入り解除
  • 選択解除 — 選択状態をリセット

実装詳細

  • state.selectedNodes(Set)と state.lastSelectionIndex を追加
  • _applyNodeSelectionToDOM() — 再レンダリング後に選択状態を DOM に同期
  • updateNodeBulkBar() — バー表示/非表示とグループセレクト更新
  • バルクバーを wfm-workflow-area(flex コンテナ)の外に配置(レイアウト崩れ防止)
  • .wfm-card.multi-selected スタイルを main.css に追加(オレンジ #f59e0b でギャラリーと統一)

全タブ: フィルタークリアボタン(✕ クリア)

各タブのツールバー最右端に「✕ クリア」ボタンを追加。クリックで全フィルターを一括リセット。

タブ リセット対象
ワークフロー 検索テキスト・グループ・バッジフィルタ(ALL に戻す)・バッチ表示フラグ
ノード 検索テキスト・カテゴリ・パッケージ・タグ・グループ・お気に入りフラグ
モデル 検索テキスト・タグ・フォルダ・グループ・ステータス・お気に入り・バッチフラグ
ギャラリー 検索テキスト・タグ・グループ・お気に入りフラグ(ソート順は維持)

全タブ: 検索入力オーバーレイ X ボタン

検索テキストボックス右端に ✕ ボタンをオーバーレイ表示。

  • 入力が空のときは非表示、文字を入力すると自動表示
  • クリックで入力をクリアしてフォーカスを戻し、即時フィルタリング更新
  • 汎用関数 setupSearchClearBtn(inputId, clearBtnId, onClear) を util.js に追加し4タブで共通使用
  • .wfm-search-wrap / .wfm-search-clear-btn スタイルを main.css に追加

対象

  • ワークフロータブ: wfm-search
  • ノードタブ: wfm-nodes-search
  • モデルタブ: wfm-models-search
  • ギャラリータブ: wfm-gallery-search

バグ修正

  • ノードバルクバーレイアウト崩れ — バルクバーを wfm-nodes-browser(display:flex コンテナ)内から外側に移動
  • 検索Xボタン非表示 — btn.style.display = "" がCSS display:none を復元してしまうバグを "flex" 明示指定に修正
  • ノードカード multi-selected 未適用 — .wfm-card.multi-selected が main.css になく視覚変化なしだったため追加

2026-06-27: WFS_PromptText の GenerateUI 対応・ドロップ時 title 固定

変更ファイル: static/js/comfyui-workflow.js, web/comfyui/node_sets_menu.js

概要

WFS_PromptText ノードを含むワークフローを GenerateUI タブで読み込んだ際に、Prompt タブの Positive/Negative テキストエリアへ正しく反映されるよう対応。あわせてサイドバーのプロンプトプリセットをキャンバスにドロップしたときのノード title をプリセット名から「Wfs Prompt」固定に変更し、ワークフローの再利用性を向上。


WFS_PromptText → GenerateUI Prompt タブ対応

変更内容(static/js/comfyui-workflow.js)

  • analyzeWorkflow Pass2 に WFS_PromptText 検出を追加
    • inputs.positive が文字列 → role: "positive", textKey: "positive" で prompt_nodes に登録
    • inputs.negative が文字列 → role: "negative", textKey: "negative" で prompt_nodes に登録
    • 同一ノードが positive/negative の両ロールを持つため(CLIPTextEncode 経由の伝播で両参照される)、getRole() に依存せず固定ロールで登録
  • _getWidgetMapping に WFS_PromptText: ["positive", "negative"] を追加
    • UI 形式 → API 形式変換時のフォールバック: widgets_values[0] → inputs.positive、[1] → inputs.negative

動作

手順 内容
UI 形式読み込み convertUiToApi が widgets_values を inputs.positive/negative にマップ
解析 analyzeWorkflow が WFS_PromptText を positive/negative 2 つの prompt_node として認識
表示 Prompt タブのセレクトにID:X Wfs Prompt [positive] / [negative] が表示される
Apply textKey: "positive"/"negative" でワークフローへ書き戻し

ドロップ時 title 固定(web/comfyui/node_sets_menu.js)

  • placePromptNode 内の if (promptName) node.title = promptName; を node.title = "Wfs Prompt"; に変更
  • サイドバーの Prompts タブからどのプリセットをドロップしても title が「Wfs Prompt」固定になる
  • プリセット内容(positive/negative テキスト)はウィジェット値として保存されるため変わらない

2026-06-27: v0.3.62 — Send GenUI Image ボタン追加・tagger_settings.json バグ修正・README 更新

変更ファイル: static/js/gallery-tab.js, static/js/comfyui-editor.js, static/js/i18n.js, static/js/app.js, templates/index.html, py/routes/settings_routes.py, README.md

概要

ギャラリータブのツールバーに Send GenUI Image ボタンを追加。選択画像を GenerateUI タブの Input → Image スロット 0 へ自動適用できるようになった。データエクスポートのバグ修正(tagger_settings.json 漏れ)も同梱。


Send GenUI Image ボタン(Gallery タブ)

実装内容

  • templates/index.html — ギャラリーツールバーの Image Edit ボタン右隣に #wfm-gallery-send-genui-image-btn を追加
  • static/js/comfyui-editor.js — comfyEditor.applyImageToSlot(file, slotIndex) メソッドを追加:
    • comfyUI.currentAnalysis.load_image_nodes[slotIndex] でターゲットノードを特定
    • comfyUI.uploadImage() で ComfyUI input フォルダへアップロード
    • currentWorkflow[node.id].inputs.image を更新してワークフローへ即時反映
    • GenerateUI の Input > Image スロット UI(プレビュー・ファイル名・ステータス)を即時更新
  • static/js/gallery-tab.js — ボタンクリックハンドラを追加:
    • 選択画像の blob を fetch → File オブジェクト化
    • DOM クリックチェーン(main tab → subtab → inner tab)で GenerateUI Input Image タブへ自動遷移
    • comfyEditor.applyImageToSlot(file, 0) 呼び出し後にサクセストーストを表示
  • static/js/i18n.js — galleryNoLoadImageNode / gallerySentGenUI / helpGallery17 を EN/JA/ZH で追加
  • static/js/app.js — helpIdMap に "wfm-help-gallery-17": "helpGallery17" を追加
  • templates/index.html — ヘルプ #wfm-help-gallery-17 を追加

動作条件

  • ワークフローが GenerateUI に読み込まれており、LoadImage ノードが 1 つ以上存在すること
  • 画像が Gallery で選択されていること

データ管理バグ修正 — tagger_settings.json をエクスポート対象に追加

  • py/routes/settings_routes.py の _DATA_FILES リストに "tagger_settings.json" を追加
  • Tagger のカスタムモデルディレクトリ・閾値設定がエクスポート/インポートされない問題を修正
  • tagger.db(SQLite)と wildcard/(ディレクトリ)は JSON バンドル非対応のため除外継続

2026-06-27: データ管理バグ修正 — tagger_settings.json をエクスポート対象に追加

変更ファイル: py/routes/settings_routes.py

概要

設定タブのデータエクスポート/インポート機能(_DATA_FILESリスト)に tagger_settings.json が漏れていたバグを修正。

config.py で定義されている全JSONデータファイルと _DATA_FILES を照合したところ、Taggerの設定ファイル(カスタムモデルディレクトリ・タグ閾値など)がバックアップ対象から除外されていた。settings_routes.py の _DATA_FILES リストに "tagger_settings.json" を追加して修正。

tagger.db(SQLite)と wildcard/(ディレクトリ)はJSONバンドルに含められないため除外のまま。


2026-06-27: v0.3.61 — G'MIC バグ修正・セキュリティ・Draw/Mask ブラシ操作性改善

変更ファイル: py/routes/gmic_routes.py, static/js/image-edit-tab.js, static/js/image-edit/DrawTool.js, static/js/image-edit/MaskTool.js, static/js/settings-tab.js, static/js/i18n.js, static/js/app.js, templates/index.html

概要

v0.3.60 で実装した G'MIC-Qt 連携の動作修正・セキュリティ強化と、Draw / Mask ツールのブラシ操作性を大幅に改善した。


G'MIC バグ修正

引数順序の修正

gmic_qt.exe の起動引数を正しい順序 -o <output> <input> に修正。以前の順序(<input> -o <output>)では G'MIC-Qt Standalone が即時終了してGUIが開かなかった。

CREATE_NEW_CONSOLE フラグ追加

Windows でバックグラウンドプロセス(ComfyUI)からGUIウィンドウを表示するために必須のフラグ。一度削除したが eagle_comic_creater_web の参考実装を確認して再追加した。

設定パスのプレースホルダー修正

settings-tab.js の G'MIC-Qt パス入力フィールドのデフォルト値とプレースホルダーからユーザー固有のパスを削除し、汎用的な C:\path\to\gmic_qt.exe に変更。gmic_routes.py のフォールバック値も同様に空文字に変更。


セキュリティ修正

handle_result パストラバーサル対策

POST /api/wfm/gmic/result の result_path は以前リクエストボディから直接 open() に渡していた。Path.resolve().relative_to(gmic_temp) による検証を追加し、gmic_temp ディレクトリ外のパスは 403 を返すように変更。eagle_comic_creater_web の同等実装と同じパターン。


ヘルプ更新(3点セット)

templates/index.html / static/js/i18n.js(EN/JA/ZH)/ static/js/app.js(helpIdMap)を同時更新。

追加したヘルプカード(Mask Tool カードの直後):

カード 内容
G'MIC Filter Edit with G'MIC ボタンの使い方、OKから「filter output」ウィンドウを閉じるまでのフロー
G'MIC-Qt Installation gmic.eu/download.html からのダウンロード手順、解凍先、設定タブへのパス設定方法

Draw / Mask ツール改善

ブラシカーソルを円に変更

  • DrawTool.activate() / MaskTool.activate() のカーソルを "crosshair" → "none" に変更
  • image-edit-tab.js に _initBrushCursor() / _updateBrushCursor(e) / _hideBrushCursor() を追加
  • <body> に固定配置の #ie-brush-cursor div を動的生成(白枠 + 黒シャドウで背景色に依存しない表示)
  • window.mousemove でキャンバスの getBoundingClientRect().width / canvas.width を用いてブラシサイズ(canvas 座標)を CSS px に変換し、円サイズを毎フレーム更新
  • ツール切り替え・mouseleave 時に非表示

キャンバス外からのドラッグ開始

ie-canvas-wrap(キャンバスを囲む余白エリア)の mousedown に draw / mask 用の処理を追加。e.target が drawCanvas / overlayCanvas の場合は既存ハンドラに委譲し、余白部分のクリックは DrawTool.getCanvasPos(drawCanvas, e) でキャンバス相対座標に変換して描画を開始。

キャンバス外でのストローク継続

  • window.mousemove に draw / mask の描画継続ロジックを追加(_drawing フラグが true の間はキャンバス外でも _onToolMouseMove を呼ぶ)
  • window.mouseup に描画終了ロジックを追加(キャンバス外でボタンを離した場合も正常終了)
  • _onToolMouseLeave() から draw / mask の onMouseLeave() 呼び出しを削除(mouseleave で描画が中断されなくなった)

2026-06-27: v0.3.60 — Image Edit Filter Tool (G'MIC-Qt 連携) 実装

変更ファイル: py/routes/gmic_routes.py(新規), py/wfm.py, templates/index.html, static/js/image-edit-tab.js, static/js/settings-tab.js

概要

Image Edit タブの「★ Filter」ツール(これまで非アクティブ)に G'MIC-Qt GUI 連携機能を実装。アクティブレイヤーの画像を G'MIC-Qt で開き、フィルター適用後の結果をレイヤーに反映できる。処理中は進行状況を表示し、Undo/Redo にも対応。


バックエンド

py/routes/gmic_routes.py(新規)

G'MIC-Qt 連携用の API ルートを新規作成。

エンドポイント 概要
POST /api/wfm/gmic/open base64 画像を受け取り一時 PNG に保存→gmic_qt.exe -o <out> <in> をバックグラウンドスレッドで起動。job_id を返す
GET /api/wfm/gmic/status/{job_id} ジョブの進行状況(pending / processing / completed / failed)を返す
POST /api/wfm/gmic/result result_path を受け取り、処理済み画像を base64 エンコードして返す
  • G'MIC-Qt の実行パスは Settings に保存された gmic_qt_path を参照。未設定の場合はデフォルトパス C:\path\to\gmic_qt.exe にフォールバック。
  • プロセスは subprocess.Popen + CREATE_NEW_CONSOLE でノンブロッキングに起動し、スレッド内でポーリングして完了を検出。

py/wfm.py

gmic_routes を import し、gmic_routes.setup_routes(app) をルート登録リストに追加。


フロントエンド

templates/index.html

左サイドバーの「★ Filter」ボタンから ie-tool-placeholder クラスを削除し、クリック可能な通常ボタンに変更。

static/js/image-edit-tab.js

  • TOOL_DEFS の filter を ready: false → ready: true に変更
  • コンストラクタに this._gmicState(lastResultJobId / processing / aborted)を追加
  • _setActiveTool("filter") → 他ツールへ切り替えた際に _gmicAbort() を呼ぶ
  • _renderToolOptions("filter") — 以下の UI を生成:
    • 「G'MIC GUI で編集」ボタン — 処理中は disabled
    • 「結果を反映」ボタン — 処理完了後に有効化
    • 進行状況テキスト — サーバーからのステータスメッセージを表示
    • 「中断」ボタン — 赤色。クリックでポーリングを停止し UI をリセット
新規メソッド 概要
_gmicOpenGui() アクティブレイヤーを PNG (base64) に変換→/api/wfm/gmic/open で起動
_gmicWaitForJob(jobId) 2 秒間隔でステータスをポーリング(最大 10 分)。完了で「結果を反映」を有効化
_gmicApplyResult() result_path 経由で処理済み画像を取得→ _saveUndo() → アクティブレイヤーに上書き描画
_gmicAbort() _gmicState.aborted = true でポーリングを中断し UI をリセット

static/js/settings-tab.js

Settings タブに「G'MIC-Qt Integration」セクション(<details>)を追加。

  • G'MIC-Qt 実行ファイルパス入力フィールド(id="wfm-settings-gmic-path")と「Save」ボタン
  • Save クリックで saveServerSettings({ gmic_qt_path: path }) を呼び出し settings.json に永続化
  • 保存結果を下部ステータステキストで通知

使用フロー

  1. Settings タブ → 「G'MIC-Qt Integration」セクションで gmic_qt.exe のパスを設定・保存
  2. Image Edit タブで画像をロードし、左サイドバーの「★ Filter」ツールを選択
  3. 上部ツールオプションバーの「G'MIC GUI で編集」をクリック
  4. G'MIC-Qt ウィンドウが起動し、アクティブレイヤーの画像が表示される
  5. フィルターを選択し「OK」をクリック
  6. WFS が完了を検出し「結果を反映」ボタンが有効化される
  7. 「結果を反映」をクリックするとアクティブレイヤーに処理済み画像が適用される
  8. Ctrl+Z でフィルター適用前の状態に戻すことも可能

2026-06-27: v0.3.59 — Image Edit Mask Tool 実装・Draw/Mask 描画バグ修正・クリッピング保存対応

変更ファイル: static/js/image-edit-tab.js, static/js/image-edit/MaskTool.js, static/js/image-edit/DrawTool.js, static/js/image-edit/LayerManager.js, templates/index.html, static/css/image-edit-tab.css, static/js/i18n.js, static/js/app.js

概要

Image Edit タブに Mask Tool (🎭) を追加した。マスクレイヤーへの白描画・消去・クリッピングマスク機能を実装。併せて Draw / Mask 描画中に他レイヤーが消えるバグ、およびクリッピング有効時に保存へ反映されないバグを修正した。


Mask Tool 新規実装

MaskTool.js(新規)

  • 白ペイント / destination-out 消去の soft round brush
  • DrawTool と同アーキテクチャ(stamp キャッシュ・_paintLine 補間)
  • paint モード: 白ラジアルグラデーション stamp
  • erase モード: 黒 stamp + destination-out globalCompositeOperation

image-edit-tab.js

  • TOOL_DEFS の mask エントリを ready: true に変更しツールを有効化
  • _maskSubtool, _maskInverted, _maskOverlayColor, _maskBlur を状態として保持
  • ツールオプションバー(mask): Paint ボタン、Invert チェックボックス、Overlay カラーピッカー、Blur スライダー
  • プロパティペイン (ie-props-pane): Mask ツール選択時に表示;Size・Hardness・Mode(Add/Erase ボタン)
  • _renderMaskLayerOverlay(): overlayColor fill → destination-in/destination-out でマスクを適用 → 半透明合成
  • ie-add-mask-btn ハンドラ: マスクレイヤー追加 → Mask ツール自動切り替え
  • レイヤーリストでマスクレイヤーをクリックすると自動的に Mask ツールに切り替え

templates/index.html

  • ie-props-pane ペイン追加(レイヤーパネル左隣)
  • レイヤーヘッダーに "M" ボタン追加

image-edit-tab.css

  • .ie-props-pane / .ie-props-header / .ie-props-body / .ie-props-row スタイル追加
  • マスクレイヤー行の ⬚ アイコンを赤で強調

クリッピングマスク機能

LayerManager.js

  • Layer クラスに maskApply プロパティ追加(toJSON / fromJSON 対応)
  • LayerManager.toggleMaskApply(id) メソッド追加

image-edit-tab.js

  • レイヤーリストのマスクレイヤー行に ✂ ボタン追加(data-action="mask-apply");有効時は青色
  • _updateCompositeView: maskApply=true のマスクレイヤー直下(back 側、配列 i+1)のレイヤーを tmp canvas でクリッピング合成
  • _renderMaskedLayer(ctx, canvas, maskLayer, targetLayer, showOverlay) 新メソッド
  • _buildCompositeCanvas: LayerManager.composite() から _compositeForExport() に変更
  • _compositeForExport() 新メソッド: クリッピングを適用しつつマスクオーバーレイは除外して保存用合成を行う

image-edit-tab.css

  • レイヤーパネル幅 190px → 220px(✂ ボタン追加による見切れを解消)

Draw / Mask 描画バグ修正

問題: DrawTool / MaskTool が drawCanvas に描いていたため _loadActiveLayerToCanvas() がコンポジット表示を上書きし、描画中に他レイヤーが消えていた。

修正:

  • DrawTool / MaskTool を layer.canvas に直接描画するよう変更
  • _onToolMouseDown(draw/mask): _loadActiveLayerToCanvas() を除去し setCanvas(activeLayer.canvas) + _updateCompositeView() に変更
  • _onToolMouseMove(draw/mask): _drawing フラグが立っている間 _updateCompositeView() を都度呼び出し、全レイヤーを常時合成表示
  • DrawTool / MaskTool の onChange: _syncActiveLayerFromCanvas() を除去(layer.canvas 直接描画のため不要)
  • _activateCurrentTool(draw/mask): setCanvas(activeLayer.canvas) を呼ぶよう変更
  • DrawTool.js: constructor / activate() / deactivate() に null ガード追加(canvas=null 初期化に対応)

ヘルプ更新(3点セット)

  • templates/index.html: Mask Tool カード追加(mask-title/1〜5)、helpImageEdit1(Mask 追記)、helpImageEdit7d・8b・12(⬚ アイコン)追加
  • static/js/i18n.js: EN/JA/ZH 全3言語に上記キーを追加;helpImageEditMask4(Add/Erase ボタン)・helpImageEditMask5(✂ ボタン説明)を更新
  • static/js/app.js: helpIdMap に 7d・8b・mask-title・mask-1〜5 を追加

2026-06-27: v0.3.58 — GenerateUI Model タブ GGUF対応拡張(Diffusion Model / Text Encoder)

変更ファイル: static/js/comfyui-workflow.js, static/js/comfyui-client.js, static/js/comfyui-editor.js, static/js/models-tab.js, templates/index.html, static/js/i18n.js

概要

GenerateUI の Model タブで、Diffusion Model セクション(UNET)と Text Encoder セクションを GGUF カスタムノードおよび CLIP バリアント全種に対応させた。


Diffusion Model — GGUF 対応

analyzeWorkflow(comfyui-workflow.js)

  • LoaderGGUF / LoaderGGUFAdvanced を diffusion_model_nodes に追加
  • ノードオブジェクトに inputKey: "gguf_name" を明示記録(UNETLoader 系は "unet_name")
  • _getWidgetMapping に LoaderGGUF: ["gguf_name"]、LoaderGGUFAdvanced: ["gguf_name", ...] を追加

fetchDiffusionModels(comfyui-client.js)

  • _fetchModelList(["UNETLoader", "UnetLoaderGGUF"], "unet_name") と _fetchModelList(["LoaderGGUF", "LoaderGGUFAdvanced"], "gguf_name") を Promise.all で並列フェッチし重複除去してマージ

renderModelTab / Apply(comfyui-editor.js)

  • Apply 時にノードの class_type が LoaderGGUF / LoaderGGUFAdvanced であれば inputKey を自動的に "gguf_name" に切り替え

applyToGenUI(models-tab.js)

  • unet タイプの検索キーに "gguf_name" を追加(["unet_name", "gguf_name"] 順で検索)

Text Encoder — CLIPLoader / DualCLIPLoader / GGUF 対応

analyzeWorkflow(comfyui-workflow.js)

  • 認識ノードに ClipLoaderGGUF、DualClipLoaderGGUF を追加
  • ノードオブジェクトに clip_type(= inputs.type)、device(= inputs.device)フィールドを追加
  • _getWidgetMapping に ClipLoaderGGUF: ["clip_name", "type"]、DualClipLoaderGGUF: ["clip_name1", "clip_name2", "type"] を追加

fetchTextEncoders(comfyui-client.js)

  • DualCLIPLoader / DualClipLoaderGGUF(clip_name1 キー)と CLIPLoader / ClipLoaderGGUF(clip_name キー)を並列フェッチしてマージ

renderModelTab(comfyui-editor.js)

  • Text Encoder セクションを sections 汎用処理から分離し #wfm-te-section プレースホルダーに非同期初期化関数 _initTextEncoderSection で描画
  • 単体 CLIP(CLIPLoader / ClipLoaderGGUF): clip_name 1段 + type ドロップダウン + device(GGUF のみ)
  • Dual CLIP(DualCLIPLoader / DualClipLoaderGGUF): clip_name1 + clip_name2 の 2 段 + type ドロップダウン + device(GGUF のみ)
  • type 選択肢はノードの /object_info から動的取得
  • フィルター入力は 1 段目(clip_name1)のみ対応

applyToGenUI(models-tab.js)

  • textencoder タイプの検索キーに "clip_name" を追加(CLIPLoader 系対応)

ヘルプ更新(3点セット)

  • templates/index.html — wfm-help-gen-5 初期テキスト更新
  • static/js/i18n.js — 英語・日本語・中国語 helpGen5 を更新(Diffusion Model / Text Encoder の詳細ノード種別を追記)

2026-06-26: v0.3.57 — Image Edit Blur Tool / BG Remove Tool 追加

変更ファイル: templates/index.html, static/js/image-edit-tab.js, static/js/app.js, static/js/i18n.js

概要

Image Edit タブに Blur ツール と BG Remove ツール を追加。両ツールはサイドバーから選択でき、ie-tool-placeholder を除去してアイコンを通常の白色で表示するよう修正した。ヘルプの Blur / BG Remove セクションを新規追加し、英語・日本語・中国語の i18n に対応。


Blur ツール(static/js/image-edit-tab.js)

TOOL_DEFS

  • { id: "blur", icon: "≈", label: "Blur", ready: true } — ready: false → true に変更

クラスプロパティ(constructor)

  • _blurRectMode — アクティブな矩形モード("blur" / "mosaic" / null)
  • _blurDragging, _blurDragStart, _blurDragCur — 矩形ドラッグ状態

ツールオプション UI(_renderToolOptions("blur"))

  • Whole Blur — 強度スライダー(1〜50 px)+ Apply ボタン
  • Whole Mosaic — ブロックサイズスライダー(5〜100 px)+ Apply ボタン
  • Rect Blur — トグルボタン(青色)でドラッグ矩形モードに入り、マウスアップ時に選択範囲をぼかす
  • Rect Mosaic — トグルボタン(橙色)で同様の矩形モザイク;Rect Blur と排他

マウスイベント

  • _onToolMouseDown/Move/Up/Leave に case "blur" を追加してドラッグ座標を追跡
  • _activateCurrentTool — _blurRectMode が有効な場合はキャンバスカーソルを crosshair に変更

新規メソッド

メソッド 概要
_applyWholeBlur(intensity) ctx.filter = "blur(Xpx)" でオフスクリーンキャンバスに描画し layer.canvas を更新
_applyWholeMosaic(size) _applyMosaicToRegion でレイヤーキャンバス全体をピクセル化
_canvasToLayerCoords(layer, canvasX, canvasY) applyTransform の逆変換:中心移動 → 逆回転 → スケール除算 → flip → レイヤーキャンバス座標に変換
_drawBlurPreview(start, cur) オーバーレイキャンバスにプレビュー矩形を描画(Blur=青、Mosaic=橙)
_applyRectEffect(start, cur) _canvasToLayerCoords でレイヤー座標に変換し、ぼかし or _applyMosaicToRegion を適用

モジュールレベル関数

function _applyMosaicToRegion(ctx, x, y, w, h, size)

ImageData のピクセルブロックを左上ピクセルの色で塗りつぶすことでモザイクを実現。


BG Remove ツール(static/js/image-edit-tab.js)

TOOL_DEFS

  • { id: "bgremove", icon: "⬚", label: "BG Remove", ready: true } — ready: false → true に変更

ツールオプション UI(_renderToolOptions("bgremove"))

  • Model セレクト — lightweight (@imgly) / birefnet (coming soon)
  • New Layer チェックボックス — ON: 結果を新規レイヤーとして追加 / OFF: アクティブレイヤーを上書き
  • Remove BG ボタン — 処理開始;ステータステキストで進捗を表示

新規メソッド

メソッド 概要
_bgRemoveImgly(imageDataUrl) @imgly/background-removal@1.5.7(esm.sh CDN)を動的 import() で読み込み、Blob URL を返す
_bgRemoveBiRefNet(imageDataUrl) POST /api/wfm/bg-remove を呼び出す stub(BiRefNet 実装予定)
_applyBgRemove() アクティブレイヤーを PNG に変換 → モデル呼び出し →Image に展開 → New Layer or 上書き適用 → Undo スタックに push

サイドバーアイコン修正(templates/index.html)

  • blur ボタンと bgremove ボタンから ie-tool-placeholder クラスを除去
  • filter と mask は未実装のため ie-tool-placeholder のまま維持(opacity: 0.38)

ヘルプ更新(templates/index.html / static/js/app.js / static/js/i18n.js)

追加カード

  • Blur Tool (≈) — Whole Blur / Whole Mosaic / Rect Blur / Rect Mosaic の操作説明(6項目)
  • BG Remove Tool (⬚) — モデル説明・New Layer 動作・処理フロー(5項目)

Tools リストに追記

  • helpImageEdit7b — Blur ツールの概要
  • helpImageEdit7c — BG Remove ツールの概要

i18n

  • app.js: 14 エントリの ID→キーマッピングを追加
  • i18n.js: 英語・日本語・中国語に各 14 キーを追加

2026-06-26: v0.3.56 — Style機能追加(GenerateUI ツールバー & Batchタブ)

変更ファイル: py/routes/settings_routes.py, static/js/generate-tab.js, templates/index.html, README.md

概要

Fooocus形式({name, prompt, negative_prompt})のスタイルJSONを生成時に自動適用する機能を追加。user/default/Workflow-Studio/style/ に置いた *.json ファイルをすべて読み込んでドロップダウンに表示し、ポジティブ/ネガティブプロンプトへ適用する。ワークフロー本体は変更せず生成ごとのコピーにのみ適用される。


バックエンド

py/routes/settings_routes.py

  • GET /api/wfm/styles を追加
    • DATA_DIR/style/*.json をファイル名順に読み込み、各ファイルの配列を結合して返す
    • ファイル読み込みに失敗した場合はその1ファイルをスキップしてログを出力(全体のエラーにはしない)

フロントエンド

static/js/generate-tab.js

スタイル管理

  • _stylesData — ロードしたスタイルオブジェクト配列({name, prompt?, negative_prompt?})
  • _batchStyleSelected — Batchタブで選択中のスタイル名 Set
  • _batchStyleOverride — Styleバッチ実行中に適用スタイルを一時的に上書きする変数(null のとき通常モード)

追加関数

関数 概要
_loadStyles() /api/wfm/styles を取得しドロップダウンとバッチリストを更新
_renderStyleDropdown() ツールバーの#wfm-style-select をスタイル名で再描画
_rebuildStyleList() _buildSimpleGroupList を使って左ペインのスタイル一覧を描画
_applyNamedStyle(workflow, style) プロンプトノードにスタイルを適用したワークフローのコピーを返す共通ロジック
_applyStyleToWorkflow(workflow) バッチ上書きがあればそれを優先、なければツールバー設定を参照して_applyNamedStyle を呼ぶ

適用ロジック(_applyNamedStyle)

  • ポジティブノード: style.prompt に {prompt} が含まれる場合は置換、含まれない場合は 元のプロンプト, スタイルプロンプト で連結
  • ネガティブノード: style.negative_prompt を元のネガティブプロンプトの末尾に , で連結
  • ワークフローコピー(JSON.parse(JSON.stringify(workflow)))に対して操作するため、comfyUI.currentWorkflow は変更されない

既存関数の変更

  • _coreGenerate(): ワイルドカード展開後に _applyStyleToWorkflow() を呼ぶよう変更(変数名 workflowForGenerate → workflowExpanded → workflowForGenerate の2段階に分割)
  • _updateBatchTypeLabel(): labels マップに style: "Style" を追加
  • _renderBatchPreview(): Style列の更新を追加(wfm-batch-style-count / wfm-batch-style-preview-list)
  • initBatchTab(): Styleタブ切り替え時に _rebuildStyleList() を呼ぶよう追加、All/Noneボタン(wfm-style-batch-select-all / wfm-style-batch-deselect-all)のイベントリスナーを追加、Batchタブ表示時に _rebuildStyleList() を追加
  • initGenerateTab(): 初期化時に await _loadStyles() を追加
  • _runBatchGenerate(): case "style" を追加 — 選択スタイルを _batchStyleOverride にセットして _runBatchLoop を実行、finally で _batchStyleOverride = null にリセット

templates/index.html

ツールバー(Styleセレクター)

Reset Workflow ボタンの右隣に以下を追加:

  • <label id="wfm-style-label"> + <input type="checkbox" id="wfm-style-enabled"> — Style の有効/無効チェックボックス
  • <select id="wfm-style-select"> — スタイルドロップダウン(min-width:120px / max-width:220px)

Batchタブ左ペイン

  • wfm-batch-left-tab-nav に <button data-left-tab="style">Style</button> を追加
  • wfm-batch-left-style コンテンツを追加(All/Noneボタン + wfm-batch-style-list)

Batchタブ Batch Queue(右ペイン)

  • Style 列を追加(wfm-batch-style-count / wfm-batch-style-preview-list / data-batch-type="style" チェックボックス)

ヘルプ

  • wfm-help-gen-2: ツールバーへのStyle追加とスタイルファイルパス・適用ロジックを追記
  • wfm-help-gen-12: 左ペインのタブ数 3→4、Styleタブの説明を追記
  • wfm-help-gen-14: Batch Queue の列数 6→7 に更新
  • wfm-help-gen-17(新規): Styleバッチ専用の説明を追加

スタイルファイル仕様

user/default/Workflow-Studio/style/ 配下の任意の *.json(複数可)を配置する。各ファイルはオブジェクトの配列形式で、以下のフィールドを持つ:

[
  {
    "name": "スタイル名",
    "prompt": "スタイルプロンプト({prompt} プレースホルダー使用可)",
    "negative_prompt": "ネガティブプロンプト"
  }
]

prompt / negative_prompt はいずれも省略可能。Fooocusの sdxl_styles_*.json 形式をそのまま使用できる。


2026-06-25: v0.3.55 — グループ機能バグ修正(全タブ)

変更ファイル: py/services/models_service.py, static/js/models-tab.js, static/js/generate-tab.js, static/js/prompt-tab.js, static/js/i18n.js

根本原因(データ消失): パス区切り文字の不一致

Windows 環境で ComfyUI が返す Checkpoint/LoRA 名はバックスラッシュ区切り(例: Illustrious\\model.safetensors)だが、バックエンドの _scan_model_names はフォワードスラッシュで正規化するため、グループ保存時にサブディレクトリ内モデルが「存在しない」と判定され全件削除・上書き保存されていた。

修正箇所

py/services/models_service.py

  • パス正規化: get_model_groups でメンバー名を比較前にバックスラッシュ→フォワードスラッシュへ正規化。save_model_groups でも保存時に正規化し、以後バックスラッシュが混入しない
  • ネットワークドライブ保護: モデルディレクトリが1つもアクセスできない場合(外付けHDD切断・ネットワークドライブ未接続等)はクリーンアップをスキップし、グループ全消去を防止
  • スキャンキャッシュ: _scan_model_names に 30秒 TTL キャッシュを追加。起動時の多重スキャン(モデルタブ + 生成UIタブ)による高負荷を軽減

static/js/models-tab.js

  • bulkRemoveFromGroup: RESERVED_GROUPS(Batch/Stack)の最後のメンバーを削除しても、キーごと消えないよう保護
  • renderGroupFilter: グループ削除後に state.groupFilter が削除済みグループを指したままモデルが全件非表示になるバグを修正(削除検出時に自動リセット)
  • toggleGroupEnable: グループ一括 enable/disable の部分失敗時、失敗したモデルもクライアント側で成功扱いになりサーバーと状態が乖離していた → data.ok リストのモデルのみ更新

static/js/generate-tab.js

  • Checkpoint/LoRA/Prompt/Workflow 全バッチグループ: v0.3.55 で誤って追加した予約グループ(Batch/Stack)除外フィルターを削除 → Batch/Stack グループはバッチ実行UIに表示され正常に選択可能
  • LoRA グループ: API取得後にバックスラッシュ→フォワードスラッシュ正規化を追加(Checkpoint 修正と同等)
  • batchComplete トースト: 英語ハードコード文字列を削除し t("batchComplete", ...) に変更

static/js/prompt-tab.js

  • migrateLocalStoragePresets: localStorage→API 移行時に negText(ネガティブプロンプト)が完全消失していたバグを修正
  • renderPmGroups: グループビューでメンバーを全削除したとき、Batch 予約グループがキーごと消失していたのを保護

static/js/i18n.js

  • batchComplete キーを英語・日本語・中国語に追加

ヘルプタブ — Image Edit i18n 対応

変更ファイル: templates/index.html, static/js/app.js, static/js/i18n.js

  • Image Edit ヘルプページ(wfm-help-page-imageedit)の全要素が英語ハードコードのままで、言語切替に追従していなかった問題を修正
  • 6セクション(Image Edit Tab / Tools / Layer Panel / Text Object Quality / Export & Navigation / Keyboard Shortcuts)の見出し6件・本文30件すべてに id 属性を付与
  • Shortcuts ページの Image Edit Tab Shortcuts セクションにも id 属性を付与(本文はキーを再利用)
  • static/js/app.js の helpIdMap に wfm-help-imageedit-* → helpImageEdit* の45エントリを追加
  • static/js/i18n.js に英語・日本語・中国語の3言語で37キー(タイトル7 + 本文30)を追加

2026-06-25: v0.3.54 — Image Edit Shape ツール追加・Models タブ overlay バグ修正

変更ファイル: static/js/image-edit/ShapeTool.js(新規), static/js/image-edit-tab.js, templates/index.html, static/js/models-tab.js

Image Edit Tab — Shape ツール(□ / S キー)

Eagle Comic Creator の画像編集タブ形状描画機能を移植。

対応形状

  • Rect — 矩形(Rounded チェックで角丸)
  • Ellipse — 楕円
  • Line — 直線(Stroke None 非表示 / 常にストローク有効)
  • FreeLine — フリーハンド曲線(旧 Curve。Stroke None 非表示 / 常にストローク有効)

Tool Options バー

  • Shape ドロップダウン、Rounded チェック(Rect/Ellipse のみ表示)
  • Fill: None チェック + カラーピッカー(Line/FreeLine 時は非表示)
  • Stroke: None チェック + カラーピッカー + 幅入力(Line/FreeLine 時は None チェック非表示)
  • Opacity スライダー、↩ Undo ボタン

動作

  • ドラッグ中: overlayCanvas に青い破線でプレビュー表示(FreeLine はリアルタイム描画)
  • ドラッグ離す: 確定 → 独立した draw レイヤーとして LayerManager に追加 → Undo 1操作で除去可
  • ShapeTool.drawShape(ctx, shapeObj) スタティックメソッドでレイヤー canvas に描画

実装ファイル

  • static/js/image-edit/ShapeTool.js(新規): ShapeTool クラス(プレビュー・確定・静的描画メソッド)
  • image-edit-tab.js: import・TOOL_DEFS 追加・_setActiveTool/_activateCurrentTool/_renderToolOptions/マウスハンドラー/_initCanvases を更新
  • templates/index.html: □ ツールボタン追加

ヘルプ更新

  • Image Edit Tab Tools セクション: Shape ツールの説明を追加
  • Image Edit Keyboard Shortcuts: S: Shape tool を追記(ヘルプ内・グローバル Shortcuts ページ)
  • image-edit-tab.js キーボードハンドラーに s → "shape" を追加(ヘルプ記載と実装を一致)

Models タブ — overlay ボタン非更新バグ修正

async イベントハンドラー内で await 後に e.currentTarget が null になる問題を 2箇所修正。

  • B(Batch)ボタン — 前回修正済み
  • S(Stack)ボタン — await toggleStack() 後に e.currentTarget.classList.toggle() を呼んでいたため TypeError が発生し黄色に変わらなかった → const btn = e.currentTarget を await 前に保存して修正
  • ⏸ enable/disable ボタン — 非 async ハンドラーで renderModelGrid() 経由の再描画を使用しており問題なし(確認のみ)

2026-06-25: v0.3.53 — Image Edit Tab 実装・レイヤーロック・ギャラリー保存

変更ファイル: static/js/image-edit-tab.js, static/js/image-edit/LayerManager.js, static/js/image-edit/SelectTool.js, static/js/image-edit/TextTool.js, static/css/image-edit-tab.css, templates/index.html, static/js/gallery-tab.js, py/routes/gallery_routes.py

Image Edit Tab(新規タブ)

レイヤー合成ベースの画像編集タブを追加。

ツール

  • Select (V) — クリック選択、ドラッグ移動、コーナーハンドルでリサイズ、上辺ハンドルで回転、Flip H / Flip V / Rotate 角度入力
  • Draw (B) — フリーハンドブラシ。色・サイズ・ブレンドモード設定
  • Text (T) — クリック位置にテキスト入力オーバーレイを表示。フォント・サイズ・スタイル・カラー・アライン設定。確定後はテキストのバウンディングボックスサイズのレイヤーとして配置

レイヤー管理

  • レイヤーパネル(右側): 可視トグル(👁/🚫)、ロックトグル(🔒/🔓)、不透明度スライダー
  • アクティブレイヤーのみ描画 vs 全レイヤー合成 を _compositeMode フラグで制御
  • Undo / Redo(Ctrl+Z / Ctrl+Y、上限 30 ステップ)

画像ロード

  • ドラッグ&ドロップ、Upload ボタン、Gallery タブ「Image Edit」ボタンで送信
  • レイヤーなし → Layer 1 として配置(自動ロック)、レイヤーあり → fitToCanvas でスケールして追加レイヤーとして追加
  • New ボタン: WxH プロンプトで新規キャンバス作成

テキストオブジェクト品質改善

  • measureText() でバウンディングボックスサイズのオフスクリーン canvas を生成し、レイヤーの contentW/H に設定(余白ゼロ)
  • リサイズ後(transformEnd)に _rerenderTextLayer() を呼び canvas.width = displayW で再描画 → 拡大時のぼやけを防止
  • layer.textProps にテキストプロパティを保存。ダブルクリックで再編集ダイアログを開き、OK で既存レイヤーを上書き更新
  • 回転ハンドル位置バグ修正: _getRotateHandle() の x 座標計算が cx - sinR * dist (符号逆) だったのを cx + sinR * dist に修正

レイヤーロック機能

  • Layer.locked プロパティ追加(toJSON/fromJSON 対応)
  • LayerManager.toggleLocked(id) メソッド追加
  • SelectTool: ロック中はドラッグ開始を禁止(選択・オーバーレイ表示は維持)
  • SelectTool _drawOverlay(): ロック中はオレンジ破線バウンディングボックス + 中央🔒表示、ハンドル非表示
  • Layer 1 読み込み時に layer1.locked = true で自動ロック(誤操作防止)
  • レイヤーリストに🔒/🔓ボタン追加

ギャラリーへ保存

  • Image Edit タブアクションバーに「Save to Gallery」ボタンを追加(Save PNG 右隣)
  • _saveToGallery(): prompt() でファイル名確認(デフォルト wfs-image-YYYYMMDDHHmmss)→ base64 PNG を POST /wfm/gallery/image/save へ送信
  • gallery_routes.py: save_image_to_gallery() を追加 — base64 デコード → _service._allowed_root 直下に保存。ファイル名サニタイズ・.png 自動付与
  • Gallery ツールバーの Refresh ボタン右隣に「Image Edit」ボタンを追加
  • 選択画像を Image Edit タブへ送信(レイヤーあり → 追加レイヤー、なし → Layer 1)

ヘルプ更新

  • Help タブナビに「Image Edit Tab」ページを追加(5カード構成: 概要・ツール・レイヤーパネル・テキスト品質・エクスポート&ナビ+ショートカット)
  • Keyboard Shortcuts ページに Image Edit ショートカット(V/B/T/Delete/Ctrl+Z/Y/Space+ドラッグ/ホイール)を追記
  • Gallery Tab ヘルプに「Image Edit ボタン」の説明を追記

2026-06-25: GenUI設定タブ LATENT IMAGE にプリセット機能を追加

変更ファイル: static/js/comfyui-editor.js

変更内容

正方形プリセットボタン

Applyボタン下部に固定サイズボタンを追加。クリックするとWidth・Height両方に同じ値をセットする。

  • 512 768 1024 2048 — よく使う正方形サイズ
  • 1K(1024) 2K(2048) 4K(4096) — 映像規格対応の正方形サイズ

解像度ドロップダウン + WHSet / HWSet ボタン

ドロップダウンからアスペクト比プリセットを選択し、横並びの2ボタンでWidth/Heightにセット。

  • WHSet — 選択値をそのままWidth/Heightにセット(例: 720x1280 → W=720, H=1280)
  • HWSet — W/Hを入れ替えてセット(例: 720x1280 → W=1280, H=720)

デフォルト10件(削除不可): 720x1280 768x1024 1152x896 1344x768 832x1216 832x1248 832x1280 1920x1080 2560x1440 3840x2160

カスタムプリセット管理(+/-ボタン)

  • + — 現在のWidth/Height値をカスタムプリセットの先頭に追加。デフォルトや既存カスタムと重複する場合は無視
  • − — 選択中のカスタムプリセットを削除。デフォルト行は削除不可(data-default="1" で判定)

カスタムプリセットは wfm_latent_presets キーでlocalStorageに永続化。ドロップダウンはカスタム→デフォルトの順で表示。


2026-06-24: GenUI ModelでLoRA適用後、Modelサブタブ再表示時に値がリセットされるバグ修正

変更ファイル: static/js/comfyui-editor.js

問題

ModelsタブのGenUI ModelボタンでLoRAをGenerate UIタブのLORA Singleに適用後、別サブタブ(Prompt/Image/Settings等)に移動してModelサブタブに戻ると、LoRAセレクトが元のワークフロー読み込み時の値にリセットされていた。

原因

renderLoraPane()はModelサブタブに切り替えるたびにel.innerHTMLを完全再構築する(Stackグループ変更を反映するため)。その際にcurrentValをanalysis.lora_nodes[0]?.lora_name(ワークフロー読み込み時点のcurrentAnalysisの値)から取得していた。applyToGenUI()はcurrentWorkflowを更新するがcurrentAnalysisは更新しないため、再描画時に古い値が使われて元に戻っていた。syntax表示とtrigger wordsもinnerHTMLリセットで消えていた。

修正

  • currentVal取得ロジックを修正: analysis.lora_nodes[0]?.lora_nameの代わりにcomfyUI.currentWorkflowから直接読み取る。標準LoraLoaderはinputs.lora_name、LoraManagerはinputs.loras.__value__[0].nameからstemを取得してフルパスに復元。
  • syntax/trigger wordsを再描画をまたいで保持: el.innerHTML設定前にwfm-lora-single-syntaxとwfm-lora-single-triggersの現在値を保存し、HTML再構築後に復元。

2026-06-24: v0.3.51 — Embedding GenUI PP/NP対応 + GenerateUIプロンプトタブEmbeddingセレクタ追加

変更ファイル: templates/index.html, static/js/comfyui-editor.js, static/js/models-tab.js

変更内容

ModelsタブEmbeddingタイプにGenUI PP / GenUI NPボタンを追加

  • templates/index.html: サイドパネルナビに #wfm-side-genui-np-btn を追加(Embeddingタイプ時のNP用ボタン)
  • models-tab.js:
    • applyEmbeddingToPrompt(modelName, promptType) 関数を追加。モデルstemから (embedding:Stem:1.0) を生成し comfyEditor.appendEmbeddingToPrompt() を呼ぶ
    • サイドパネルナビ: Embeddingタイプ時は "GenUI PP" / "GenUI NP" を表示、他タイプは従来の "GenUI Model" のみ表示
    • renderSideInfo(): Embeddingタイプ時は PP/NP ボタンを表示
    • openDetailModal(): Embeddingタイプ時は PP/NP ボタンを表示
    • closeSidePanel(): ナビボタンをすべて非表示にする(タイプ切替時の残留防止)

GenerateUI PrompタブにEmbeddingセレクタ追加

  • comfyui-editor.js:
    • models.embeddings: [] を追加、loadModelLists() に fetchEmbeddings() を追加
    • _lastPromptFocus モジュール変数を追加(最後にフォーカスしたpromptテキストエリアのIDと選択位置を保持)
    • renderPromptTab(): PromptタブのPositive/Negativeの下にEmbeddingsセクションを追加
      • Filter入力 + Selectドロップダウン(埋め込みモデル一覧)+ Weight入力(デフォルト1.0) + Pasteボタン
      • Pasteボタン: 最後にフォーカスしたテキストエリアのカーソル位置に (embedding:Stem:weight) を挿入
      • テキストエリアにblur/click/keyupリスナーを追加してカーソル位置を追跡
    • appendEmbeddingToPrompt(syntax, promptType) メソッドを追加: ワークフローの対応ノードとテキストエリア両方を更新してプロンプト末尾に追加

2026-06-24: v0.3.51 — GenerateUIタブ Hypernetwork対応 + ModelsタブGenUI Modelボタン追加

変更ファイル: static/js/comfyui-workflow.js, static/js/comfyui-editor.js, static/js/models-tab.js

変更内容

GenerateUI → Model タブに Hypernetwork セクション追加

  • comfyui-workflow.js analyzeWorkflow(): hypernetwork_nodes: [] を追加。HypernetworkLoader ノードを検出し hypernetwork_name / strength を収集。
  • comfyui-editor.js:
    • models.hypernetworks: [] を追加、loadModelLists() に fetchHypernetworks() を追加
    • renderModelTab() の sections 末尾に Hypernetwork を追加。extras プロパティで strength(数値フィールド)を表示・Apply 時に反映
  • models-tab.js GENUI_TYPE_MAP: hypernetwork: { key: "hypernetworks", inputKey: "hypernetwork_name" } を追加。これにより詳細パネル・モーダルの GenUI Model ボタンが Hypernetwork タイプでも有効になる。

2026-06-24: v0.3.51 — Hypernetwork/Embeddingモデル未表示バグ修正

変更ファイル: static/js/comfyui-client.js

原因

_fetchModelList が ComfyUI V3ノード(IO.ComfyNode)の object_info レスポンス形式に未対応だった。

  • V1形式: [["model1.pt", ...], {}] → 配列チェックで対応済み
  • V2形式: ["COMBO", {"values": [...]}] → valuesキーで対応済み
  • V3形式: ["COMBO", {"options": [...]}] → 未対応 ← HypernetworkLoader がこの形式

HypernetworkLoader は V3 IO.ComfyNode で実装されており、options キーを使う。_fetchModelList が values しか見ていなかったため、常に空配列を返してHypernetworkモデルが一切表示されなかった。

修正内容

comfyui-client.js

  • _fetchModelList: V3形式 inputDef[1]?.options の取得ケースを追加
  • fetchEmbeddings: ComfyUIの /embeddings エンドポイントへの直接アクセスをやめ、WFSバックエンドAPI /api/wfm/models/files?type=embedding を使うよう変更(SPAのURLによっては /embeddings がHTMLを返す問題があったため)

py/services/models_service.py

  • _MODEL_EXTENSIONS 定数を追加(.safetensors, .ckpt, .pt, .pth, .bin, .gguf, .pt2)
  • list_model_files(model_type) メソッドを追加: モデルファイルのみをフィルタリングしてソート済みリストを返す

py/routes/models_routes.py

  • GET /api/wfm/models/files?type=<type> エンドポイントを追加

2026-06-24: v0.3.51 — ModelsタブサイドパネルにGenUI Modelボタン追加

変更ファイル: templates/index.html, static/js/models-tab.js

変更内容

モデルタブのサイドパネル(詳細パネル)のタブナビゲーション(Info / Group / CivitAI)に、CivitAIタブの右横へ GenUI Model ボタンを追加した。

  • index.html: wfm-side-tab-nav 内にボタン(#wfm-side-genui-nav-btn)を追加。margin-left:auto で右端寄せ、初期状態は非表示。
  • models-tab.js showSidePanel(): モデル選択時に GENUI_TYPE_MAP で対応タイプ(checkpoint / lora / vae 等)なら表示、非対応なら非表示に切り替え。ボタンテキストとtitleもi18nで設定。
  • models-tab.js init: ボタンクリックで applyToGenUI(selectedModel, activeModelType) を呼び出すイベントを追加。

モーダル内の既存 GenUI Model ボタン(#wfm-modal-genui-model)はそのまま維持。


2026-06-22: v0.3.50 — ギャラリータブ プロンプト検索負荷改善(バックグラウンドインデックス)

変更ファイル: py/services/gallery_service.py, py/routes/gallery_routes.py

問題

v0.3.49で導入したプロンプト検索の未キャッシュ画像オンデマンド読み込みが、大量の画像フォルダで深刻な負荷問題を引き起こしていた。

  • list_images()がasyncio.to_thread()なしでaiohttpイベントループ上で同期実行されていたため、全未キャッシュ画像のファイルI/O+DB書き込みがComfyUIのイベントループをブロックし、ハングアップが発生していた。

修正内容

gallery_routes.py

  • list_imagesルートで_service.list_images()をasyncio.to_thread()でスレッドプールに投げるよう変更(イベントループブロック解消)
  • 検索なしのフォルダロード時に_service.start_background_index()を呼び出してバックグラウンドインデックスを自動起動

gallery_service.py

  • import threadingを追加
  • __init__にバックグラウンドインデックス用キャンセルイベント(_bg_cancel)を追加
  • list_images()からオンデマンド読み込みを完全に削除(prompt_cacheが空の場合の読み込み処理を除去)
  • start_background_index(folder_path)メソッドを追加:前回のスレッドをキャンセルして新しいdaemonスレッドを起動
  • _bg_index_folder(folder_path, cancel)メソッドを追加:
    • 未キャッシュ画像のみを対象(prompt_cacheが存在する画像はスキップ)
    • 10枚処理するたびにバッチDB保存 + 50msスリープでメインスレッドへの影響を最小化
    • キャンセルイベントで即座に中断可能(フォルダ切替時など)

新しい動作フロー

フォルダ選択(検索なし)
  → asyncio.to_thread で list_images() をスレッド実行(ノンブロッキング)
  → 画像一覧を即返却
  → バックグラウンドスレッドが自動起動
      → 未キャッシュ画像を10枚ずつ処理、50msスリープを挟んで低負荷で順次インデックス
      → インデックス完了後の検索でA1111画像等もヒットするようになる

プロンプト検索
  → asyncio.to_thread で list_images() をスレッド実行(ノンブロッキング)
  → キャッシュ済み画像のみ検索(重いI/O処理なし)

2026-06-22: v0.3.49 — 生成UIタブ A1111風ワイルドカード展開・ギャラリータブ バグ修正2件

変更ファイル: static/js/generate-tab.js, static/js/app.js, py/services/gallery_service.py, static/js/i18n.js, templates/index.html

生成UIタブ — A1111風SPAサイドワイルドカード展開

ImpactWildcardEncode/Processor以外のノード(CLIPTextEncodeなど)のテキスト入力に__name__構文が含まれる場合、ComfyUIへの送信前にSPA側でワイルドカードを展開する機能を追加。

  • generate-tab.js に _fetchWildcardLines(), _expandWildcardText(), _expandWildcardsInWorkflow() を追加
  • _coreGenerate() 内の comfyUI.generate() 呼び出し前にワイルドカード展開を実行
  • ワイルドカードファイルは既存の /api/wfm/wildcards/content API経由で取得(WFS wildcard フォルダ参照)
  • ファイル内容はセッション中キャッシュ(_wcLineCache);ランダム選択は毎回独立して実行
  • ネスト展開対応(最大5パス);見つからないワイルドカードはそのまま通過
  • ImpactWildcardEncode/ProcessorノードはスキップしてComfyUIサーバー側(Impact Pack)に委ねる
  • 単体生成・バッチ生成どちらも _coreGenerate() を経由するため両方で機能する
  • ヘルプ更新(EN/JA/ZH): helpGen16 を追加(index.html, app.js, i18n.js 3言語)

ギャラリータブ — エクスポートボタンのラベル欠落を修正

app.js の updateUITexts() に wfm-gallery-bulk-export ボタンへの textContent 設定が抜けていた。galleryBulkMove と galleryBulkDelete の間に galleryBulkExport の設定を追加。

ギャラリータブ — プロンプト検索で未キャッシュ画像にヒットしない問題を修正

prompt_cache は詳細パネルで一度開いた画像にのみ構築されていた。A1111など外部ツールで生成した画像をWFS外から追加した場合、一度も開かれていない画像はプロンプト検索にヒットしなかった。

  • gallery_service.py の list_images() で検索実行時、prompt_cache が空の画像をその場でメタデータ読み込み(_read_png_metadata / _read_jpeg_metadata)してキャッシュに保存してから検索対象に含める
  • A1111形式PNG(tEXtチャンク parameters キー)も正しく検索対象になる
  • 初回検索時のみ読み込みが発生;以降はキャッシュから即時検索

2026-06-22: v0.3.48 — ギャラリータブ 画像ダウンロード・一括エクスポート機能追加

変更ファイル: templates/index.html, static/js/gallery-tab.js, static/css/gallery-tab.css, py/routes/gallery_routes.py, static/js/i18n.js

詳細パネルの画像プレビューにダウンロード機能を追加

  • プレビュー画像にマウスホバーでダウンロードアイコン(⬇)をオーバーレイ表示
  • クリックで単一画像をダウンロード(ファイル名はオリジナルを保持)
  • CSS: .wfm-gallery-preview-wrapper, .wfm-gallery-preview-overlay, .wfm-gallery-download-btn を追加

複数選択バーに「エクスポート」ボタンを追加

  • 複数選択した画像をZIPファイルにしてダウンロード可能
  • ファイル名: gallery_export_<タイムスタンプ>.zip
  • Backend: POST /wfm/gallery/images/export-zip エンドポイント実装
    • リクエスト: { "paths": [...] } (画像パスリスト)
    • レスポンス: ZIP ファイルをバイナリで返送
    • セキュリティ: _check_path_allowed() でパストラバーサル防止

i18n対応

  • 英語: "Export" / "Download started" / "Export completed. ZIP file is downloading."
  • 日本語: "エクスポート" / "ダウンロード開始" / "エクスポート完了。ZIPファイルをダウンロード中です。"
  • 中国語: "导出" / "下载开始" / "导出完成。正在下载 ZIP 文件。"

ヘルプテキスト更新

  • ヘルプ wfm-help-gallery-6: 複数選択バー説明に "Export" を追加
  • ヘルプ wfm-help-gallery-7: 詳細パネル説明にダウンロードアイコンについての記述を追加

2026-06-22: v0.3.47 — モデルタブ ファイル移動後グループ状態修正

変更ファイル: static/js/models-tab.js

モデル移動後のグループ状態不整合を修正

bulkMoveModels でモデルをサブディレクトリ間移動した際、Python側(move_models)はグループメンバーの名前を from → to に更新していたが、JS の state.modelGroups が更新されていなかった。

このため移動直後にグループフィルターで絞り込むと、移動したモデルが消えてしまう(旧名で参照されるため一致しなくなる)問題があった。

  • data.moved.forEach ループ内で state.modelGroups の各グループメンバーを from → to でリネーム
  • サーバーが _groups を更新する処理と対称的なクライアント側更新
  • state.modelGroups と state.allModelGroups[type] は同一オブジェクト参照のためインプレース更新で両方に反映される
  • Generate タブ Batch の _batchGroupState.groups はサーバーから毎回取得するため影響なし

2026-06-21: v0.3.46 — プロンプトタブ AI Assistant LM Studio対応・設定タブ AI Assistant設定に変更

変更ファイル: static/js/settings-tab.js, static/js/prompt-tab.js, static/js/i18n.js, templates/index.html

設定タブ「AI Assistant設定(プロンプトタブ)」

設定タブの「Ollama設定(プロンプトタブ)」セクションを AI TOOL タブの SETTINGS ペインと同等の内容に変更。

  • セクション名を 「AI Assistant設定(プロンプトタブ)」 に変更
  • バックエンド選択ラジオ(Ollama / LM Studio) を追加
  • API URL 入力 — バックエンド切替時にプレースホルダーが自動変更(Ollama: http://localhost:11434、LM Studio: http://localhost:1234)
  • モデル選択 + Refresh — 選択中のバックエンドへ直接接続してモデル一覧を取得
  • Test ボタン — バックエンドへ直接接続テスト
  • Save ボタン — 設定を localStorage wfm_prompt_ai_settings に保存(サーバーAPI不要)
  • AI TOOL タブの設定(wfm_ai_settings)とは独立した設定キーを使用

プロンプトタブ AI Assistant LM Studio対応

Python プロキシルート(/api/wfm/ollama/*)依存を廃止し、AI TOOL タブと同方式のブラウザから直接バックエンドAPIを呼び出す実装に変更。

  • Ollama: {url}/api/tags(モデル一覧)、{url}/api/chat(チャット)
  • LM Studio: {url}/v1/models(モデル一覧)、{url}/v1/chat/completions(チャット)
  • 画像添付(ビジョンモデル)も両バックエンド対応 — Ollama形式(imagesフィールド)を LM Studio形式(content配列のimage_url)に自動変換
  • 設定タブで保存したモデルを初期選択として表示
  • ウェルカムメッセージの "AI Assistant (Ollama)" 表記を削除

バックエンド変更時の自動モデル再取得

設定タブでバックエンドを変更した後にプロンプトタブですぐ送信しようとすると、ドロップダウンに旧バックエンドのモデル名が残ったまま送信されモデル未認識エラーが発生する問題を修正。

  • _promptAiBackendForModels 変数にモデル一覧取得時のバックエンドを記録
  • sendMessage() の冒頭で現在設定と比較し、バックエンドが変わっていた場合は自動でモデル一覧をリフレッシュしてから送信
  • モデル一覧表示に [ollama] / [lmstudio] のバックエンド名を付記

i18n 更新(EN/JA/ZH)

  • ollamaSettings → "AI Assistant Settings (Prompt Tab)"
  • ollamaUrl → "API URL"(汎用)
  • saveOllama → "Save AI Assistant Settings"
  • ollamaSaved → "AI Assistant settings saved"
  • helpPrompt1 — "Ollama" → "Ollama / LM Studio"・設定場所の説明を追記
  • helpSettings5 — バックエンド選択・URL・モデル・接続テストの説明に更新
  • helpTrouble2 — "Ollamaに接続できない" → "AIアシスタントに接続できない(Ollama/LM Studio両対応)"

2026-06-21: v0.3.45 — グループ孤立エントリ自動クリーンアップ・Galleryタブ Shift+クリック範囲選択

変更ファイル: py/services/models_service.py, py/services/gallery_service.py, py/services/gallery_metadata.py, static/js/gallery-tab.js, static/js/i18n.js

グループ孤立エントリ自動クリーンアップ

ファイルをアプリ外(OS)で移動・削除した際にグループデータに残る孤立エントリを自動的に除去する仕組みを追加。

Modelsタブ

  • models_service.py に _scan_model_names(model_type) を追加 — _get_model_dirs の全ディレクトリを rglob で走査し、有効なモデルファイル名のセットを返す(.disabled サフィックスを除いた相対パス)
  • get_model_groups(model_type) に自動クリーンアップを追加 — タイプ指定時にファイルスキャンを実行し、存在しないモデル名をグループから除去;変更があった場合のみ保存
  • move_models() でグループエントリを即時更新 — アプリ内での移動時に renames リストを収集し、_groups[model_type] 内の旧パスを新パスに一括書き換え

Galleryタブ(フォルダ表示)

  • gallery_metadata.py に cleanup_stale_images(folder_path, existing_paths) を追加 — folder_path 配下のメタデータキーのうち existing_paths に含まれないものを削除して保存
  • gallery_service.py の list_images() で _scan_folder 直後にクリーンアップを呼ぶ — スキャン結果の abs_path セットを渡すため、60秒 TTL キャッシュとフォルダ mtime 変化検知に連動して自動実行

Galleryタブ(グループ画像一覧)

  • gallery_metadata.py に remove_stale_paths_from_group(group_name, stale_paths) を追加 — 孤立パスをグループから一括削除し1回の保存で完結
  • gallery_service.py の list_images_in_group() で Path.is_file() チェックを追加 — FeederタブのGalleryモード(GET /wfm/gallery/groups/{name}/images)呼び出し時に孤立パスをグループから自動除去して既存ファイルのみ返す

各タブのクリーンアップトリガー

タブ トリガー 対象
Modelsタブ タブ切替・モデルタイプ変更時 全グループから存在しないモデル名を削除
Modelsタブ(アプリ内移動) move_models API 呼び出し時 グループ内パスを即時更新
Galleryタブ(フォルダUI) フォルダ表示時(mtime 変化 or 60秒後) フォルダ内の孤立メタデータエントリを削除
Galleryタブ(Feederモード) グループ画像一覧取得時 グループから孤立パスを削除

GalleryタブのShift+クリック範囲選択

  • state に lastSelectionIndex: -1 を追加(Shift 選択のアンカーインデックス)
  • createThumbCard() / createTable() に data-path 属性を追加
  • _applySelectionToDOM() ヘルパーを追加 — state.selectedImages を元に描画済み要素の multi-selected クラスを一括同期;ページング未描画の要素はスクロールで描画される際に createThumbCard が自動で反映
  • サムネイルビュー・テーブルビューのクリックハンドラを更新:
    • Shift+クリック: アンカーから現在位置まで state.images のインデックス範囲で一括追加
    • Ctrl+クリック: 個別トグル(既存)+アンカー更新
    • 通常クリック: 詳細表示(既存)+アンカー更新
  • lastSelectionIndex リセット箇所: フォルダ切替時・loadImages() 完了時(ソート/フィルタ変更後も含む)・一括解除ボタン押下時
  • i18n.js の helpGallery6(EN/JA/ZH)を更新して Shift+クリック範囲選択を追記

2026-06-21: v0.3.44 — Galleryタブ高速化・比較モード・バルク強化・バルクバーUI整理

変更ファイル: py/services/gallery_service.py, py/routes/gallery_routes.py, static/js/gallery-tab.js, static/css/gallery-tab.css, templates/index.html, static/js/app.js, static/js/i18n.js

フェーズ2-A: サーバーサイドサムネイル生成

  • gallery_service.py に serve_thumbnail(path, width=256) を追加
  • Pillow で画像を 256px JPEG に縮小し data/thumb_cache/ にキャッシュ(キー: md5(path:mtime:width))
  • GIF はアニメーション保持のため元ファイルをそのまま返す;Pillow 未インストール時は元ファイルにフォールバック
  • GET /wfm/gallery/image/thumb?path=...&w=256 エンドポイントを gallery_routes.py に追加(Cache-Control 24時間)
  • gallery-tab.js のサムネイルビュー・テーブルビューを /image/serve → /image/thumb に切り替え;ライトボックス・詳細プレビューは引き続き元画像を使用

フェーズ2-B: 無限スクロールページング

  • モジュールレベル変数 PAGE_SIZE=50, _renderedCount, _scrollObserver を追加
  • renderImages() をリファクタリング: 初回は50枚のみ DOM に追加し、末尾にセンチネル div を配置
  • IntersectionObserver(rootMargin 300px)がセンチネルを検出するたびに次の50枚を追加
  • 全枚数を描画し終えたらオブザーバーを切断してセンチネルを削除
  • renderImages() 呼び出し(フォルダ切替・フィルタ変更・ソート変更)のたびに自動リセット

フェーズ3-A: バルクAPIエンドポイント

  • gallery_service.py に bulk_set_favorite(paths, value) と bulk_group_op(paths, group, action) を追加
  • POST /wfm/gallery/bulk/favorite, POST /wfm/gallery/bulk/group を gallery_routes.py に追加
  • フロントエンドの bulkSetFavorite() / bulkAddToGroup() を N並列個別リクエスト → 単一バルクリクエストに変更

フェーズ3-B: 画像比較モード

  • Ctrl+クリックで2〜4枚選択すると Bulk Bar に「比較」ボタンが出現
  • openCompare(paths) 関数を追加: --compare-cols CSS カスタムプロパティで列数を動的制御した CSS Grid サイドバイサイドライトボックス
  • 閉じるボタンは position:fixed; top:16px; right:16px で配置し overflow:auto コンテナによるクリッピングを防止
  • i18n: galleryBulkCompare(EN/JA/ZH)追加

フェーズ3-C: プロンプト全文検索

  • get_image_metadata() 実行時に PNG/JPEG 埋め込みテキスト(workflow キーを除く文字列フィールド)を連結して metadata.json の prompt_cache に保存
  • list_images() の検索フィルタに prompt_cache を追加(ファイル名・メモ・タグに加えて)
  • 検索プレースホルダを「名前・タグ・プロンプトで検索...」に更新(EN/JA/ZH)
  • ヘルプ helpGallery4 を更新して prompt 検索対応を明記

バルクバーUI整理

  • ドロップダウンプレースホルダ「グループに追加...」→「グループ選択」
  • 「追加」ボタン → 「グループに追加」
  • 「グループから削除」ボタンを「グループに追加」の右隣に新規追加
    • bulkRemoveFromGroup() 関数を追加(POST /wfm/gallery/bulk/group の action:"remove" を使用)
    • removedNImagesFromGroup i18n キー追加(EN/JA/ZH)
  • 「削除」ボタン → 「ファイル削除」(グループ削除との混同を防ぐ)
  • ヘルプ helpGallery6, helpGallery7 を更新

変更ファイル: templates/index.html, static/js/app.js, static/js/i18n.js, static/css/main.css

Feeder サブタブの Gallery mode セクション(h4 見出し・説明文・リスト6項目)が英語のハードコードのままだったのを i18n 対応に変更。

修正(templates/index.html)

  • <h4 id="wfm-help-feeder-imgloop-title"> と <p id="wfm-help-feeder-imgloop-desc"> を追加(Image Loop mode 見出し・説明文を i18n 化)
  • <h4 id="wfm-help-feeder-gal-title"> と <p id="wfm-help-feeder-gal-desc"> を追加
  • Gallery mode 6項目に id="wfm-help-feeder-gal-1" 〜 id="wfm-help-feeder-gal-6" を付与

修正(static/js/app.js)

  • helpIdMap に helpFeederImgloopTitle / helpFeederImgloopDesc / helpFeederGalTitle / helpFeederGalDesc / helpFeederGal1〜helpFeederGal6 を追加

修正(static/js/i18n.js)

  • helpFeederDesc: 「2モード切り替え」の説明に EN/JA/ZH で更新
  • helpFeederImgloopTitle / helpFeederImgloopDesc: Image Loop mode 見出し・説明を EN/JA/ZH で追加
  • helpFeederGalTitle / helpFeederGalDesc / helpFeederGal1〜helpFeederGal6: Gallery mode 全項目を EN/JA/ZH で追加
  • helpSearchPlaceholder: 検索ボックスのプレースホルダーを EN/JA/ZH で追加

ヘルプ全体フォントを 1.5 倍に変更

修正(static/css/main.css)

  • .wfm-help-nav-item: 12px → 18px
  • .wfm-help-sidebar 幅: 170px → 220px(フォント拡大に伴う調整)
  • .wfm-help-card h3: 14px → 21px
  • .wfm-help-card h4: 18px(新規追加)
  • .wfm-help-card p: 20px(新規追加)
  • .wfm-help-card li: 13px → 20px
  • .wfm-help-link-title: 15px → 23px
  • .wfm-help-link-desc: 12px → 18px
  • .wfm-help-thanks: 13px → 20px
  • .wfm-help-support-card > p: 14px → 21px

ヘルプタブに検索機能を追加

修正(templates/index.html)

  • サイドバーの .wfm-help-nav 上部に <input id="wfm-help-search"> を追加

修正(static/js/app.js)

  • _onHelpSearch(): 入力テキストで全ヘルプページの textContent を検索。マッチしないナビゲーションボタンは search-hidden クラスで非表示。マッチするページが1つでもあれば最初にマッチしたページを自動表示

修正(static/css/main.css)

  • .wfm-help-search-wrap / .wfm-help-search / .wfm-help-search:focus / .wfm-help-nav-item.search-hidden のスタイルを追加

2026-06-21: Send to Canvas — window.opener経由のキャンバス直接ロード対応

背景: SPAは window.open(url, "_blank") でComfyUIとは別タブとして開かれる。同一オリジンの別タブでは window.opener 経由でComfyUIウィンドウのJavaScriptに直接アクセスできる。従来の「タイトルドラッグ(Send to Canvas)」はSPAウィンドウ→ComfyUIウィンドウのクロスウィンドウDnDだったが、dragover イベント中はブラウザのセキュリティ制限でカスタムMIMEタイプが DataTransfer.types に含まれず e.preventDefault() が呼ばれないため drop イベントがキャンバスに届かなかった(Wタブ内DnDは同一ウィンドウなので app.handleFile が機能していた)。

修正(web/comfyui/node_sets_menu.js)

  • window.wfmReceiveWorkflow(data) グローバル関数を追加: loadDataOnCanvas(data) → app.handleFile(file) 経由でワークフローをキャンバスにロード(UI/API形式どちらも対応)
  • WタブのAPI形式アイテムのグレーアウト(wfm-nlp-item--disabled クラス・draggable=false・title属性)を削除 → 全フォーマットでDnD・ダブルクリック対応

修正(static/js/workflow-tab.js)

  • sendToCanvas(): window.opener.wfmReceiveWorkflow が存在する場合は直接呼び出す(UI/API形式両対応)。存在しない場合(ブックマーク等から直接開いた場合)はlocalStorageフォールバック(UI形式のみ、API形式はエラートーストで案内)
  • サイドパネル・詳細モーダルのSend to CanvasボタンのAPI形式 disabled 属性を削除

修正(static/js/gallery-tab.js)

  • _updateCopyCanvasBtn(): API形式による disabled 制限を削除(workflow存在チェックのみ)
  • Copy & Send Canvasクリックハンドラ: window.opener.wfmReceiveWorkflow 優先に変更(フォールバックはlocalStorage + タイトルドラッグ)

修正(static/js/i18n.js)

  • apiFormatCanvasNoOpener キーを追加(EN/JA/ZH): window.openerなしでAPI形式を送ろうとした場合のエラーガイドメッセージ
  • helpGallery8 / helpSidepanel17 を新しい動作(直接送信・フォールバック説明)に合わせて更新(3言語)

2026-06-20: API形式ワークフローのキャンバス読み込み修正・画像メタデータ埋め込み修正

API形式ワークフローをキャンバスへドラッグすると空になる問題を修正

症状: Workflowタブ「Send to Canvas」やLibraryからのDnDで、API形式(_api.json)のワークフローをComfyUIキャンバスへドロップすると空のグラフになる。ファイルエクスプローラーからのドロップは正常。

原因: app.loadGraphData() はUIフォーマット({nodes:[...], links:[...]})を期待するが、API形式({nodeId: {class_type, inputs}})が渡されていた。

修正(web/comfyui/node_sets_menu.js)

  • isApiWorkflowFormat(data): API形式かUI形式かを判定するヘルパーを追加
  • convertApiToUiWorkflow(api): API形式→UI形式に変換するヘルパーを追加(ノードはグリッド自動配置、リンクタイプは "*"、last_node_id/last_link_id を正しく設定)
  • loadWorkflowOnCanvas: fetchWorkflowRaw 後にAPI形式を検出・変換してから app.loadGraphData() を呼ぶよう修正
  • pendingRaw ドロップハンドラ: Send to Canvas経由のタイトルDnD時にも同様の変換を挿入

修正(static/js/workflow-tab.js)

  • sendToCanvas(): comfyWorkflow.detectFormat でフォーマット判定し、API形式なら comfyWorkflow.convertApiToUi() でUI形式に変換してからlocalStorageに保存

修正(static/js/gallery-tab.js)

  • comfyWorkflow を comfyui-workflow.js からimport追加
  • 「Copy & Send Canvas」ハンドラ: クリップボードにはオリジナルJSON(API形式のまま)をコピーしつつ、localStorage保存前にAPI→UI変換を適用

生成UIタブから生成した画像にワークフローメタデータが含まれない問題を修正

症状: 生成UIタブ(およびFeederタブ)から実行した画像生成で、SaveImageノードが保存するPNGに workflow テキストチャンクが埋め込まれず、GalleryタブでのWorkflow表示や他ツールでのメタデータ読み込みができない。

原因: comfyui-client.js の queuePrompt() が ComfyUI の /prompt エンドポイントに extra_data を渡していなかった。ComfyUIのSaveImageノードは extra_data.extra_pnginfo.workflow を受け取ってPNGの workflow テキストチャンクに埋め込むが、これが未設定のため埋め込みが行われなかった。

修正(static/js/comfyui-client.js)

  • queuePrompt(workflow, extraData = null): extraData 引数を追加。存在する場合に body.extra_data として /prompt リクエストに含める
  • generate(): queuePrompt 呼び出し時に { extra_pnginfo: { workflow } } を渡し、SaveImageノードがワークフロー(API形式)をPNGメタデータとして埋め込むよう修正

2026-06-20: セキュリティ修正(XSS・パスバリデーション)

修正内容

XSS — タグ名がエスケープなしで innerHTML に挿入される(static/js/gallery-tab.js)

  • createTable (L425): tdTags.innerHTML のタグ名を escapeHtml でエスケープ
  • renderTagsDisplay (L605): タグ名と data-tag 属性を escapeHtml でエスケープ
  • 悪意あるタグ名(例: <img onerror=...>)を保存→表示したときに JS が実行される問題

XSS — ファイル名がエスケープなしで innerHTML に挿入される(static/js/gallery-tab.js)

  • loadImageDetail (L566): alt="${img.filename}" を escapeHtml でエスケープ
  • openLightbox (L1175-1176): alt 属性とライトボックスフッターの両方をエスケープ

XSS — エラーメッセージが innerHTML に未エスケープ(static/js/gallery-tab.js)

  • フォルダツリーエラー (L139, L155) と 画像グリッドエラー (L263) を escapeHtml でエスケープ
  • サーバーエラーメッセージにパス名等の特殊文字が含まれる場合の対策

メタデータ書き込みエンドポイントにパスバリデーション追加(py/services/gallery_service.py)

  • save_image_meta, toggle_favorite, add_to_group, remove_from_group に _check_path_allowed() を追加
  • delete/move など他の書き込みエンドポイントには既にあったが、これら4メソッドのみ漏れていた
  • 修正により gallery_metadata.json に許可ルート外のパス文字列が書き込まれるのを防止

list_folder_tree が _allowed_root を任意パスで上書きできる問題(py/routes/gallery_routes.py)

  • gallery_routes.py のモジュールロード時に _init_allowed_root() を呼び出し、保存済み gallery_output_dir(なければ ComfyUI デフォルト output ディレクトリ)で _allowed_root を初期化
  • _allowed_root が起動直後に設定済みになるため、list_folder_tree の if self._allowed_root is None ブランチが発火せず、任意パスによる上書き不可
  • 副次効果として、Gallery タブを一度も開かない状態でも serve_image が正常動作(→ Feeder Gallery モードでの 404 バグ修正に直結)

escapeHtml にシングルクォートのエスケープを追加(static/js/util.js)

  • ' → &#x27; を追加
  • 現在の使用箇所はすべて二重引用符属性のため即時影響はないが、将来の誤用を防止

2026-06-20: WFS_GalleryFeeder — ComfyUI キャンバス上コントロール追加

新規ファイル(web/comfyui/gallery_feeder_extension.js)

app.registerExtension を使い、ComfyUI キャンバス上の WFS_GalleryFeeder ノードに直接コントロールを追加。

  • After Gen コンボ(loop / increment / fixed): serialize: false で prompt には含まれない
  • ▶ Run ボタン: グループ画像を取得 → index ウィジェットを更新 → app.queuePrompt → 完了待ち → 繰り返し
  • ■ Stop ボタン: app.api.interrupt() で現在の生成を中断し、ループフラグをクリア
  • waitForExecution(): executing (null)・execution_interrupted・execution_error の3イベントを購読して1プロンプトの完了を Promise で待つ
  • onRemoved フックでノード削除時にループを自動停止・状態をクリーンアップ

2026-06-20: Feeder グループ保護 + サムネイル F ボタン + seed バグ修正

__Feeder__ グループの予約済み保護

サーバーサイド(py/routes/gallery_routes.py)

  • _RESERVED_GROUPS = {"__Feeder__"} 定数を追加
  • rename_group / delete_group エンドポイントで予約グループへの操作を 403 で拒否

クライアントサイド(static/js/gallery-tab.js)

  • グループ管理ドロップダウンで __Feeder__ を選択したとき、リネーム・削除ボタンを disabled に
  • セレクトオプションに 🔒 プレフィックスを追加して視覚的に区別
  • change イベントリスナーで動的に切り替え

ギャラリーサムネイル「F」オーバーレイボタン(static/js/gallery-tab.js, static/css/gallery-tab.css)

  • モデルタブの「B」バッチ登録ボタンと同様の位置(カード左上)に「F」ボタンを追加
  • inFeeder 初期状態は img.groups 配列から判定(list_images() が返す groups フィールドを使用)
  • クリックで __Feeder__ グループへの追加 / 除外をトグル(APIコール + in-memory キャッシュ更新)
  • アクティブ時はシアン色 (#38bdf8)、ホバー時のみ表示(active 時は常時表示)

i18n(static/js/i18n.js)

  • 英/日/中に addedToFeeder / removedFromFeeder キーを追加

seed バリデーションエラー修正(py/nodes/gallery_feeder_node.py)

症状: Feeder run 実行時に "Prompt outputs failed validation" エラーが発生

原因: comfyui-client.js の applySeedToWorkflow() がグラフ内の全ノードの seed を Number.MAX_SAFE_INTEGER(約 9×10¹⁵)以下のランダム値に上書きする。WFS_GalleryFeeder が宣言していた max: 0x7FFFFFFF(約 2.1×10⁹)を大幅に超えるため、ComfyUI の validate_prompt() が value_bigger_than_max でリジェクト。

修正: seed の max を 0x7FFFFFFF → 0xffffffffffffffff(KSampler と同値)に変更。


概要

Feeder タブにギャラリー連携モードを追加。外部プラグイン(comfyui-image-feeder)に依存せず、本プラグイン単独でギャラリーグループの画像を連続生成ループに利用できるようになった。

新規ノード(py/nodes/gallery_feeder_node.py)

  • WFS_GalleryFeeder(Workflow Studio カテゴリ)
    • 入力: group_name (STRING), index (INT), sort_mode (COMBO: filename_asc/filename_desc/random), seed (INT)
    • 出力: IMAGE
    • gallery_metadata.json を直接読み込み、指定グループの画像を index % len(images) で1枚出力
    • ファイルの存在チェックつき(削除済み画像はスキップ)
    • IS_CHANGED で毎回実行(インデックスが変わるためキャッシュ無効化)
    • データファイルの場所は Path(__file__) ベースで自動解決(ComfyUI のパス構成に依存しない)

ノード登録(__init__.py)

  • WFS_GalleryFeeder を _NODE_MODULES に追加("Gallery Feeder (WFS)" として表示)
  • あわせて既存の WFS_PromptText のディスプレイ名がハードコードされていたバグを修正(_NODE_MODULES にディスプレイ名を持たせる構造に変更)

Feeder グループ管理(py/services/gallery_metadata.py, py/services/gallery_service.py)

  • clear_group(group_name): グループ内の全画像を除外(グループ自体は残す)
  • ensure_group(name): グループが存在しない場合のみ作成

API エンドポイント(py/routes/gallery_routes.py)

  • POST /wfm/gallery/groups/{name}/clear — グループの全画像を除外(FC ボタン用)
  • POST /wfm/gallery/groups/ensure — グループが存在しない場合のみ作成(Feeder タブ初期化時に __Feeder__ を自動作成)

ギャラリータブ FC ボタン(static/js/gallery-tab.js, templates/index.html)

  • FEEDER_GROUP = "__Feeder__" 定数を export
  • ensureFeederGroup() を export(Feeder タブ初期化時に呼び出す)
  • clearFeederGroup(): /wfm/gallery/groups/__Feeder__/clear を呼び出してグループをクリア
  • ギャラリーツールバーのグループフィルタ横に [FC] ボタンを追加(BC/SC ボタンと同様の位置)
  • API 定数に groupEnsure, groupClear, groupImages を追加
  • 左ペイン上部に [Image Loop] / [Gallery] のモード切り替えボタンを追加(localStorage に保存)
  • Image Loop モード(既存機能): 変更なし
  • Gallery モード(新機能):
    • 左ペイン: WFS_GalleryFeeder ノード選択・Apply・グループ選択・Sort Mode / Index / Seed・After gen (Loop/Increment/Fixed)・Run/Stop
    • 中央ペイン: 選択グループの画像グリッド(/wfm/gallery/image/serve でサムネイル表示)。クリックで Index を更新
    • 右ペイン: プレビュー(既存と共通)
    • Run ループ: JS 側でインデックスを管理(WebSocket sync 不要)。Increment モードで末尾到達時に自動停止
    • タブ初期化時に ensureFeederGroup() を呼び出して __Feeder__ グループを自動作成

i18n(static/js/i18n.js)

  • 英・日・中に3キー追加: feederGalNoNode, feederGalEmptyGroup, feederGroupCleared

2026-06-19: Generate UI — ImpactWildcardEncode/Processor プロンプト検出バグ修正(v0.3.41)

修正内容(static/js/comfyui-workflow.js)

症状:comfy-impact-pack の ImpactWildcardEncode / ImpactWildcardProcessor ノードを含むワークフローを Generate UI に読み込んだとき、Prompt タブにプロンプトが表示されない(ID は正しく表示されていた)。

原因1: CLIPTextEncodeEditPlus 経由の BFS ロール伝播漏れ

  • これらのワークフローでは ImpactWildcard ノードの出力が CLIPTextEncodeEditPlus の text2 入力に接続され、CLIPTextEncodeEditPlus が KSampler の positive に繋がる構成。
  • BFS 伝播で CLIPTextEncodeEditPlus の STRING 入力(text1, text2)に positive/negative ロールを伝播するコードが未実装だったため、上流の ImpactWildcard ノードのロールが unknown のままになっていた。
  • 修正: BFS ループに CLIPTextEncodeEditPlus の text1 / text2 への伝播を追加。

原因2: CLIPTextEncodeEditPlus が空の text_edit で prompt_nodes の先頭に入り込む

  • CLIPTextEncodeEditPlus 自体も positive ロールとして検出され、text_edit = "" でノードIDが小さいため positiveNodes[0] になっていた。
  • これによりテキストエリアに空文字が表示され、ImpactWildcard ノードのプロンプトが埋もれていた。
  • 修正: text_edit が空文字列の場合は prompt_nodes に追加しないよう条件を追加(textVal !== "")。空の text_edit は「上流ノードからプロンプトが来る場合のデフォルト状態」であり、UI で表示する必要がない。

原因3: object_info マッピングの不確実性

  • convertUiToApi() で impact-pack バージョンによる object_info のウィジェット順序差異が発生した場合、wildcard_text が正しくマッピングされない可能性があった。
  • 修正1: ポストプロセスで widgets[0] を常に wildcard_text として上書き(!("wildcard_text" in inputs) ガードを除去)。
  • 修正2: _getWidgetMapping() フォールバックに ImpactWildcardProcessor: ["wildcard_text"]、ImpactWildcardEncode: ["wildcard_text"] を追加(ComfyUI オフライン時の対応)。

2026-06-19: AI TOOL タブ Chat ペイン・ワイルドカード生成追加(v0.3.40)

Chat ペイン(templates/index.html, static/css/main.css, static/js/ai-tab.js, web/comfyui/node_sets_menu.js)

  • AI TOOL タブを 3ペイン → 4ペイン(Translation | Chat | TOOLS | Settings)に変更
  • Chat ペイン: LLM とのマルチターン会話;会話履歴を毎回送信;Enter で送信、Shift+Enter で改行;Clear ボタンで履歴リセット
  • Ollama: /api/chat エンドポイント(messages 配列)、LM Studio: /v1/chat/completions(同形式)
  • 送信失敗時は入力テキストを復元してリトライ可能
  • サイドパネル Aタブにも Chat サブタブを追加(Translation → Chat → TOOLS → Settings の4タブ構成)
  • CSS: .wfm-ai-chat-* / .wfm-nlp-ai-chat-* でユーザーバブル(右)・アシスタントバブル(左)スタイル追加

ワイルドカード生成(templates/index.html, static/css/main.css, static/js/ai-tab.js, web/comfyui/node_sets_menu.js)

  • TOOLS ペインのドロップダウンに「Create wildcards」オプションを追加
  • タスク切替で UI を動的切り替え:VLM選択時→ドロップゾーン表示、Wildcard選択時→Name・Count入力フォームを表示
  • プロンプト: Generate ${count} wildcard entries for the category "${name}". Output only plain text in English, one entry per line, no numbers, no markdown, no asterisks, no bold, nothing else.
  • 後処理: ** / 行頭 * / 行頭数字(\d+\.\s*)を除去してプレーンテキストを保証
  • SPA(static/js/ai-tab.js)とサイドパネル(web/comfyui/node_sets_menu.js)両方に実装

ヘルプ・i18n(static/js/i18n.js, static/js/app.js, templates/index.html)

  • AI TOOL タブヘルプを5項目→6項目に再構成(ai-3: Chat、ai-4: VLM、ai-5: Wildcards、ai-6: 設定)
  • helpSidepanel16 を EN/JA/ZH 更新(Chat・ワイルドカード機能を追記)
  • helpAi1〜helpAi6 を EN/JA/ZH 全更新
  • 新 i18n キー追加(3言語): aiStatusChatting, aiToastChatFailed, aiToastWcNoName

2026-06-18: Send to Canvas機能追加(ワークフロータブ・ギャラリータブ→LibraryタイトルDnD)

ワークフロータブ — Send to Canvas(static/js/workflow-tab.js, static/js/app.js, templates/index.html)

  • ツールバーおよび詳細モーダルの Open in ComfyUI ボタンを Send to Canvas に変更
  • sendToCanvas(workflowData): ワークフローJSONを localStorage["wfm_pending_workflow"] に保存してトースト表示(新しいブラウザタブを開かない)
  • openInComfyUI() 関数は後方互換のため残置

ギャラリータブ — Copy & Send Canvas(static/js/gallery-tab.js, templates/index.html)

  • Copy Workflow ボタンを Copy & Send Canvas にリネーム
  • クリック時にクリップボードコピーと localStorage["wfm_pending_workflow"] への保存を同時実行

Workflow Studio Library — タイトルドラッグ(web/comfyui/node_sets_menu.js)

  • createPanel() 内でタイトル要素(.wfm-nlp-title)にドラッグロジックを追加
    • updateTitlePendingState(): localStorage の wfm_pending_workflow 有無を検出して draggable 属性・CSS クラス・title 属性を動的切り替え
    • panel.mouseenter / window.storage イベントで状態を自動更新(メインアプリ→ComfyUI切り替え後も検出)
    • dragstart: pending JSON を application/x-wfm-pending MIME タイプにセット
  • installCanvasDropHandler(): application/x-wfm-pending を dragover と drop の両ハンドラに追加
    • drop 時に JSON.parse → app.loadGraphData() → localStorage削除 → タイトルリセット
  • CSS: .wfm-nlp-title-pending { color: #66aaff; cursor: grab } + ::after で緑●インジケーター表示

i18n・ヘルプ(static/js/i18n.js, static/js/app.js, templates/index.html)

  • sendToCanvas / copyAndSendCanvas / workflowSentToCanvas を EN/JA/ZH に追加
  • ヘルプ: helpWf6(send to canvas)・helpGallery8(Copy & Send Canvas説明)を3言語更新
  • ヘルプ: helpSidepanel17 新規追加(タイトルドラッグ機能説明)を3言語で追加、app.jsマッピング・index.html li要素も追加

2026-06-15: TaggerシングルGenUI:Pボタン追加+生成UIのZITワークフロープロンプト解析修正

TaggerシングルGenUI:Pボタン(templates/index.html, static/js/tagger-tab.js, static/js/i18n.js)

  • Single タブの結果アクション行に GenUI:P ボタンを追加(「プロンプトに送信」の左隣)
  • クリックすると生成UIタブの #wfm-prompt-pos-text(ポジティブプロンプトtextarea)末尾にタグを追記し、#wfm-prompt-pos-apply を自動クリックしてワークフローへ即時反映
  • 生成UIにワークフローが読み込まれていない場合は警告トーストを表示
  • i18n: taggerSendToGenUI / taggerSentToGenUI / taggerNoGenUI を EN/JA/ZH に追加
  • ヘルプ: helpTagger6 を5項目に更新(GenUI:P を先頭に追加)

生成UIのZIT/Lumina2ワークフロープロンプト解析修正(static/js/comfyui-workflow.js, py/services/workflow_analyzer.py)

  • comfyui-workflow.js の COND_PASSTHROUGH から "ConditioningZeroOut" を除外
    • ZITワークフローでは KSampler.negative → ConditioningZeroOut → CLIPTextEncode という接続を持つが、ConditioningZeroOut は上流テキストを完全に破棄するため negative ロールを上流に伝播すべきでない
    • 修正前: CLIPTextEncode が positive / negative 両ロール付与 → getRole() が "unknown" → Generate UI のプロンプト欄に表示されない
    • 修正後: CLIPTextEncode は positive ロールのみ → 正常にポジティブプロンプトとして表示
  • workflow_analyzer.py の _CLIP_TYPE_TO_MODEL に "lumina2": "Z-IMAGE" を追加
    • CLIPLoader の type フィールドが "lumina2" のワークフローを Z-IMAGE として正確に分類

2026-06-15: Taggerバッチ.txt出力追加+Galleryタグ保存バグ修正

Taggerバッチ.txt出力(templates/index.html, static/js/tagger-tab.js, static/js/i18n.js, py/routes/tagger_routes.py, py/services/tagger_service.py)

  • バッチ出力オプションに Write .txt チェックボックスを追加
  • 有効にすると処理した各画像と同一フォルダに <ファイル名>.txt(UTF-8)を生成、Interrogator + VLM タグのカンマ区切り文字列を書き込む
  • batch_start() シグネチャに write_txt: bool パラメータを追加、ルート・フロントエンドも連動更新
  • i18n: taggerBatchWriteTxt キーを EN/JA/ZH に追加

Galleryタグ保存バグ修正(static/js/tagger-tab.js)

  • _saveToGallery() がタグをカンマ区切り文字列のまま POST していたため、Gallery が img.tags.forEach is not a function エラーで更新不能になっていた
  • 修正: 送信前に tags.split(",").map(s => s.trim()).filter(Boolean) で配列に変換

2026-06-15: Taggerタブ新規実装+ギャラリー詳細パネルUI改善+パストラバーサル修正

Taggerタブ新規実装

バックエンド(py/config.py, py/services/tagger_service.py, py/services/tagger_db_service.py, py/routes/tagger_routes.py, py/wfm.py)

  • py/config.py: TAGGER_DB_FILE / TAGGER_SETTINGS_FILE / TAGGER_MODELS_DIR(ComfyUI/models/tagger/)定数追加
  • TaggerService: WD Tagger(ONNX)・SwinV2・DeepDanbooru(.h5 / TensorFlow optional)推論、Ollama VLM連携(/api/chat)、JPEGへのpiexif EXIF書込・PNGへのPngInfo書込・サイドカー.tags.json書込、スレッドベースバッチ処理(batch_start / batch_stop / batch_status)、設定の永続化
  • TaggerDbService: SQLite(tagger.db)によるタグ保存・一覧・検索・更新・削除・CSV出力
  • tagger_routes.py: /wfm/tagger/ 配下に17エンドポイントを登録(models・predict・ollama/models・ollama/predict・batch/start/status/stop・db CRUD・write_meta・settings)、全CPU処理を asyncio.to_thread() でラップ
  • py/wfm.py: tagger_routes.setup_routes(app) を追加
  • requirements.txt 新規作成: onnxruntime>=1.16・piexif>=1.1.3 を必須、TensorFlow をオプション(コメントアウト)として記載

フロントエンド(templates/index.html, static/css/tagger-tab.css, static/js/tagger-tab.js, static/js/i18n.js, static/js/app.js)

  • templates/index.html: Taggerタブナビボタン追加、<section id="wfm-tab-tagger"> を追加(Single / Batch / DBの3サブタブ構成)
  • static/css/tagger-tab.css 新規作成: サブタブナビ・シングルレイアウト・プレビューエリア(破線ボーダー+ドラッグオーバーハイライト)・バッチ進行状況バー・DBテーブルのスタイル
  • static/js/tagger-tab.js 新規作成:
    • initTaggerTab(): i18n適用・サブタブ切り替え・スライダー連動・Ollamaトグル・各種イベント登録・モデル一覧ロード・設定読み込み
    • openImageInTaggerTab(img): Gallery→Taggerタブ遷移+画像ロード(/wfm/gallery/image/serve 経由でbase64変換)
    • Single: ドラッグ&ドロップ・ファイルアップロード・タグ生成(WD Tagger+Ollama並行)・4出力先(Prompt送信・Gallery保存・ファイル書込・DB保存)
    • Batch: バッチ開始/停止・1秒間隔ポーリングによるリアルタイム進行状況更新
    • DB: 一覧表示・検索・行選択編集・保存・削除・CSV出力
  • static/js/i18n.js: tabTagger・Taggerタブ関連キー約50件を EN/JA/ZH 3言語で追加
  • static/js/app.js: tabMap に tagger 追加、initTaggerTab() インポート&呼び出し追加

ギャラリー詳細パネルUI改善(templates/index.html, static/css/gallery-tab.css, static/js/gallery-tab.js, static/js/app.js)

  • タブ行(Info / JSON / Groups)とアクションボタン行(Metadata / Load GenUI / Tagger)を分離: タブは wfm-side-tab-nav、ボタンは新設の wfm-gallery-detail-action-row へ移動
  • .wfm-gallery-detail-action-row のCSSを gallery-tab.css に追加(flex・flex-wrap・padding)
  • Galleryの詳細パネルに Tagger ボタン(wfm-gallery-open-tagger-btn、紫)追加: クリックで選択画像を openImageInTaggerTab() へ渡してTaggerタブへ遷移・画像ロード
  • .wfm-gallery-action-btn-tagger スタイル(gallery-tab.css)追加
  • app.js の applyI18nToHtml() でTaggerボタンのテキストを t("tabTagger") に設定

セキュリティ修正: パストラバーサル対策(py/services/tagger_service.py, py/services/gallery_service.py)

  • TaggerService._validate_model_name(): モデル名に / \ .. \x00 が含まれる場合はロード拒否(mdir / model_name のパストラバーサルを防止)
  • TaggerService.write_meta_to_file(): ユーザー指定パスを resolve() した上で許可拡張子(.jpg/.jpeg/.png/.webp/.bmp/.gif)のみ処理、それ以外はエラー返却
  • GalleryService._check_path_allowed(): _allowed_root is None のときに tautology(resolved == path.resolve()、常にTrue)だった判定を return False に修正(list_folder_tree 呼び出し前に全パスが通る問題を解消)

ヘルプ更新(templates/index.html, static/js/i18n.js, static/js/app.js)

  • ヘルプナビに「Tagger Tab」追加
  • Taggerタブのヘルプページ新設(9項目: モデル配置・Single操作・閾値・Ollama・出力先・Batch・DB・インストール手順)EN/JA/ZH
  • Galleryタブヘルプに helpGallery14(Taggerボタン説明)を EN/JA/ZH で追加
  • トラブルシューティングに helpTrouble7(Taggerモデル未表示時の対処)を EN/JA/ZH で追加

2026-06-15: 全選択ボタン追加+ギャラリーバルクバーi18n対応

変更内容

Modelsタブ: 複数選択バーに「全選択」ボタン追加(static/js/models-tab.js, static/js/i18n.js)

  • selectAll() 関数を追加: filterModels() の結果(フィルター・検索適用後の表示中モデル)を state.selectedModels に一括追加して renderModelGrid() / renderBulkActionBar() を呼び出す
  • renderBulkActionBar() のバーHTMLに wfm-bulk-select-all-btn を Deselect All ボタンの右隣に追加
  • イベントリスナーを追加("wfm-bulk-select-all-btn" → selectAll)
  • i18n: modelBulkSelectAll を英語 "Select All" / 日本語 "全選択" / 中国語 "全选" で追加

Galleryタブ: 複数選択バーに「全選択」ボタン追加+全ボタンi18n対応(templates/index.html, static/js/gallery-tab.js, static/js/app.js, static/js/i18n.js)

  • wfm-gallery-bulk-select-all ボタンを Deselect All ボタンの右隣に追加(templates/index.html)
  • クリックリスナー: state.images の全パスを state.selectedImages に追加し renderImages() / updateBulkBar() を呼び出す
  • バルクバーの全ボタン・セレクトのテキストを空にして applyI18nToHtml() 経由で t() 設定に変更(ハードコード英語文字列を排除)
  • updateBulkBar() の選択件数表示を t("galleryBulkSelected") でi18n化("${count} selected" → "${count} ${t(...)}" )
  • グループセレクトの初期オプションを t("galleryBulkAddToGroup") でi18n化
  • app.js の applyI18nToHtml() にギャラリーバルクバー全要素のi18n設定処理を追加
  • i18n: 英語 / 日本語 / 中国語の3言語でギャラリーバルクバー用キー10個を新規追加: galleryBulkSelected / galleryBulkDeselectAll / galleryBulkSelectAll / galleryBulkAddToGroup / galleryBulkAdd / galleryBulkFavAll / galleryBulkUnfavAll / galleryBulkMoveTo / galleryBulkDelete

2026-06-10: v0.3.36 — コードレビュー修正+リファクタリング+README刷新

コードレビュー修正(v0.3.35の不具合10件)

ヘルプi18n配線の修正(static/js/i18n.js, static/js/app.js, templates/index.html)

  • バグ: applyI18nToHtml() が起動時に全言語でヘルプ文を上書きするため、v0.3.35でindex.htmlに追記したヘルプ更新(Saveボタン・ソート・JSONタブ改名)が一切表示されなかった
  • helpGen2 / helpModels2 / helpGallery8 / helpGallery10 を3言語ともindex.htmlの新文言に同期
  • helpGallery12 / helpGallery13 キーを3言語で新規追加(Metadata / Load GenUI ボタン説明)
  • helpIdMap に wfm-help-models-12 / wfm-help-trouble-6 / wfm-help-gallery-12 / wfm-help-gallery-13 を登録
  • index.html の「Thumbnail, Card, and Table」誤記を修正(カードビューはv0.3.22で廃止済み)

GenerateUI Save の安全化(static/js/generate-tab.js)

  • 上書き確認: 保存前に GET /api/wfm/workflows で既存ファイルを照会し、同名があれば confirm 表示。保存先が UI形式(analysis.format === "ui")の場合はAPI形式上書きでノード配置が失われる旨の専用警告
  • Raw JSON同期: Raw JSONテキストエリアに未Applyの編集があればパースして保存対象に反映し、保存後エディタへ同期。不正JSONはエラーで中断(従来は古い内容を黙って保存していた)
  • HTML注入防止: ファイル名を value="${...}" 属性埋め込みからDOM経由の input.value 設定に変更
  • デフォルト名: 拡張子除去を .json のみ→画像拡張子(.png/.jpg/.jpeg/.webp/.gif)にも拡大
  • 二重送信ガード: saving フラグでEnter連打・多重POSTを防止

その他

  • sortModels を decorate-sort-undecorate 方式に変更(sortKeyOf() で1モデル1回だけキー計算。比較ごとの parseModelPath 再計算を解消)(static/js/models-tab.js)
  • ギャラリーの Metadata / Load GenUI ボタンを .wfm-gallery-detail-tab-btn から専用の .wfm-gallery-action-btn に分離し、CSSの !important 16箇所を全廃(templates/index.html, static/css/gallery-tab.css)
  • Save関連・ギャラリーの新規文字列をi18n化(saveWorkflowTitle / savedAs / overwriteConfirm / gallerySelectImageFirst 等を3言語追加)

リファクタリング

util.js 新設(static/js/util.js 新規)

  • escapeHtml(): 5ファイル(gallery / metadata / models / nodes / workflow)の重複定義を統一。metadata-tab版は " エスケープが抜けており属性値でHTML注入の恐れがあった(修正済み)
  • readJsonStorage(key, fallback) / getSettings(): JSON.parse(localStorage.getItem(...)) の直書き15箇所(8ファイル)を置換。try/catchの重複も削減

トーストi18n化(全タブJS, static/js/i18n.js)

  • ハードコード英語トースト約115箇所をすべて t() 化(残り0件)
  • 共通キー約60個を3言語(EN/JA/ZH)で追加: errorWithMsg / groupCreated / presetSavedName / batchNoneSelected / generationComplete など
  • 既存キーとの衝突回避: 文字列型の既存 importError / presetSaved と重複したため、新関数型キーは importErrorMsg / presetSavedName に命名
  • 既存バグ修正: Settings画面の textSizeLabel / jsonColor* 系11キーが未定義で生キー名が表示されていた(v0.3.14由来。t() || "fallback" はt()がキー名を返すため機能しない)→ 3言語で定義追加
  • ZHブロックに欠落していた copyPositivePrompt / copyNegativePrompt / copiedToClipboard / noTextToCopy を追加
  • 検証: 全 t() 呼び出しキーの定義確認・3言語のキー完全一致・重複キーなし・全JS構文チェック通過

README刷新(README.md, pyproject.toml, docs/)

  • 冒頭説明文を3本柱構成に書き換え: 📁 Management(ワークフロー/モデル/画像/プロンプト管理、AIプロンプト支援・翻訳・タグ生成、ファイルドロップ&ギャラリー連携メタデータ)/ ⚡ GenerateUI(全タブ連携、モデル・サンプラー等バッチ生成、Image Feeder)/ 📚 Workflow Studio Library(キャンバスドロップ、画像/JSONメタデータ表示&ドロップ、AIツール)
  • スクリーンショット差し替え・追加: 6_ws_library.png 差し替え、9_GenUI_LoraStack.png(GenUI LoRA Stack)・10_GenUI_Batch.png(GenUI Batch)・11_multiple_select_menu.png(Models Multi-select Menu)を新規追加し、生成UI系3枚が連続するよう表を再配置
  • pyproject.toml の description をREADME新説明文と整合する内容に更新

2026-06-10: v0.3.35 — テーブルソート・Load GenUI・ワークフロー保存

変更内容

Modelsタブ テーブルビュー: 列ヘッダークリックでソート(static/js/models-tab.js, static/css/main.css)

  • state に sortColumn(null または列キー)と sortDir("asc" / "desc")を追加
  • sortModels(models) 関数を追加:ソート列に応じて ★・ファイル名・サブディレクトリ・Type・Base Model・拡張子・Tags・Memo・E/D の各キーで昇降順ソートを実行
  • filterModels() の末尾で sortModels() を呼び出し、フィルタ後の全ビューに適用
  • thSortHtml(label, col, extraClass, extraStyle) ヘルパーを追加:アクティブ列はアクセントカラー(--wfm-accent)に変色し ▲/▼ 矢印を表示
  • renderTableView のヘッダーをすべて thSortHtml ベースに変更。クリックで asc → desc → 解除のサイクル
  • Enable/Disable 列の空ヘッダーを E/D に変更
  • .wfm-table-th-sortable CSS を追加(cursor: pointer、-webkit-user-select: none、white-space: nowrap、ホバー時アクセントカラー)

ギャラリータブ: Load GenUI ボタン追加(templates/index.html, static/js/gallery-tab.js, static/css/gallery-tab.css)

  • 詳細パネルのタブナビに Load GenUI(青)ボタン(wfm-gallery-load-genui-btn)を Metadata ボタンの右隣に追加
  • gallery-tab.js に loadWorkflowIntoEditor を generate-tab.js からインポート
  • クリック時の動作:画像未選択 → warning トースト / ワークフロー未埋め込み → warning トースト / 非対応フォーマット → loadWorkflowIntoEditor 内でトースト表示 / 成功時 → GenerateUI タブへ自動切り替え
  • Metadata ボタンに wfm-gallery-action-btn-green クラスを追加し緑色背景に変更
  • gallery-tab.css に .wfm-gallery-action-btn-green(緑)・.wfm-gallery-action-btn-primary(--wfm-primary 青)スタイルを追加:padding・border-radius・align-self: center・margin を共通指定

GenerateUIタブ: Save ボタン追加(templates/index.html, static/js/generate-tab.js)

  • wfm-gen-subtab-nav の右端(margin-left:auto)に Save ボタン(wfm-gen-save-btn、wfm-btn-primary)を設置
  • generate-tab.js のインポートに openModal・closeModal を追加
  • saveCurrentWorkflow() 関数を追加:
    • ワークフロー未ロード時は warning トースト
    • モーダルでファイル名を入力(デフォルト: 現在のワークフロー名)。Enter キーでも確定可能
    • comfyUI.currentWorkflow を JSON シリアライズして File オブジェクトを生成、POST /api/wfm/workflows/import で Workflow タブに保存
    • 保存成功後 wfm-gen-wf-name の表示・dataset.filename を新ファイル名に更新
    • エラー時はトーストでメッセージ表示

ヘルプ更新(templates/index.html)

  • GenerateUI Tab(wfm-help-gen-2):Save ボタンの説明を追加
  • Gallery Tab(wfm-help-gallery-8/10):「Metadata tab」→「JSON tab」に表記修正;wfm-help-gallery-12/13 として Metadata(緑)・Load GenUI(青)ボタンの説明を新規追加
  • Models Tab(wfm-help-models-2):テーブルヘッダークリックソートと E/D 列の説明を追記

2026-06-09: v0.3.34 — Modelsタブ テーブルビュー強化・バルク操作改善

変更内容

テーブルビュー: Type / Base Model 列追加(static/js/models-tab.js, static/css/main.css)

  • renderTableView にて Subdirectory と Extension 列の間に Type / Base Model 列を追加
  • データは state.civitaiCache[sha256] から取得(追加API呼び出しなし)
  • .wfm-table-th-civtype / .wfm-table-td-civtype(70px)、.wfm-table-th-basemodel / .wfm-table-td-basemodel(110px)を追加
  • .wfm-table-th-filename / .wfm-table-td-filename の max-width を 160px に制限し、overflow ellipsis 適用

モーダル: Delete ボタン追加(static/js/models-tab.js)

  • 詳細モーダルにモデル削除ボタンを設置
  • POST /api/wfm/models/delete を呼び出してモデルファイル本体および全サイドカーファイル(.preview.png、.json、.civitai.info、.metadata.json、.cm-info.json など)を削除
  • 削除後にローカルステート(modelsByType、modelMetadata、disabledModels、selectedModels)を差分更新して renderModelGrid() を呼び出す

バルクアクションバー: ファイル移動機能追加(static/js/models-tab.js, static/css/main.css)

  • fetchSubdirs() 関数追加:GET /api/wfm/models/subdirs?type= でルート直下のサブフォルダ一覧を取得し state.subdirs にキャッシュ
  • bulkMoveModels(destSubdir) 関数追加:POST /api/wfm/models/move で選択モデルをサブフォルダへ移動(サイドカーファイルも同時移動)。ローカルステートのキー(modelsByType、modelMetadata、disabledModels)を移動後の相対パスに更新
  • 移動後に fetchSubdirs() → renderDirFilter() → renderModelGrid() → renderBulkActionBar() を連続実行

バルクアクションバー: UI再構成(static/js/models-tab.js, static/css/main.css)

  • バーを ヘッダー行(件数表示・Deselect All・Favorite 操作)+3行グリッド(Group / Badge / File)に再構成
  • Group 行:グループ選択ドロップダウン・Add / Remove ボタン・新規グループ名入力・Create & Add
  • Badge 行:バッジ選択ドロップダウン・+Badge / −Badge ボタン
  • File 行:移動先フォルダ選択ドロップダウン・Move ボタン・新規フォルダ名入力・Create & Move・Delete Files(行右端)
  • Select ボタンを緑色(.wfm-btn-success)に変更(templates/index.html)
  • X(Clear Selection)ボタンを削除。Deselect All のみを使用
  • 各ドロップダウン幅を 110px → 330px に拡大

バックエンド: subdirs / move エンドポイント追加(py/services/models_service.py, py/routes/models_routes.py)

  • ModelsService.get_subdirs(model_type) — ルート直下のディレクトリ名一覧を返す
  • ModelsService.move_models(model_type, model_names, dest_subdir) — モデルと全サイドカーを shutil.move で移動。パストラバーサル防止(.. チェック・セパレーター禁止・絶対パス禁止・resolve().relative_to() containment検証)・上書き防止を実装
  • GET /api/wfm/models/subdirs、POST /api/wfm/models/move ルートを登録。_VALID_MODEL_TYPES frozenset でモデルタイプをホワイトリスト検証
  • _SIDECAR_EXTENSIONS に .metadata.json、.cm-info.json を追加(delete / move 両操作に反映)

B/S ボタン: 行移動バグ修正(static/js/models-tab.js)

  • toggleBatch / toggleStack から renderModelGrid() 呼び出しを削除
  • テーブルビュー・カードビュー両方の B / S ボタンハンドラーを async に変更し、await 後にボタンの active クラスのみを差分更新(classList.toggle)
  • state.showBatchOnly フィルター有効時(バッチから外すと行が消えるべき場合)のみ renderModelGrid() を呼び出す

ギャラリー → Metadata タブ連携(static/js/gallery-tab.js, static/js/metadata-tab.js)

  • metadata-tab.js に loadFileIntoMetadataTab(file) をエクスポート:Metadata タブへ切り替えてファイルを読み込む外部 API
  • gallery-tab.js に openImageInMetadataTab(img) を追加:画像を fetch してバイナリを Metadata タブに渡す
  • ギャラリー画像カード(サムネイル・テーブル両ビュー)で Alt+クリック すると Metadata タブで開く
  • 詳細パネルに Metadata ボタン を追加(index.html の wfm-gallery-open-metadata-btn):選択中の画像を Metadata タブで開く
  • 詳細タブ切り替えロジックを修正(data-detail-tab を持つボタンのみ active/display を切り替え、Metadata ボタンを除外)

ヘルプ更新(templates/index.html)

  • Models Tab ヘルプ(wfm-help-models-2、-6、-7、-11)を今回の変更に合わせて更新

2026-06-09: v0.3.33 — LoRAペイン・ワークフロー解析バグ修正

変更内容

LoRAペイン:ワークフロー読み込み時のLoraManager上書き防止(static/js/comfyui-editor.js)

  • renderLoraPane 末尾にあった「Auto-apply Stack to LoraManager on load」ブロックを削除
  • ワークフロー読み込み時にModelタブのStackグループ内容が inputs.loras / inputs.text を上書きし、RAW JSONが変わるバグを修正
  • Apply ボタン押下時のみStackがLoraManagerに書き込まれる正しい動作に

LoRAペイン:🔄ボタン押下でSingleタブに戻るバグ修正(static/js/comfyui-editor.js)

  • renderLoraPane 呼び出し前に現在のアクティブタブを _prevActiveTab に保存
  • el.innerHTML 再構築後にタブ状態(activeクラス・display)を復元する処理を追加
  • 🔄ボタン・モデルタブ切り替えによる再レンダリング後もStackタブに留まるよう修正

ModelsタブCheckpointへのStackグループ非表示(static/js/models-tab.js)

  • loadModelsForCurrentType のグループロード後、STACK_MODEL_TYPES(lora のみ)に含まれないタイプでは state.modelGroups["Stack"] をメモリ上から削除
  • Checkpointなどのタイプでグループフィルターおよびサイドパネルのグループ管理にStackが表示されなくなった

CLIPTextEncodeEditPlus ウィジェット値マッピング修正(static/js/comfyui-workflow.js)

  • convertUiToApi でUIスロット入力にリンクが接続されている場合(linkedSlotNames)、widgets_values のインデックスを進めないよう変更
  • 従来は object_info の STRING 型オプション入力(text1 等)がウィジェット名リストに含まれ、リンクあり判定でインデックスがズレていた
  • widgets_values: ["girl", "+af", ""] で text_edit="girl" / mode="+af" が正しくマッピングされるよう修正

Stack Apply時のTrigger Words処理修正(static/js/comfyui-editor.js)

  • Applyクリックハンドラー内でトリガーワードをレンダリング時のスナップショット変数から取得していたため、チェックボックス変更が反映されないバグを修正
  • currentAllTriggers / currentActiveTriggers を Apply 時点で _stackActive + metadata + civitaiCache から動的に再計算するよう変更
  • 修正前の不具合:1回目ApplyはLORA SYNTAXのみ(TRIGGER WORDSなし)、2回目で追加される / モデル無効でApply後もTRIGGER WORDSが残る

2026-06-08: v0.3.32 — Lora Loader (LoraManager) LoRA検出対応・ライブラリ機能拡張

変更内容

Lora Loader (LoraManager) LoRA検出対応(web/comfyui/node_sets_menu.js, static/js/metadata-tab.js)

  • _extractLoRAs / extractLoRAs の add 関数を parseFloat ベースに変更。LoraManager ノードは strength / clipStrength を文字列 ("0.20") で保存するため typeof === "number" では 1.0 にフォールバックしていたバグを修正
  • APIフォーマット(class_type ベース)の else 節に Lora Loader (LoraManager) を追加。inputs.loras.__value__ からLoRAリストを取得(Array.isArray(inputs.loras) をフォールバックとして併用)
  • Iタブ(ライブラリ)・Metadataタブの両方に同様の修正を適用

placeLoraMgrNode textウィジェット同期(web/comfyui/node_sets_menu.js)

  • LoraManagerノード配置時に loras ウィジェット(配列)だけでなく text ウィジェット(LoRA構文)も明示的に更新するよう修正
  • strength = clipStrength の場合 <lora:name:s>、異なる場合 <lora:name:s:c> の形式で構文を生成
  • l.strength フォールバックを追加し、GroupsのloraList形式(strength キー直接)にも対応

Mタブ Groupsの LoRAグループ → LoraManagerドロップ対応(web/comfyui/node_sets_menu.js)

  • renderModelGroups でLoRAタイプのグループリスト末尾に「All N LoRAs → Lora Loader (LoraManager)」アイテムを追加
  • ドラッグで application/x-wfm-lora-multi データ送出 → キャンバスに Lora Loader (LoraManager) ノードを全LoRAセット済みで配置
  • ダブルクリックでも即時配置(placeLoraMgrNode)
  • モデルファイル名から stem(拡張子・パス除去)に変換して LoraManager 名前形式に合わせる

Pタブ Groupsサブタブ追加(web/comfyui/node_sets_menu.js)

  • state に promptSubTab2 と promptGroups を追加
  • loadPromptData で localStorage["wfm_prompt_preset_groups"] からグループを読み込み、存在しないIDをクリーンアップ
  • Pタブのrow2に「📁 Groups」サブタブを追加(Wタブ・Nタブと同様の2段構成)
  • renderPromptGroups 関数を実装:グループ名セクション展開・検索フィルター対応・createDraggablePromptItem でドラッグ動作を継承
  • Batchで作成したPromptグループが同じlocalStorageを参照するためそのままライブラリから利用可能

ヘルプ更新(static/js/i18n.js)

  • helpSidepanel11(EN/JA/ZH):PタブGroups追加・MタブLoRAグループ→LoraManagerドロップを追記
  • helpSidepanel13(EN/JA/ZH):Lora Loader (LoraManager) ノードタイプ対応を追記
  • helpSidepanel15(EN/JA/ZH):LoRA検出ノードタイプ一覧・APIフォーマット対応・LoRA構文自動入力を追記

2026-06-08: v0.3.31 — LoRAペイン Single/Stack タブ分割・GenUI Model LoRA対応強化

変更内容

LoRAペイン Single/Stack タブ分割(static/js/comfyui-editor.js, static/css/main.css)

  • renderLoraPane を全面書き換え。Single / Stack の2タブ構成に変更
  • Single タブ: フィルター・モデルドロップダウン・強度(M/C)・ターゲットノード選択・Apply・P・LORA SYNTAX表示・TRIGGER WORDS表示
    • モデル選択・強度変更時にLORA SYNTAX が自動更新
    • Apply: ワークフローノードへの適用 + LORA SYNTAX/TRIGGER WORDS を Positive プロンプトへ差分更新
    • P: Positive プロンプトをワークフローに反映
  • Stack タブ: ターゲットノード選択・Apply・P・Toggle-all・グローバル強度調整(+/−)・LORA SYNTAX・TRIGGER WORDS・モデルリスト
    • 検索フィルターとモデルドロップダウンを削除(Stack専用UIに整理)
  • _refreshLoraSingleDynamic(metadata, civitaiCache) 関数を追加(Single タブの動的更新)
  • comfyEditor.switchLoraSingleTab() メソッドを追加(外部からSingleタブへ切り替え)
  • comfyEditor.disableAllStack() メソッドを追加(外部からStack全無効化)

GenUI Model LoRA対応強化(static/js/models-tab.js)

  • Lora Loader (LoraManager) ノード対応: lora_name 検索ではなく currentAnalysis.lora_nodes を参照し is_lora_manager で書き込み形式を分岐
  • LoRA適用時: Singleタブへ切り替え・Stack全無効化・wfm-lora-single-syntax/wfm-lora-single-triggers を更新
  • Positive promptへの直接書き込みを廃止(Apply/Pボタンで明示的に反映する設計に統一)

ヘルプ更新(static/js/i18n.js)

  • helpGen5: LoRA に「(Single/Stack タブ)」の説明を追加(EN/JA/ZH)
  • helpModels8: GenUI Model LoRA 動作の詳細を追記(EN/JA/ZH)

2026-06-08: v0.3.30 — LoRAスタック トリガーワードバグ修正・ヘルプ更新

変更内容

LoRAスタック TRIGGER WORDS 表示バグ修正(static/js/comfyui-editor.js)

  • 問題: スタックで1つのモデルを無効にしてもTRIGGER WORDSに無効モデルのトリガーワードが残っていた
  • 原因1: 初期表示の triggerHtml が allTriggerWords(全モデル分)を使っていた → activeTriggerWords に修正
  • 原因2: チェックボックス切り替え・toggle-all・強度調整時に呼ばれる _refreshLoraPaneDynamic がLORA SYNTAXのみ更新してTRIGGER WORDSを更新していなかった
  • 修正: _refreshLoraPaneDynamic(stackModels, metadata, civitaiCache) でTRIGGER WORDSも再計算・再描画するよう拡張(アクティブモデルのみ表示)

ヘルプ トラブルシューティング追加(templates/index.html, static/js/i18n.js, static/js/app.js)

  • LoRAスタック実行時に「No such file or directory」エラーが出る場合のトラブルシューティングを追記
  • 原因: comfyui-lora-managerが該当LoRAを認識していない(既知の問題)
  • 対処: 別のLoRAで試す、またはLora Manager自体のUIでファイルが表示されるか確認

2026-06-08: v0.3.29 — LoRAセクション統合・ヘルプタブサイドバー・UI細部改善

変更内容

LoRAセクション統合(static/js/comfyui-editor.js, static/css/main.css)

  • 単体LoRAセクションとStackセクションを1つの統合レイアウトに統合(重複を削除)
    • 表示順: Filter → Model select → ID行(ID select / Apply / P) → Strength行(Stack label / ☑ / M / C / Str M adjuster / C adjuster) → Lora syntax → Trigger words → Stack model一覧
  • Apply ボタン統合: 単体・スタック共通の1ボタンに
    • スタックモデルあり → 全アクティブモデルをノードに適用 + プロンプト同期
    • スタックモデルなし → 選択中の単体LoRAをM/C強度でノードに適用 + プロンプト同期
  • 単体モードのプロンプト同期対応: スタックなし時も <lora:stem:M:C> syntax とCivitAIトリガーワードをPositiveプロンプトへ追記・差分更新
  • Ctrl+Apply → 通常Apply に変更: 以前のCtrl限定操作を通常クリックに変更(デフォルト動作)
  • P ボタン追加: Apply右隣に配置。InputタブのPositiveプロンプト内容をワークフローへ即時反映(プロンプトタブに切り替える手間を省略)
  • IDドロップダウン幅調整: flex:1 で利用可能な幅いっぱいに広げ、max-width制限を撤廃
  • "Stack"ラベルの位置変更: ID行から削除し、Strength行左端(チェックボックス横)に移動
  • Stack toggle-all チェックボックスの移動: ヘッダー行からStrength行左端に移動
    • 個別モデル行のチェックボックス列と視覚的に揃い、全体ON/OFFの役割が明確に
  • M/C入力幅修正: 54px → 60px に拡大(スピナーボタン分の余裕を確保し数値が切れる問題を修正)
  • Str M / C ラベルの位置変更: 別ヘッダー行から All 調整行(Strength行)に統合
  • All調整行をグリッドレイアウトに変更: 個別モデル行と同じ grid-template-columns で右揃えに

Modelsタブ — モデル選択時のデフォルトタブ変更(static/js/models-tab.js)

  • サイドパネルのデフォルト表示タブを「Info」から「CivitAI」に変更
  • モデル選択のたびにCivitAI情報が即座に表示される

ヘルプタブ — 左サイドバー化(templates/index.html, static/js/app.js, static/css/main.css)

  • 1カラムスクロールから「左サイドバー(170px) + 右コンテンツペイン」の2カラムレイアウトに変更
  • サイドバーに14項目のナビゲーションボタンを配置、選択中項目をハイライト(左ボーダー + プライマリカラー)
  • Supportを最下部に固定表示(border-top セパレーター付き)
  • 各項目クリックで右ペインのコンテンツを切り替え(.wfm-help-page.active の付け替え)
  • initHelpTab() を app.js の DOMContentLoaded に追加

ヘルプコンテンツ更新(templates/index.html)

  • GenerateUI Tab (gen-2): Reset Workflowボタンの説明を追加
  • GenerateUI Tab (gen-4): InputタブのPrompt/Imageタブ化・テキストボックス高さ2倍の説明に更新
  • GenerateUI Tab (gen-5): LoRA Stack ApplyによるLoRA Syntax + Trigger Words同期の説明を追加
  • Feeder subtab (feeder-4): (root)フォルダ自動選択の説明を追加
  • Feeder subtab (feeder-6): PREVIEWペインが右端固定・RUN中に現在画像表示の説明に更新
  • Models Tab (models-4): CivitAIタブがデフォルト表示になった旨を追記

2026-06-08: 生成UIタブ 複数改善 — LoRA Stackトリガーワード連携・Reset Workflowボタン・Inputタブタブ化

変更内容

LoRA Stack Ctrl+Apply — トリガーワード追加 + 再適用時の差分更新(static/js/comfyui-editor.js)

  • Ctrl+Apply 時に LORA SYNTAX に続いてアクティブモデルのトリガーワードをプロンプトへ追加
    • 追加形式:既存プロンプト, <lora:...>, triggerA, triggerB
  • activeTriggerWords(アクティブのみ)と allTriggerWords(UI表示用・全モデル)を分離
    • 非アクティブモデルの lora syntax は除外済みだったが、トリガーワードも同様に除外
  • 再 Ctrl+Apply 時はプロンプトを差分更新(全スタック分を除去 → アクティブ分を再追加)
    • <lora:stem:...> トークンを stem 単位で正規表現除去
    • 全スタックモデルのトリガーワードをカンマ分割で除去してから再追加

Reset Workflow ボタン(templates/index.html, static/js/generate-tab.js)

  • 上部ツールバーの「Refresh Models」右隣に「Reset Workflow」ボタンを追加
  • 現在読み込まれているファイル(.json)を /api/wfm/workflows/raw から再取得して再ロード
  • loadWorkflowIntoEditor でファイル名を data-filename 属性に保存し Reset 時に参照
  • ファイルベースでない場合(Raw JSON貼り付け等)は警告トーストを表示

Inputタブ — Prompt/Image タブ化 + テキストボックス高さ2倍

タブ化(templates/index.html, static/js/generate-tab.js, static/css/main.css)

  • Prompt と Image を上下スタックから「Prompt | Image」の内部タブ構成に変更
  • .wfm-input-inner-tabnav / .wfm-input-inner-tab / .wfm-input-inner-panel を新規追加
  • アクティブタブにはアンダーライン強調スタイルを適用

テキストボックス高さ2倍(static/js/comfyui-editor.js)

  • Positive Prompt: rows="4" → rows="8"
  • Negative Prompt: rows="3" → rows="6"

2026-06-08: Feederタブ UI改善 — (root)自動選択・PREVIEWペイン右移動・RUN中プレビュー反映

変更内容

(root)フォルダの自動選択(static/js/feeder-tab.js)

  • initFeederTab() 末尾に _selectDir("") を追加
  • タブを開いた初期状態で (root) がハイライトされ、画像一覧が即座に表示される

PREVIEWペインをレイアウト最右に移動(templates/index.html)

  • wfm-feeder-preview-pane を wfm-feeder-library-body(内側 flex row)から wfm-feeder-layout(外側 flex row)の直接子に移動
  • IMAGES グリッドが横幅を最大限確保できるようになった
  • レイアウト構造:Settings (220px) | Library (flex:1) | Preview (160px)

RUN中のプレビュー自動更新(static/js/feeder-tab.js)

  • _startRun() 内の _syncHandler で thumbnail_path 受信時に _showPreview() を呼び出し
  • ノード実行完了のたびに処理中の画像サムネイル・ファイル名・解像度がPREVIEWペインに反映される

2026-06-07: v0.3.28 — Batchタブ Sampler/Scheduler対応 + Modelsサムネイルバグ修正

概要

GenerateUI の Batch タブにサンプラー・スケジューラーのバッチ処理を追加。 また Models タブでサムネイル変更後に画像が更新されなかった問題を修正した。

変更内容

Modelsタブ サムネイル変更バグ修正

原因1: ブラウザキャッシュ(py/routes/models_routes.py)

  • handle_get_preview の Cache-Control を public, max-age=3600 → no-cache に変更
  • モーダルを閉じて再度開いた際、ブラウザが1時間キャッシュした古い画像を返し続けていた

原因2: 破損ファイルの検出なし(py/services/models_service.py)

  • find_preview_image でファイルサイズが 100バイト未満のファイルをスキップするよう変更
  • 0バイトや極小の破損プレビューファイルが返されてカードが黒く表示される問題を解消

原因3: disabledモデルの非対応(py/routes/models_routes.py)

  • handle_change_preview でモデルファイルを検索する際、.disabled 拡張子付きファイルも候補に追加
  • 無効化済みモデルのサムネイル変更が「Model file not found」エラーになっていた

GenerateUI Batchタブ Sampler/Scheduler対応

左ペインのタブ化(templates/index.html, static/js/generate-tab.js, static/css/main.css)

  • 左ペインを Checkpoints / Sampler / Scheduler の3タブ構成に変更
  • Sampler・Scheduler タブには KSampler ノードから取得したリストをチェックボックスで表示
  • 各リストは Checkpoint と同様の (root) グループ行(全選択チェックボックス + 折りたたみ)で構成
  • All/None ツールバーボタンも各タブに配置

BATCH QUEUE 拡張(templates/index.html)

  • Sampler 列・Scheduler 列を追加し合計6列(Checkpoint / Lora / Prompt / Workflow / Sampler / Scheduler)
  • .wfm-batch-right の最小幅を 320px → 380px に拡張

バッチ処理ロジック追加(static/js/generate-tab.js)

  • _batchGroupState に _samplerSelected・_schedulerSelected(Set)を追加
  • _buildSimpleGroupList() 共通関数を新規追加(Sampler・Scheduler リストの (root) グループ描画)
  • _renderBatchPreview() に Sampler・Scheduler 列を追加
  • _runBatchGenerate() に case "sampler" / case "scheduler" を追加
    • ワークフロー内の全 sampler_nodes の sampler_name / scheduler を差し替えて順次生成
  • モデルリフレッシュ時に Sampler・Scheduler リストも再描画

ヘルプ更新(3言語)

  • helpGen11〜helpGen14 を Sampler/Scheduler 対応後の内容に更新(英/日/中)
  • helpGen15 を新規追加(Sampler/Scheduler バッチの操作説明)
  • helpModels6 を更新(サムネイル変更の詳細:即時反映・破損ファイル自動スキップ)
  • app.js に wfm-help-gen-15 のマッピングを追加

2026-06-07: v0.3.27 — model-and-prompt-from-metadata カスタムノード対応 + README修正

概要

model-and-prompt-from-metadata カスタムノードパックの全ノードをワークフロー解析に対応。 また README の LoraManager リポジトリリンクの誤記を修正した。

変更内容

ワークフロー解析対応(comfyui-workflow.js)

ノード表示名 クラス名 検出区分
CLIP Text Encode edit+ CLIPTextEncodeEditPlus prompt_nodes
Model from Metadata ImageMetadataCheckpointLoader 既存CheckpointLoader 判定で検出済み
Model-Prompt from Metadata ImageMetadataPromptLoader checkpoint_nodes + prompt_nodes(両方)
LoRA from Metadata ImageMetadataLoRALoader lora_nodes(3スロット)

CLIPTextEncodeEditPlus

  • text1 は forceInput(常にリンク接続)のため直接読み取り不可
  • ローカル上書きテキスト text_edit を編集ターゲット(textKey: "text_edit")として prompt_nodes に登録

ImageMetadataPromptLoader

  • 単一ノードが checkpoint + positive/negative プロンプトを兼ねる構造
  • checkpoint_nodes に ckpt_name で登録
  • positive_text を role "positive"、negative_text を role "negative" として prompt_nodes に登録
  • ロールはハードコード(sampler → CONDITIONING のスロット番号で両方参照される可能性があるため getRole() に依存しない)

ImageMetadataLoRALoader

  • lora_1〜lora_3 を順にチェックし、"None" 以外のスロットを lora_nodes に登録
  • 各スロットの strength_model_N / strength_clip_N も合わせて取得

_getWidgetMapping 静的フォールバック追加

  • object_info 取得失敗時の UI→API 変換用マッピングに上記4ノードを追加

README 修正(README.md)

  • LoraManager リポジトリ URL の誤記を修正
    • 誤: https://github.com/willchil/ComfyUI-Lora-Manager
    • 正: https://github.com/willmiao/ComfyUI-Lora-Manager

2026-06-07: v0.3.26 — Stack LORA SYNTAX 表示修正 + 全体強度調整 UI

概要

Stack ペインの LORA SYNTAX が空表示になるバグを修正し、全モデルの強度を一括増減できる調整ボックスを追加した。

変更内容

LORA SYNTAX 空表示バグ修正(comfyui-editor.js)

  • 原因: <lora:name:str> 構文を el.innerHTML テンプレートリテラルに埋め込んでいたため、 ブラウザが <lora:...> を未知の HTML タグとして解析し、本文が消えていた。 , 区切り文字のみが残って表示される症状だった。
  • 修正: el.innerHTML 代入直後に getElementById("wfm-lora-stack-syntax").textContent で テキストとして上書きするよう変更。_refreshLoraPaneDynamic は既に textContent を使用しており問題なし。

全体強度調整ボックス追加(comfyui-editor.js, main.css)

  • Stack モデル一覧ヘッダーの直下に All 調整行を追加
  • M・C それぞれに [−][step][+] コントロールを配置
    • + / − ボタン: step 入力欄の値(デフォルト 0.05)を全モデルの強度に加減算
    • step 入力欄は直接編集可能(min: 0.01、max: 2.0)
    • 有効・無効状態に関わらず全モデルの強度値を更新(無効モデルの保持値も変更)
  • 調整後は各行の入力欄と LORA SYNTAX 表示を自動更新
  • .wfm-lora-stack-global-adj / .wfm-lora-stack-global-adj-group / .wfm-lora-stack-adj-step / .wfm-btn-xs CSS を追加

強度入力欄の表示切れ修正(main.css, comfyui-editor.js)

  • 原因: .wfm-input の padding: 8px 12px が大きすぎ、56px 幅にスピナーと合わせて 数値が収まらず右端が切れていた。
  • CSS グリッド列幅を 56px → 64px に拡大(ヘッダー・入力欄とも)
  • .wfm-lora-stack-strengths input[type="number"] に padding: 4px 4px; text-align: center を適用
  • WebKit / Firefox スピナーボタンを非表示化 (グローバル調整の +/− ボタンで代替できるため、個別スピナーは不要と判断)

2026-06-07: v0.3.25 — Lora Loader (LoraManager) 対応 + Stack 有効無効切り替え

概要

生成UI の Lora ペインを Lora Loader (LoraManager) ノードに対応。 ワークフロー解析・単体適用・Stack 適用・バッチ生成の全経路で LoraManager 専用フォーマット (inputs.loras.__value__ 配列 + inputs.text 構文)への書き込みに対応した。 また Stack の各モデルと Stack 全体の有効 / 無効を切り替えるチェックボックス UI を追加。 Model サブタブを開くたびに LoRA ペインを再描画することで、 Models タブでの Stack グループ更新が即座に反映されるようにした。

変更内容

生成UI Model タブ — 左右ペイン等幅化(main.css)

  • wfm-gen-params-col--narrow を flex: none; width: 220px; から flex: 1; に変更
  • 左ペイン(Checkpoint / VAE 等)と中央ペイン(LoRA)が均等幅になった

Lora Loader (LoraManager) ノード対応

ワークフロー解析 (comfyui-workflow.js)

  • analyzeWorkflow() に Lora Loader (LoraManager) 検出ブロックを追加
  • lora_nodes に { id, type, title, is_lora_manager: true } として登録

ヘルパー関数追加 (comfyui-editor.js)

  • _buildLoraManagerSyntax(stackModels) — <lora:name:strM:strC> 形式(スペース区切り)
  • _applyLoraToNode(nodeId, loraPath, strModel, strClip, isLoraManager) — ノード種別に応じた書き込み
    • LoraManager: inputs.loras.__value__ + inputs.text を更新
    • 標準 LoraLoader: inputs.lora_name / strength_model / strength_clip を更新

単体 Apply (comfyui-editor.js)

  • Apply ボタンが is_lora_manager フラグを確認し、LoraManager 形式で書き込み

Stack Apply (comfyui-editor.js)

  • LoraManager ターゲット: Stack の全モデルを __value__ 配列に一括適用
    • 無効モデルは active: false で配列に保持(データを削除しない)
  • 標準 LoraLoader ターゲット: 従来通り先頭モデルのみ適用

バッチ生成 (generate-tab.js)

  • "lora" バッチケースで is_lora_manager を確認
  • LoraManager ノードには inputs.loras.__value__ + inputs.text を書き込み
  • 標準 LoraLoader ノードは従来通り inputs.lora_name を書き込み

Stack 有効 / 無効切り替え UI (comfyui-editor.js, main.css)

  • モジュールレベルに _stackActive 状態({ modelPath: boolean })を追加
  • _syncStackToggleAll(stackModels) — Toggle All チェックボックスの checked / indeterminate 状態を同期
  • _buildLoraSyntax / _buildLoraManagerSyntax — 無効モデルをフィルタリングして構文を生成
  • Toggle All チェックボックス — Stack ヘッダー左端に追加(全ON / 全OFF / 中間状態に対応)
  • モデルごとのチェックボックス — 各 Stack モデル行の左端に追加
    • 無効時: モデル名に取り消し線 + 透過(opacity 0.4)、入力欄も透過 + disabled
    • Stack Apply 時: 無効モデルを active: false で __value__ に保持(LoraManager がスキップ)
  • CSS グリッド更新:
    • .wfm-lora-stack-models-header: 1fr 56px 56px → 16px 1fr 56px 56px
    • .wfm-lora-stack-model-row: 1fr auto → 16px 1fr auto
    • .wfm-lora-stack-model-row--off スタイルを追加

Stack の自動適用 (comfyui-editor.js, generate-tab.js)

  • stackTargetOpts を新設: Stack ターゲットのドロップダウンが LoraManager ノードを優先的に selected
  • renderLoraPane 末尾で自動適用: LoraManager がターゲットかつ Stack にモデルがある場合、 ワークフロー読み込み時に自動で inputs.loras / inputs.text を更新
  • Model サブタブをクリックするたびに renderLoraPane を再実行 → Models タブで Stack グループを更新した後、すぐに GenerateUI に反映

2026-06-06: v0.3.24 — Lora Stack 登録機能 + 生成UI Model タブ 3ペイン化

概要

Models タブに Lora 専用の Stack グループ登録機能を追加。 生成UI の Model タブを 3ペイン(モデル選択 / Lora 専用 / RAW JSON)に分割し、 Lora ペインに単体適用セクションと Stack セクション(構文自動生成・トリガーワード表示・強度設定)を実装。

変更内容

Models タブ — Batch B ボタンの Lora/Checkpoint 限定表示(models-tab.js)

  • サムネイルビュー・テーブルビューともに、showBatchBtn フラグを導入し Checkpoint・Lora 以外のモデルタイプでは B ボタン/B 列を非表示に変更
  • イベントリスナーを ?.addEventListener でヌルセーフ化

Models タブ — Stack グループ登録機能(models-tab.js, index.html, main.css)

  • RESERVED_GROUPS に "Stack" を追加(削除不可)
  • STACK_MODEL_TYPES = ["lora"] 定数を追加
  • Lora タイプ読み込み時に Stack グループを自動初期化(2箇所)
  • isInStack / toggleStack / clearStackGroup 関数を追加
  • サムネイルビュー: Lora のみ左下に S オーバーレイボタン追加(active 時は緑色 #60d0a0)
  • テーブルビュー: B 列の右に S 列を追加(ヘッダー含む)
  • SC ボタン: ツールバーの BC ボタン右隣に Stack Clear ボタンを追加
  • セレクトモード時の click 除外対象に .wfm-stack-btn を追加

生成UI Model タブ — 3ペイン化(index.html, main.css, comfyui-editor.js)

  • Model タブの wfm-gen-tab-cols を 3列構成に変更
    • 左ペイン: wfm-gen-params-col--narrow(width: 220px 固定)— Checkpoint / VAE 等
    • 中央ペイン: wfm-gen-lora-col(flex: 1)— Lora 専用
    • 右ペイン: RAW JSON(変更なし)
  • renderModelTab から LoRA セクションを削除
  • renderAll に renderLoraPane 呼び出しを追加

生成UI Lora ペイン(comfyui-editor.js, main.css)

単体セクション(上部)

  • フィルター入力 / モデルセレクト / ノード ID + Apply ボタン / Strength M・C

Stack セクション(下部)

  • Stack ラベル + ノード ID セレクト + Apply ボタン
    • 通常クリック: 選択ノードに Stack 先頭 Lora を適用
    • Ctrl+クリック: Input タブ Positive プロンプト末尾に Lora 構文をカンマ区切りで追加し、ワークフローにも反映
  • Lora 構文表示: <lora:stem:strength> 形式を自動生成(Strength 変更でリアルタイム更新)
  • トリガーワード表示: Stack 登録モデルの CivitAI trainedWords を一括表示
  • モデルリスト: Stack 登録 Lora を行単位で表示、各行に Strength M / C 入力
  • モジュールレベルの _stackStrengths(強度状態)、_loraBasename、_buildLoraSyntax、_refreshLoraPaneDynamic を追加

2026-06-05: v0.3.23 — バグ修正2件

バグ修正

ワークフローバッチ 404 エラー修正(generate-tab.js)

  • _runBatchGenerate() の workflow ケースで、ワークフローファイル取得 URL が /api/wfm/workflows/{filename}(存在しないルート)になっていた
  • 正しいエンドポイント /api/wfm/workflows/raw?filename={filename} に修正

ワークフローリネーム二重送信バグ修正(workflow-tab.js)

  • モーダルを開くたびに titleInput.addEventListener("blur", commitRename) が累積していた
  • 2回目以降のリネーム時に blur が複数発火し、同一ファイルへのリネームリクエストが 2本送られて 1本目成功後に 2本目が 409 または 404 になっていた
  • titleInput._commitRename で前回のリスナー参照を保持し、 再オープン時に removeEventListener で除去してから登録するよう修正

2026-06-05: v0.3.22 — カードビュー廃止 + Batch タイプ切り替え UI + 4タイプバッチ生成

概要

WorkflowタブとModelsタブのカードビューを廃止(サムネイル/テーブルの2択に統一)。 生成UIタブのBatch機能を4タイプ(Checkpoint/Lora/Prompt/Workflow)のいずれかを有効化する ラジオ選択型に拡張し、バッチ実行ループも全タイプ対応。

カードビュー廃止(Workflow・Models タブ)

  • ビュー切り替えボタンから Card(☰)を削除(Thumb・Table の2択)
  • viewMode の初期値に "card" が保存されている場合は "thumb" にフォールバック
  • Nodes タブのカードビューは変更なし

Batch タイプ切り替え(BATCH QUEUE ヘッダー)

  • 各列ヘッダー右端にチェックボックスを追加(Checkpoint / Lora / Prompt / Workflow)
  • ラジオ動作:1つを選択すると他3つは自動解除(_activeBatchType 変数で管理)
  • バッチ有効時に Generate ボタンを押すと対応タイプのバッチ実行

Batch ステータスパネル(旧 Checkpoint Batch)

  • "Checkpoint Batch" チェックボックス+ラベルを廃止
  • 「Batch」+ アクティブタイプ名の常時表示に変更(実行中は青色ハイライト)
  • 進捗バー・Pause ボタンはバッチ実行中のみ表示

バッチ生成 4タイプ対応

  • 汎用ループ _runBatchLoop(items, applyFn, labelFn) を実装
  • _runBatchGenerate() が _activeBatchType に応じてディスパッチ:
    • Checkpoint: checkpoint_nodes の ckpt_name を差し替え
    • Lora: lora_nodes の lora_name を差し替え
    • Prompt: prompt_nodes の positive に text、negative に negText を適用(textKey 経由)
    • Workflow: 各ファイルをロード → 生成 → 完了後に元ワークフローを復元

バグ修正

  • Prompt バッチ: _getSelectedPromptGroupItems() がすでにプリセットオブジェクト配列を返すにもかかわらず、ID として再解決しようとして list が空になるバグを修正

変更内容

templates/index.html

  • Workflow タブ・Models タブのビュー切り替えから Card ボタンを削除
  • BATCH QUEUE 各列ヘッダー: テキストを <span> でラップ + <input class="wfm-batch-type-cb" data-batch-type="..."> を追加
  • Checkpoint Batch パネル: チェックボックス削除 → wfm-batch-type-label スパン(アクティブタイプ名表示)に置換。wfm-ckpt-batch-body の display:none 初期非表示を廃止

static/js/workflow-tab.js

  • viewMode 初期値: localStorage の値が "table" 以外なら "thumb" にフォールバック
  • renderGrid(): thumb/card の分岐を削除。常にサムネイルビューを描画
  • tags 変数宣言を削除(カードビューでしか使用していなかったため)

static/js/models-tab.js

  • viewMode 初期値: 同上フォールバック
  • renderModelGrid(): else if (viewMode === "card") 分岐を削除。thumb/table の2択に
  • renderCardView() 関数を削除(62行)

static/js/generate-tab.js

  • _activeBatchType 変数追加(null | "checkpoint" | "lora" | "prompt" | "workflow")
  • _updateBatchTypeLabel(running): バッチステータスパネルのタイプ名ラベルを更新
  • initCheckpointBatch(): 旧チェックボックスハンドラを削除 → .wfm-batch-type-cb のラジオ動作ハンドラに置換
  • _runBatchLoop(items, applyFn, labelFn): 汎用バッチループ実装(進捗表示・一時停止・中断対応)
  • _runBatchGenerate(): Checkpoint単体から4タイプディスパッチャに書き換え
  • handleGenerate(): wfm-ckpt-batch-enabled チェック → _activeBatchType !== null に変更

static/css/main.css

  • Workflow/Models カードビュー関連スタイルを削除(計約86行):
    • .wfm-view-card 系全ルール / .wfm-card-io / .wfm-model-card / .wfm-view-card .wfm-toggle-btn 等
  • .wfm-batch-queue-col-header: display: flex; justify-content: space-between 追加
  • .wfm-batch-type-cb: チェックボックス用スタイル追加(12px、accent-color 指定)

2026-06-05: (未リリース) — 生成UIタブ Batch 4タイプ対応(Lora/Prompt/Workflow グループ選択 + BATCH QUEUE 4列化)

概要

生成UIタブの Batch ペインを Checkpoint 単体から Lora・Prompt・Workflow を含む4タイプ対応に拡張。 グループチェックペイン(中央ペイン)の Lora・Prompt・Workflow タブに実際のグループリストを実装し、 BATCH QUEUE(右ペイン)を4列横並びレイアウトに変更。

グループチェックペイン(中央)

  • Lora タブ: /api/wfm/models/groups?type=lora からグループ取得。ファイル名(末尾)を表示
  • Prompt タブ: /api/wfm/prompts でプリセット取得 + localStorage[wfm_prompt_preset_groups] でグループ取得。ID を name フィールドで解決して表示
  • Workflow タブ: localStorage[wfm_groups] からグループ取得。.json 除去したファイル名を表示
  • 各タブクリック時に対応するグループを再ロード。Batchサブタブ表示時は全タイプを一括ロード

BATCH QUEUE(右ペイン)

  • Checkpoint / Lora / Prompt / Workflow の4列横並び表示
  • 各列ヘッダー・件数・アイテムリストを独立表示
  • 右ペイン幅を flex: 1.2; min-width: 320px に拡大

バグ修正

  • ワークフロー カードビュー の B ボタンを左端から右下(★の下)に移動
  • Prompt グループ選択ペインで UUID がそのまま表示されていた問題を修正(title → name フィールド)

変更内容

templates/index.html

  • Lora/Prompt/Workflow タブ: "Coming soon" プレースホルダー → グループリストコンテナ(wfm-batch-lora-group-list / wfm-batch-prompt-group-list / wfm-batch-wf-group-list)に置換
  • BATCH QUEUE 右ペイン: 単一リスト → wfm-batch-queue-grid 内に4列(各 wfm-batch-queue-col)

static/js/generate-tab.js

_batchGroupState 拡張

  • Lora: loraGroups / loraSelectedGroups / loraPartialSelections
  • Prompt: promptGroups / promptPresets / promptSelectedGroups / promptPartialSelections
  • Workflow: wfGroups / wfSelectedGroups / wfPartialSelections

新規汎用関数

  • _getItemsFromGroupState(groupsData, selectedGroups, partialSelections) — グループ状態からメンバーSet取得
  • _getGroupSelCountFrom(name, ...) — 汎用選択数カウント
  • _renderAnyGroupList(listEl, groupsData, ...) — Checkpoint と同じグループリストUIを任意タイプで再利用

新規選択取得関数

  • _getSelectedLoraGroupItems() / _getSelectedPromptGroupItems() / _getSelectedWfGroupItems()

新規ロード/レンダー関数

  • _loadBatchLoraGroups() / _renderBatchLoraGroupList()
  • _loadPromptGroupsForBatch() / _renderBatchPromptGroupList()
  • _loadWorkflowGroupsForBatch() / _renderBatchWfGroupList()

_renderBatchPreview() リファクタリング

  • _renderQueueColumn(countId, listId, items, displayFn, singular, plural) を抽出
  • 4タイプ(Checkpoint/Lora/Prompt/Workflow)を各列に独立レンダリング

initBatchTab() 更新

  • 内部タブクリック時に対応する load 関数を呼び出し
  • Batch サブタブ表示時に全タイプを一括ロード

バグ修正

  • Prompt グループ表示: presetsMap.get(id)?.title → presetsMap.get(id)?.name(APIフィールド名修正)

static/js/workflow-tab.js

  • カードビュー HTML: B ボタンと★ボタンを wfm-card-actions ラッパーで包み縦並び(★上・B下)に変更

static/css/main.css

  • .wfm-batch-right: 固定210px → flex: 1.2; min-width: 320px
  • .wfm-batch-queue-grid / .wfm-batch-queue-col / .wfm-batch-queue-col-header: 4列横並びレイアウト用スタイル追加
  • .wfm-view-card .wfm-card: align-items: stretch に変更
  • .wfm-view-card .wfm-card-actions: flex-direction: column; justify-content: space-between で縦並び配置

2026-06-05: (未リリース) — Prompt・Workflow Batchグループ登録UI

概要

Prompt タブと Workflow タブに Batch グループへの登録・解除 UI を追加。 モデルタブと同じ操作感で統一されており、B ボタンで登録/解除、B フィルターで絞り込み、BC ボタンで一括解除が可能。

Prompt タブ

  • 各プリセットアイテムの★ボタン左隣に B ボタンを追加(黄色=登録済み)
  • 検索ボックス右端に BC ボタンを追加(wfm-pm-search 行を flex 化)

Workflow タブ

  • サムネイルビュー:wfm-card-thumb 左上に B ボタン(絶対配置)
  • カードビュー:カード左下に B ボタン(絶対配置)
  • テーブルビュー:右端に B 列追加
  • フィルター行の★ボタン右隣に B フィルタリングボタンを追加
  • View Settings ボタン右隣に BC ボタンを追加

変更内容

static/js/prompt-tab.js

新規関数

  • isInBatchPreset(id) — pmGroups["Batch"] に id が含まれるか判定
  • toggleBatchPreset(id) — バッチへの登録/解除トグル → saveGroups() で LocalStorage 保存
  • clearBatchPresets() — pmGroups["Batch"] = [] で一括解除

createPmItem()

  • inBatch 変数を追加し .pm-batch-btn クラスに batch-active を付与
  • ★ボタンの左に <button class="wfm-pm-action-btn pm-batch-btn">B</button> を追加
  • .pm-batch-btn クリックで toggleBatchPreset() を呼び出し

initPromptTab()

  • wfm-pm-batch-clear-btn クリック → clearBatchPresets() 呼び出し

static/js/workflow-tab.js

新規 state フィールド

  • state.showBatchOnly: false

新規関数

  • isInBatch(filename) / toggleBatch(filename) / clearBatch() — groups.data["Batch"] を操作

filterWorkflows()

  • state.showBatchOnly が true のとき Batch グループメンバーのみに絞り込む処理を追加

renderModelFilters()

  • ★フィルターボタンの右に B フィルタリングボタン(.wfm-wf-batch-filter-btn)を追加
  • クリックで state.showBatchOnly トグル

renderGrid() — thumbビュー

  • wfm-card-thumb 内に <button class="wfm-batch-btn">B</button> を追加(左上絶対配置)

renderGrid() — cardビュー

  • favBtnの前に <button class="wfm-batch-btn">B</button> を追加(左下絶対配置)

renderTableView()

  • thead 右端に空の th を追加(width:30px)
  • 各行右端に <td class="wfm-table-td-batch">B ボタン</td> を追加

initWorkflowTab()

  • wfm-wf-batch-clear-btn クリック → clearBatch() 呼び出し

templates/index.html

  • Prompt タブ: wfm-pm-search 内にBC ボタン(wfm-pm-batch-clear-btn)を追加、入力ボックスに flex:1 追加
  • Workflow タブ: View Settings ボタン(wfm-badge-settings-btn)の右に BC ボタン(wfm-wf-batch-clear-btn)を追加

static/css/main.css

  • .wfm-pm-search { display: flex; gap: 4px; align-items: center; } — BC ボタン配置用
  • .wfm-pm-action-btn.batch-active { color: #f0c040; } — Prompt タブ Bボタン active 時
  • .wfm-card-thumb { position: relative; } — ワークフローthumbビューの Bボタン絶対配置基準
  • #wfm-workflow-grid.wfm-view-card .wfm-batch-btn { position: absolute; bottom: 6px; left: 6px; } — ワークフローcardビュー専用(モデルタブと区別)

static/js/i18n.js

  • promptBatchClear / wfBatchClear を英語・日本語・中国語で追加

2026-06-04: (未リリース) — モデルBatchグループ登録UI(サムネイル/カード/テーブル・フィルター・一括解除)

概要

Models タブ(Checkpoint / Lora)の Batch グループへの登録・解除 UI を全ビューに追加。 お気に入り(★)と同じ操作感で、1クリックで登録/解除できる「B」ボタンをサムネイル・カード・テーブルの各ビューに配置。 フィルターボタン(B)でバッチ登録済みモデルのみ絞り込み表示、BC ボタンで一括解除が可能。

変更内容

templates/index.html

  • ★フィルターボタンの右隣に B フィルターボタン(wfm-models-batch-filter-btn)を追加
  • Select ボタンの右隣に BC ボタン(wfm-models-batch-clear-btn)を追加

static/css/main.css

  • .wfm-batch-btn スタイルを追加
    • 通常時: グレー (var(--wfm-text-secondary))・opacity 0.5
    • .active 時: 黄色 (#f0c040)・opacity 1
    • サムネイルビュー: position: absolute; top: 6px; left: 6px;(プレビュー左上)
    • カードビュー: position: static; flex-shrink: 0;(★ボタンの左隣)
    • テーブルビュー: position: static
  • .wfm-table-td-batch セル幅スタイルを追加

static/js/models-tab.js

新規 state フィールド

  • state.showBatchOnly: false — Bフィルターの有効状態

新規ヘルパー関数

  • isInBatch(modelName) — state.modelGroups["Batch"] に含まれるか判定
  • toggleBatch(modelName) — バッチへの登録/解除トグル → saveModelGroups() でサーバー保存
  • clearBatchGroup() — state.modelGroups["Batch"] = [] で一括解除、Bフィルターも解除

フィルター処理

  • state.showBatchOnly が true のとき modelGroups["Batch"] メンバーのみに絞り込む処理を追加(showFavoritesOnly の直後)

renderThumbView

  • batchClass 変数を追加(active で黄色クラス付与)
  • プレビュー画像エリア左上に <button class="wfm-batch-btn">B</button> を追加
  • .wfm-batch-btn クリックで toggleBatch() を呼び出し
  • selectMode のクリックガードに .wfm-batch-btn を追加

renderCardView

  • 同様に batchClass を追加し ★ボタンの左に <button class="wfm-batch-btn">B</button> を追加
  • イベントハンドラ・selectMode ガードを同様に追加

renderTableView

  • 各行に <td class="wfm-table-td-batch">B ボタン</td> を右端に追加
  • thead に <th style="width:30px;">B</th> を追加
  • .wfm-batch-btn クリックイベントを各行にバインド
  • selectMode のクリックガードに .wfm-batch-btn を追加

イベントハンドラ

  • wfm-models-batch-filter-btn クリック → state.showBatchOnly トグル・renderModelGrid() 呼び出し
  • wfm-models-batch-clear-btn クリック → clearBatchGroup() 呼び出し

static/js/i18n.js

  • modelsBatch / modelsBatchClear を英語・日本語・中国語で追加

2026-06-04: (未リリース) — Batch予約グループ・Presets Save後リセット・グループフィルター種別絞り込み

概要

Workflow / Prompt / Models(Checkpoint・Lora)に「Batch」予約グループを追加。 予約グループはシステム側で自動作成され、ユーザーによる削除・リネームをブロックする。 合わせて Prompt タブの Presets で Save 後に選択が --New Preset-- へリセットされるよう修正し、 Models タブのグループフィルタードロップダウンが現在選択中のモデル種別のグループのみ表示するよう改善。

変更内容

static/js/models-tab.js

Batch 予約グループの自動作成(Checkpoint / Lora)

  • RESERVED_GROUPS = ["Batch"] / BATCH_MODEL_TYPES = ["checkpoint", "lora"] 定数を追加
  • loadModelsForCurrentType() のキャッシュヒット時・フル読み込み時の両パスで、対象タイプに "Batch" グループが存在しなければ API 経由で自動作成

削除・リネームのブロック

  • グループ削除ボタン・リネームボタンのイベントハンドラで RESERVED_GROUPS チェックを追加
  • 予約グループを操作しようとした場合 modelsGroupReserved トーストを表示して処理を中断

グループフィルター種別絞り込み

  • renderGroupFilter() を改修: 全タイプ横断表示から state.activeModelType のグループのみ表示するよう変更
  • [タイプラベル] グループ名 形式を廃止し、グループ名のみを表示

static/js/prompt-tab.js

Batch 予約グループの自動確保

  • PROMPT_RESERVED_GROUPS = ["Batch"] 定数を追加
  • loadAllPresets() のクリーニング処理で予約グループを空でも削除しないよう修正
  • クリーニング後に予約グループが存在しなければ pmGroups["Batch"] = [] で確保し saveGroups() へ反映

削除ブロック

  • グループ削除ボタンのイベントハンドラで PROMPT_RESERVED_GROUPS チェックを追加

Save 後の Preset 選択リセット

  • Preset 保存(新規作成・更新どちらも)成功後に pmSelectedId = null / presetSelect.value = "" で --New Preset-- へ戻す
  • 旧: 新規作成後は保存した Preset が選択されたまま → 誤って上書きするリスクがあった

static/js/workflow-tab.js

Batch 予約グループの自動作成

  • WF_RESERVED_GROUPS = ["Batch"] 定数を追加
  • groups.load() に予約グループ確保ロジックを追加(存在しなければ LocalStorage へ即時保存)

削除・リネームのブロック

  • groups.deleteGroup() / groups.renameGroup() に WF_RESERVED_GROUPS チェックを追加(false 返却で中断)
  • 削除・リネームボタンのイベントハンドラにもフロントエンド側チェックを追加してトースト表示

static/js/i18n.js

  • modelsGroupReserved を英語・日本語・中国語で追加(「予約済みグループは削除・リネーム不可」のメッセージ)

2026-05-31: v0.3.21 — CivitAI ホスト設定・Sample サブタブ・URL 修正・ヘルプ i18n 整備

概要

CivitAI 連携に 3 つの改善を追加。モデルリンクを開くサイトを civitai.com / civitai.red から選択できる ホスト設定を Settings タブに追加。CivitAI パネルを Info / Sample のサブタブに分割し、 サンプル画像を専用タブに移動。modelId が null のキャッシュエントリの URL を /model-versions/{versionId} にフォールバックさせ、全 324 件の既存キャッシュも即時修正。 ヘルプを Batch tab / Text Size / RAW JSON などの新項目を含めて更新し、JA/ZH 翻訳の英語混じりも解消。

変更内容

py/services/civitai_service.py

_extract_info() — modelUrl フォールバックを修正

  • 旧: modelId が null のとき https://civitai.com/models?modelVersionId={id}(モデル一覧ページに飛ぶ壊れた URL)
  • 新: https://civitai.com/model-versions/{versionId} にフォールバック(モデルページへリダイレクト)

static/js/models-tab.js

renderCivitaiInfo() — Info / Sample サブタブを追加

  • CivitAI パネルを「情報」「サンプル (N)」の 2 サブタブに分割
  • Info タブ: モデル名リンク・Type/Base Model/Hash 詳細行・タグ・トリガーワード・説明・更新ボタン
  • Sample タブ: 全サンプル画像(件数をタブ名に表示)。画像は別タブで開く

renderCivitaiInfo() — URL 修正(クライアントサイド)

  • info.modelId がある場合: https://{host}/models/{modelId}?modelVersionId={versionId}
  • info.modelId が null: https://{host}/model-versions/{versionId} にフォールバック
  • {host} は localStorage.getItem("wfm_civitai_host") から取得(デフォルト civitai.com)

static/js/settings-tab.js

CivitAI セクションにホスト選択を追加

  • CivitAI API Key アコーディオンの先頭に wfm-select ドロップダウンを追加
  • 選択肢: civitai.com(SFW のみ) / civitai.red(制限なし)
  • 変更で即座に POST /api/wfm/settings へ保存 + localStorage.setItem("wfm_civitai_host", host) を実行
  • 設定タブ読み込み時に civitai_host を localStorage へ同期(他タブが追加 fetch 不要で参照できる)

static/js/i18n.js

新規文字列(EN / JA / ZH)

  • civitaiTabInfo / civitaiTabSample / civitaiNoImages: サブタブ名・空メッセージ
  • civitaiHostSetting / civitaiHostHint / civitaiHostCom / civitaiHostRed / civitaiHostSaved: ホスト設定
  • helpGen12 / helpGen13 / helpGen14: Batch タブの 3 ペイン詳細
  • helpSettings11 / helpSettings12 / helpSettings13: Text Size・RAW JSON Colors・CivitAI ホスト

既存文字列を更新(JA / ZH)

  • helpGen3: 「4タブ → 5タブ(Batch 追加)」
  • helpGen11: Batch トグルの説明に更新、Filter/Pause/Resume/Stop の英語混じりを解消

static/js/app.js

  • wfm-help-gen-12/13/14 → helpGen12/13/14 のマッピングを追加
  • wfm-help-settings-11/12/13 → helpSettings11/12/13 のマッピングを追加

templates/index.html

  • wfm-help-settings-13(CivitAI ホスト)を Settings カードに追加

2026-05-31: v0.3.20 — CivitAI 詳細パネル強化(Type / Hash 表示・URL 修正・画像別タブ)

概要

Models タブのサイドパネル CivitAI タブを強化。Type・Base Model・Hash(BLAKE3/SHA256)の詳細行を追加し、 サンプル画像をクリックで別タブ表示できるようにした。バッチ POST API の model.id 欠落による モデルページ URL の壊れを修正。CivitAI タブの 3 状態 UI と再描画タイミングも改善。

変更内容

py/services/civitai_service.py

_extract_info() — fileHashes フィールドを追加

  • プライマリファイルの hashes オブジェクト(BLAKE3 / SHA256 / AutoV2 等)を fileHashes として保存
  • 新規取得・更新後はサイドパネルで各ハッシュ値を表示できる

_extract_info() — modelId フォールバックを追加

  • バッチ POST API(POST /model-versions/by-hash)はレスポンスの model オブジェクト内に id を含まない場合がある
  • model.get("id") or data.get("modelId") でトップレベルの modelId をフォールバックとして使用
  • これにより modelUrl が https://civitai.com/models?modelVersionId=XXXX(モデル一覧ページ)になる問題を修正

static/js/models-tab.js

renderCivitaiInfo() — 詳細行を追加

追加要素 内容
Type info.type を大文字バッジで表示("CHECKPOINT"、"LORA" 等)
Base Model info.baseModel をラベル付き行で表示(旧サブタイトルから分離)
Hash BLAKE3 優先・SHA256 フォールバック。先頭 16 文字を表示、クリックでフルハッシュをクリップボードコピー
  • ローカルメタデータの sha256(既存キャッシュでも利用可能)を SHA256 として自動補完
  • 既存キャッシュで modelUrl が壊れている場合も info.modelId + info.versionId から URL を再構築(更新不要)
  • サンプル画像を <a target="_blank"> でラップ → クリックで原寸画像を別タブに表示
  • モデル名サブタイトル行から baseModel を除去(Detail 行に移動)

renderSideCivitai() — 3 状態 UI に変更

  • sha256 なし → 「CivitAIから取得」ボタン(未確認)
  • sha256 あり・キャッシュなし → 「確認済みですが、CivitAIに見つかりませんでした。」+「再確認する」ボタン
  • キャッシュあり → CivitAI 情報を表示

CivitAI タブ切り替え時の再描画

  • サイドパネルのタブ切り替えハンドラで CivitAI タブをクリックした際に renderSideCivitai を呼び出すよう追加
  • バッチ取得後に状態が更新されていても、タブをクリックすれば必ず最新内容が表示される

static/js/i18n.js

新規文字列を追加(EN / JA / ZH): civitaiType / civitaiBaseModel / civitaiHashLabel / civitaiCopyHash / civitaiHashCopied / civitaiOpenImage


2026-05-31: v0.3.19 — CivitAI 連携強化(バッチ高速化・リトライ・APIキー・フィールド拡充)

概要

CivitAI API との連携を全面的に改善。バッチ取得を POST /model-versions/by-hash 一括リクエストに変更して大幅に高速化。 429/5xx エラーへの指数バックオフリトライを追加。APIキーをオプション対応し設定UIとエクスポート除外を実装。 取得フィールドを nsfwLevel・air・stats・updatedAt 等に拡充。

変更内容

py/services/civitai_service.py — 全面改修

バッチ高速化 (POST 一括取得)

  • _batch_fetch_post(sha256_hashes) を新設 — POST /model-versions/by-hash で最大 100 件を 1 リクエストで取得
    • レスポンスの files[].hashes.SHA256 でリクエストのハッシュと照合してキャッシュに保存
    • 100 件超は _BATCH_CHUNK_SIZE = 100 単位でチャンク処理
  • batch_fetch() を 2 フェーズ構成に変更
    • Phase 1 (hashing): 全モデルの SHA256 を計算。キャッシュ済みはここでスキップ
    • Phase 2 (fetching): 未キャッシュ分をまとめて _batch_fetch_post() に投げる
    • 以前の 1 件ずつ GET + 0.5 秒ウェイト方式を廃止 → 100 モデルで約 50 秒→数秒に短縮

429/5xx 指数バックオフリトライ

  • fetch_by_hash() および _batch_fetch_post() 両方に実装
  • 対象コード: {429, 500, 502, 503, 504}。1s → 2s → 4s、最大 3 回
  • 404 はリトライせず即 None を返す(従来通り)

APIキー対応

  • _get_api_key() を新設 — 環境変数 CIVITAI_API_KEY を優先し、なければ settings.json の civitai_api_key にフォールバック
  • _build_headers() を新設 — APIキーがあれば Authorization: Bearer ... ヘッダーを付与

ハッシュの大文字/小文字統一

  • キャッシュキー: 小文字 sha256_lower
  • API 送信 (GET URL パス・POST ボディ): sha256.upper()
  • get_cached() も .lower() を適用してキー不一致を防止

_extract_info() 拡張

追加フィールド 内容
nsfwLevel 数値 NSFW レベル(0〜6)
air AIR 識別子 (urn:air:sdxl:lora:civitai:xxx@yyy)
stats downloadCount / thumbsUpCount / thumbsDownCount
updatedAt バージョン最終更新日時
publishedAt バージョン公開日時
imageDetails 画像のurl・width・height・nsfwLevel を含む配列
fileMeta プライマリファイルのfp・size・format
  • images URLリストは後方互換のため維持

py/routes/settings_routes.py

  • _SETTINGS_EXPORT_EXCLUDE = {"civitai_api_key"} を定義
  • _build_export_bundle() でエクスポート時に settings.json から除外キーをフィルタリング — APIキーが誤って他人に渡るリスクを排除

static/js/settings-tab.js

  • Ollama 設定セクションの直後に CivitAI API Key セクションを追加
    • type="password" 入力フィールド(wfm-settings-civitai-api-key)
    • 保存ボタン → saveServerSettings({ civitai_api_key }) を呼び settings.json に保存

static/js/i18n.js

  • civitaiApiKeySetting / civitaiApiKeyHint / civitaiApiKeyPlaceholder / civitaiApiKeySave / civitaiApiKeySaved を EN・JA・ZH の 3 言語に追加

static/js/models-tab.js

  • バッチ完了 done イベント処理で data.hashes({モデル名: sha256})を直接 state.modelMetadata に適用
    • fetchModelMetadata() の結果に sha256 が含まれない場合のタイミング問題によるサイドパネル CivitAI タブ表示バグを修正

2026-05-31: v0.3.18 — Batch タブ追加・Models 複数選択強化

概要

GenerateUI タブに Batch サブタブを追加。チェックポイントの一括生成キューを 3 ペインで組み立てられる専用 UI を実装。 Models タブの複数選択バルクアクションバーにお気に入り・バッジ操作・Deselect All を追加。

変更内容

templates/index.html

  • GenerateUI サブタブナビ に <button data-subtab="batch">Batch</button> を追加(5 タブ目)
  • #wfm-gen-subtab-batch コンテンツを追加(3 ペインレイアウト)
    • 左ペイン (.wfm-batch-left): wfm-ckpt-search 検索入力 + wfm-ckpt-list フォルダツリー + All/None ボタン
    • 中央ペイン (.wfm-batch-center): 内部タブナビ(Checkpoint / Lora / Prompt / Workflow)+ Checkpoint タブにグループリスト #wfm-batch-group-list; Lora/Prompt/Workflow は "Coming soon" プレースホルダー
    • 右ペイン (.wfm-batch-right): Batch Queue (#wfm-batch-preview-count + #wfm-batch-preview-list)
  • Checkpoint Batch Panel の body を簡略化
    • 削除: wfm-ckpt-dropdown-wrap(フォルダツリー全体)・wfm-ckpt-batch-info
    • 残す: 有効/無効チェックボックスとプログレス表示(wfm-ckpt-batch-progress)のみ
  • ヘルプ更新
    • wfm-help-gen-3: 「4-tab」→「5-tab」、Batch タブを追記
    • wfm-help-gen-11: Checkpoint Batch をパネルのトグル説明のみに簡略化
    • wfm-help-gen-12〜14: Batch タブ 3 ペインの説明を新規追加
    • wfm-help-models-11: バルクアクションの全ボタン(Deselect All / ★ / ☆ / グループ / +Badge / −Badge / Delete)を網羅した説明に更新

static/js/generate-tab.js

状態管理の刷新

  • _batchGroupState の selectedModels: Set<modelName> を廃止
    • 旧: グループ選択時にメンバーをフラットな Set に追加 → グループのメンバー変更が反映されない
    • 新: selectedGroups: Set<groupName> + partialSelections: { groupName: Set<modelName> } に変更
  • _getSelectedGroupModels() 追加 — _batchGroupState.groups から常に最新のメンバーを解決して返す; グループのメンバーが変わっても自動反映
  • _getGroupSelCount(name) 追加 — グループの選択済みモデル数を返す(ヘッダー CB のカウント表示用)
  • _getSelectedCheckpoints() 更新 — 左ペイン選択 + _getSelectedGroupModels() を統合(重複排除)

Batch タブ初期化

  • _loadBatchCheckpointGroups() — /api/wfm/models/groups?type=checkpoint でグループを取得; 削除されたグループを selectedGroups / partialSelections からクリーンアップ; 完了後 _renderBatchGroupList() と _renderBatchPreview() を呼ぶ
  • _renderBatchGroupList() — グループリストを DOM 生成
    • グループヘッダー CB: selectedGroups に昇格/降格ロジック付き
    • 個別メンバー CB: グループ全体選択中に1つ外すと自動的に partialSelections に移行; 全員選択になると selectedGroups に昇格; 空になると partialSelections エントリ削除
  • initBatchTab() — 内部タブ切り替え・左ペイン検索 / All / None・Batch タブ表示時の _loadBatchCheckpointGroups() 呼び出しを登録
  • _renderBatchPreview() 追加 — Batch Queue 右ペインを更新(件数 + ファイル名リスト)

既存コード整理

  • _updateDropdownLabel() / _updateBatchInfo() を削除 → _renderBatchPreview() に統一
  • initCheckpointBatch() からドロップダウン開閉ロジック(wfm-ckpt-dropdown-* 関連)を削除; Pause/Resume ハンドラのみ残す
  • moveRawJsonToTab(): "feeder" に加えて "batch" も Raw JSON 非表示対象に追加
  • 初期選択状態を _ckptState = { mode: "none" } に変更(デフォルト全チェックなし)

static/js/models-tab.js

新規関数

  • bulkSetFavorite(isFav) — 選択中モデルのお気に入りを一括設定(既に同状態のモデルはスキップ)
  • bulkApplyBadge(badgeLabel, add) — 選択中モデルに指定バッジを一括追加 / 削除(重複・不在はスキップ)

renderBulkActionBar() 更新

  • Deselect All ボタンを件数表示直後に追加(clearSelection() 呼び出し)
  • ★ Favorite / ☆ Unfavorite ボタンを追加(グループ操作の前)
  • バッジセレクト (wfm-bulk-badge-select) + +Badge / −Badge ボタンを追加(バッジ未定義時は disabled)

static/js/i18n.js

EN / JA / ZH 全言語に以下を追加:

  • modelBulkDeselectAll — Deselect All / 選択解除 / 取消全选
  • modelBulkFavAdd / modelBulkFavRemove — ★ Favorite / ☆ Unfavorite
  • modelBulkFavDone / modelBulkUnfavDone — 完了トースト
  • modelBulkBadgeApply / modelBulkBadgeRemove — +Badge / −Badge ボタンラベル
  • modelBulkBadgeApplyDone / modelBulkBadgeRemoveDone — 完了トースト
  • modelBulkNoBadge — バッジ未選択プレースホルダー

static/css/main.css

.wfm-batch-* スタイル一式を追加(Feeder タブの直前):

  • .wfm-batch-layout — flex 3 ペインレイアウト
  • .wfm-batch-pane / .wfm-batch-left / .wfm-batch-center / .wfm-batch-right — ペイン幅・ボーダー
  • .wfm-batch-pane-header / .wfm-batch-toolbar / .wfm-batch-list — 左ペイン構造
  • .wfm-batch-inner-tab-nav / .wfm-batch-inner-tab / .wfm-batch-inner-content — 中央ペイン内部タブ
  • .wfm-batch-group-list / .wfm-batch-group-item / .wfm-batch-group-header / .wfm-batch-group-name / .wfm-batch-group-count / .wfm-batch-group-members — グループリスト
  • .wfm-batch-count / .wfm-batch-preview-list / .wfm-batch-preview-item — 右ペイン(Batch Queue)

README.md

  • バージョンバッジを 0.3.17 → 0.3.18 に更新
  • GenerateUI Tab: "4-tab" → "5-tab"、Checkpoint Batch の説明を簡略化、Batch タブの説明を追加
  • Models Tab: bulk operations の説明を全ボタン網羅に更新
  • Changelog に v0.3.18 エントリを追加

2026-05-31: v0.3.17 patch — SSLコンテキスト セキュリティ強化

概要

v0.3.17 のSSL修正で CERT_NONE(SSL検証無効)フォールバックがあり、MitM攻撃への脆弱性が指摘された。セキュリティレビューを経て当該フォールバックを削除し、SSL検証が常に維持されるよう修正。

変更内容

py/services/civitai_service.py

  • _make_ssl_context() のフォールバックロジックを変更
    • 変更前: 3段目フォールバックとして ctx.verify_mode = ssl.CERT_NONE(SSL検証無効)を設定していた
    • 変更後: CERT_NONE を削除。certifi・システムSSLの両方が失敗した場合は None を返し、urlopen(context=None) でPythonデフォルトのSSL検証(ssl.create_default_context() 相当)にフォールバック。SSL検証が無効になるパスを完全に排除

2026-05-30: v0.3.17 — SSL証明書エラー修正・Lora Manager対応

概要

2つのバグ修正。CivitAI APIへのリクエストでSSL証明書の検証エラーが発生する問題(Windows Portable Python環境で再現)の修正と、ComfyUI-Lora-ManagerのLoRAローダーノードをGenerateUIタブで正しく変換・生成できるよう対応。

変更内容

py/services/civitai_service.py

  • _make_ssl_context() ヘルパー関数を追加
    1. certifi がインストールされていればそのCA束を使用
    2. certifi がなければシステムデフォルトのSSLコンテキストを使用
    3. いずれも失敗した場合(Windows Portable Python等でCA束が壊れている環境)は CERT_NONE でフォールバック(ログ警告を出力)
  • _get_ssl_context() — モジュールレベルでSSLコンテキストを1回だけ生成してキャッシュ
  • fetch_by_hash() および download_image() の urlopen 呼び出しに context=_get_ssl_context() を追加
  • 修正対象エラー: SSL: CERTIFICATE_VERIFY_FAILED certificate verify failed: certificate has expired

static/js/comfyui-workflow.js

  • convertUiToApi() — Lora Loader (LoraManager) ノードの特殊ウィジェットマッピングを追加
    • ノードの properties.__lm_widget_ids(LoRA Managerが設定するウィジェット識別子配列)が存在する場合、widgets_values をその配列のインデックスで直接マッピング
    • loras キーの値(UI形式: 配列 [...])をAPI形式 {"__value__": [...]} にラップして出力
    • 修正前は object_info ベースのウィジェットマッピングが LoRA Manager の独自型(非 INT/FLOAT/STRING/BOOLEAN/COMBO)を認識できずスキップされていた

2026-05-20: v0.3.16 — CivitAIプレビューフォールバック表示

概要

CivitAI情報を取得済みのモデルで、ローカルのプレビューファイルがない場合(バックエンドのダウンロード失敗時など)でも、CivitAIキャッシュのimages[0]をブラウザが直接表示するフォールバックを追加。

変更内容

static/js/models-tab.js

  • loadPreviewImage() — onerror ハンドラーを拡張
    • APIプレビューが404の場合、state.modelMetadata[modelName].sha256 → state.civitaiCache[sha256].images[0] を参照してブラウザから直接表示
    • CivitAI画像URLも取得できない場合のみ「プレビューなし」プレースホルダーを表示
  • fetchCivitaiForModel() — preview_saved に依存せず常に更新
    • CivitAI情報取得成功時は preview_saved の値に関わらずサイドパネルと renderModelGrid() を更新
    • ローカル保存なし(preview_saved: false)の場合もサイドパネルに civitai.images[0] を直接セット

2026-05-20: v0.3.15 — サンプルワークフロー同梱・CivitAI自動プレビュー・Create Tagsオプション

概要

3つの機能追加。サンプルワークフローをパッケージに同梱、CivitAI情報取得時のプレビュー自動保存、AI TOOLタブのTOOLSペインにタグ作成タスクを追加。

変更内容

workflows/(新規)

  • SD1.5・SDXL・DWPose・Face Detailer・Image Editing の 13 ワークフロー(JSON + PNG サムネイル)を追加
  • Git 追跡対象に追加し、comfy node publish 時にパッケージへ同梱される

py/services/civitai_service.py

  • download_image(url, save_path, timeout=15) 静的メソッドを追加
    • urlopen で画像バイナリを取得し save_path に保存。失敗時は False を返しログ出力

py/routes/models_routes.py

  • handle_civitai_fetch(個別取得): CivitAI 情報取得成功後、find_preview_image でプレビューの有無を確認し、なければ images[0] を {model_stem}.preview.png として自動ダウンロード。レスポンスに preview_saved: true/false を追加
  • handle_civitai_batch(一括取得): バッチ完了後、CivitAI 情報があってプレビューのない各モデルに対して同様の自動ダウンロードを実行。サマリーに preview_saved 件数を追加

static/js/models-tab.js

  • 個別取得成功時: data.preview_saved が true の場合、サイドパネルのプレビュー画像を即時更新(&t= キャッシュバスター付き URL を再セット)し renderModelGrid() を呼び出してカードを更新
  • 一括取得完了時: done イベントのトーストに +N preview を付記(preview_saved > 0 の場合のみ)

static/js/ai-tab.js

  • VLM_PROMPTS に tags キーを追加: "Generate a list of descriptive tags for this image. Output only comma-separated tags in English, nothing else."

templates/index.html

  • SPA AI TOOL タブの #wfm-ai-vlm-task に <option value="tags">Create tags</option> を追加

web/comfyui/node_sets_menu.js

  • Library A タブの #wfm-nlp-ai-vlm-task に <option value="tags">Create tags</option> を追加
  • ローカルの VLM_PROMPTS に tags キーを追加

static/js/i18n.js

  • helpModels5 (EN/JA/ZH): 「プレビューなしモデルに自動ダウンロード」の説明を追記
  • helpAi3 (EN/JA/ZH): タスク一覧に「Create Tags / タグ作成 / 标签生成」を追加、ペイン名を「VLM サブタブ」→「TOOLS ペイン」に修正

README.md

  • バージョンバッジを 0.3.14 → 0.3.15 に更新
  • Sample Workflows セクションを追加(Installation と Usage の間)
  • Models Tab Features: CivitAI 関連行にプレビュー自動保存の説明を追記
  • AI TOOL Tab Features: TOOLS ペインのタスクに Create Tags を追加
  • Changelog に v0.3.15 エントリを追加

2026-05-20: AI TOOL タブ 英語化(SPA・Libraryパネル)

概要

SPA の AI TOOL タブおよびサイドパネル(Library)の AI タブで日本語固定になっていた静的ラベルをすべて英語に統一。他タブと同じ「英語固定の静的 HTML + JS 動的部分のみ t()」方式に揃えた。

変更内容

templates/index.html

  • ペインヘッダー: 翻訳 → Translation、設定 → Settings
  • 言語 option: 日本語 / 英語 / 中国語 → Japanese / English / Chinese
  • ボタン: 翻訳 → Translate、コピー → Copy、実行 → Run、接続テスト → Test connection、更新 → Refresh、設定を保存 → Save
  • タスク option: 画像の解説 → Describe image、プロンプト作成 → Create prompt
  • プレースホルダー・ラベル: 翻訳するテキストを入力... → Enter text to translate...、翻訳結果がここに表示されます... → Translation result...、実行結果がここに表示されます... → Result... 等
  • ドロップ説明: PNG / JPG / WebP をドロップまたはクリック → Drop or click PNG / JPG / WebP
  • 設定ラベル: バックエンド → Backend、接続設定 → Connection、モデル選択 → Model、Free 言語設定 → Free language、入力言語 → Source、翻訳言語 → Target

web/comfyui/node_sets_menu.js

  • サブタブラベル: 翻訳 → Translation、VLM → TOOLS、設定 → Settings
  • HTML 内ラベル: SPA と同内容をすべて英語化
  • JS 動的文字列: 翻訳中... → Translating...、接続中... → Connecting...、実行中... → Running...、設定を保存しました → Settings saved、コピーしました → Copied、モデルが見つかりません → No models found、画像をドロップしてください → Please drop an image 等

2026-05-19: v0.3.14 — AI TOOL タブ 3ペイン化・RAW JSON 文字色カスタマイズ

概要

AI タブの UI を刷新し、サブタブ廃止・3 ペイン同時表示レイアウトへ移行。また Settings タブに RAW JSON シンタックスハイライトの文字色カスタマイズ機能を追加。

変更内容

templates/index.html

  • タブボタンラベルを "A" → "AI TOOL" に変更
  • <section id="wfm-tab-ai"> をサブタブ構成から 3 ペインレイアウト に刷新
    • サブタブナビ(翻訳 / VLM / 設定ボタン)を削除
    • <div class="wfm-ai-pane wfm-ai-pane-translate"> — 翻訳ペイン(flex: 4)
    • <div class="wfm-ai-pane-divider"> — 縦区切り線
    • <div class="wfm-ai-pane wfm-ai-pane-tools"> — TOOLS ペイン(flex: 4、旧 VLM)
    • <div class="wfm-ai-pane-divider"> — 縦区切り線
    • <div class="wfm-ai-pane wfm-ai-pane-settings"> — 設定ペイン(flex: 2)
    • 各ペインに <div class="wfm-ai-pane-header"> を追加(翻訳 / TOOLS / 設定)
  • ヘルプタブ更新
    • wfm-help-sidepanel-16: 「AI TOOL tab: Translation and TOOLS (VLM) panes … 3-pane layout always visible」に変更
    • wfm-help-ai-title: "AI Tab (A)" → "AI TOOL Tab"
    • wfm-help-ai-1〜5: 3 ペインレイアウトの説明に書き直し(項目数 6→5)
    • wfm-help-settings-12 新規追加: RAW JSON Colors 設定の説明

static/css/main.css

  • #wfm-tab-ai の flex-direction を column → row に変更
  • サブタブ関連 CSS を削除: .wfm-ai-subtab-nav、.wfm-ai-subtab-btn(.active 含む)、.wfm-ai-subtab-content
  • 3 ペイン用 CSS を追加:
    • .wfm-ai-pane — display:flex; flex-direction:column; overflow:hidden; min-width:0
    • .wfm-ai-pane-translate { flex:4 } / .wfm-ai-pane-tools { flex:4 } / .wfm-ai-pane-settings { flex:2 }
    • .wfm-ai-pane-header — ペイン見出し(uppercase、border-bottom)
    • .wfm-ai-pane-divider — 幅 1px の縦区切り線

static/js/ai-tab.js

  • initSubTabs() 関数を削除(サブタブ切り替えロジック全体)
  • initAiTab() から initSubTabs() の呼び出しを削除
  • initTranslateTab()・initVlmTab()・initSettingsTab() はそのまま維持

static/js/i18n.js

  • tabAi を "A" → "AI TOOL" に EN/JA/ZH 全言語で変更(replace_all で 3 箇所一括)

static/js/settings-tab.js

  • JSON_COLOR_STYLE_ID 定数を追加 ("wfm-json-color-style")
  • JSON_COLOR_DEFS 配列を追加(6 色定義: base / yellow / pink / green / cyan / red)
    • 各要素: { id, label: () => t(key) || fallback, def: "#xxxxxx" }
  • export function applyJsonColors(colors) を追加
    • <style id="wfm-json-color-style"> を動的生成・更新
    • .wfm-json-highlight・.json-key-*・.json-val-* の 6 クラスを上書き
    • colors が undefined のときはすべてデフォルト値を使用
  • 設定 HTML テンプレートに <!-- RAW JSON Colors --> セクションを追加
    • <details> アコーディオン、<div id="wfm-json-color-grid"> に 6 個のカラーピッカー
    • <button id="wfm-json-color-reset"> でデフォルト復元
  • イベントハンドラを追加
    • #wfm-json-color-grid の input イベント: 色変更を即時反映 + jsonColors キーとして localStorage 保存
    • #wfm-json-color-reset の click イベント: jsonColors キー削除・UI リセット・applyJsonColors(undefined) 呼び出し

static/js/app.js

  • import に applyJsonColors を追加
  • 起動時ブロックに applyJsonColors(_s.jsonColors) を追加(テーマ・フォントサイズと同様に DOMContentLoaded 前に適用)

2026-05-19: v0.3.13 — AI タブ(A)追加

概要

SPA および Workflow Studio Library サイドパネルの両方に AI タブ(A)を追加。Ollama・LM Studio をバックエンドとして翻訳・VLM(画像解析)機能を提供する。設定は localStorage 経由で両インターフェース間で共有。

変更内容

templates/index.html

  • タブバーに <button class="wfm-tab" data-tab="ai">A</button> を右端に追加
  • <section id="wfm-tab-ai"> を新規追加(3 サブタブ構成)
    • 翻訳サブタブ (wfm-ai-subtab-translate): 入力テキストエリア・言語セレクター(日/英/中/Free)・⇄ 入替ボタン・翻訳ボタン・出力テキストエリア・コピーボタン・ステータス表示
    • VLM サブタブ (wfm-ai-subtab-vlm): ドロップゾーン(110px)・タスクドロップダウン(画像の解説/プロンプト作成)・実行ボタン・結果エリア・コピーボタン
    • 設定サブタブ (wfm-ai-subtab-settings): バックエンド選択ラジオ・API URL 入力・接続テストボタン・モデル選択+更新ボタン・Free 言語入力(入力/出力)・保存ボタン
  • ヘルプタブにAI タブ説明カード追加、Sidepanel カードに wfm-help-sidepanel-16 を追加

static/js/ai-tab.js(新規)

  • import { showToast } from "./app.js" / import { t } from "./i18n.js"
  • 定数: SETTINGS_KEY = "wfm_ai_settings"、LANG_NAMES、VLM_PROMPTS(describe / prompt)
  • isValidBackendUrl(url): new URL() で http: / https: スキームのみ許可
  • fileToBase64(file): FileReader → { base64, mimeType }
  • callVLM(): Ollama は images:[b64]、LM Studio は image_url コンテンツブロック
  • callLLM(): テキスト翻訳用 LLM 呼び出し(Ollama / LM Studio)
  • fetchModels(): Ollama /api/tags、LM Studio /v1/models からモデル一覧取得
  • buildTranslationPrompt(): Free 言語設定対応の翻訳プロンプト生成
  • initSubTabs() / initTranslateTab() / initVlmTab() / initSettingsTab()
  • すべての動的文字列を t() 経由で i18n 対応済み
  • export function initAiTab(): 全 init 関数のエントリポイント

static/js/app.js

  • import { initAiTab } from "./ai-tab.js" を追加
  • DOMContentLoaded 内に initAiTab() を追加
  • tabMap に ai: "tabAi" を追加
  • applyI18nToHtml() に AI タブの全要素 id マッピングを追加
    • サブタブボタン、言語セレクター option、placeholder、ボタンテキスト、セクションタイトル、ラベル
    • ヘルプタブ新規 id(wfm-help-sidepanel-16、wfm-help-ai-title、wfm-help-ai-1〜6)

static/js/i18n.js

  • tabAi: "A" を EN/JA/ZH に追加(タブボタンラベル)
  • AI タブ全キーブロックを EN/JA/ZH に追加(計 3 言語 × 約 40 キー)
    • サブタブ名、言語オプション、placeholder、ボタンテキスト、ステータス文字列、トースト文字列
    • aiToastNoText、aiToastNoModel、aiToastInvalidUrl、aiToastInvalidUrlInput
    • aiStatusTranslating、aiStatusRunning、aiStatusDone、aiStatusConnecting、aiStatusConnectOk、aiStatusConnectFail
    • aiToastTransFailed、aiToastNoCopyText、aiToastCopied、aiToastNoModels、aiToastModelsFailed
    • aiToastSettingsSaved、aiToastNoImage、aiToastVlmFailed、aiModels
    • ヘルプキー: helpSidepanel16、helpAiTitle、helpAi1〜helpAi6
  • ollamaSettings を EN/JA/ZH で "(プロンプトタブ)" サフィックス付きに改名

static/css/main.css

  • AI サブタブナビ: .wfm-ai-subtab-nav、.wfm-ai-subtab-btn(.active 状態含む)、.wfm-ai-subtab-content
  • 翻訳タブ: .wfm-ai-trans-container、.wfm-ai-trans-lang-row、.wfm-ai-lang-select、.wfm-ai-swap-btn、.wfm-ai-trans-textarea、.wfm-ai-trans-output、.wfm-ai-trans-actions、.wfm-ai-trans-status
  • ステータス色: .wfm-ai-status-working(amber)、.wfm-ai-status-ok(green)、.wfm-ai-status-error(red)
  • VLM タブ: .wfm-ai-vlm-container、.wfm-ai-vlm-drop(height: 110px、破線ボーダー、I タブと統一)、.wfm-ai-vlm-label、.wfm-ai-vlm-preview、.wfm-ai-vlm-task-row、.wfm-ai-vlm-result、.wfm-ai-vlm-copy-btn
  • 設定タブ: .wfm-ai-settings-* 各セクション

web/comfyui/node_sets_menu.js

  • state に aiSubTab: "ai-translate" を追加
  • トップタブバーに data-toptab="ai" の A ボタンを I タブの右に追加
  • rebuildSubTabs(): AI ケースを追加(翻訳/VLM/設定 サブタブ)
  • renderContent(): topTab === "ai" 分岐を追加、検索バー非表示、renderAiTab() を呼び出し
  • 追加関数:
    • isValidAiUrl(): URL セキュリティ検証
    • loadAiCfg() / saveAiCfg(): localStorage への設定読み書き(SPA と共有)
    • aiFileToBase64() / aiCallVLM() / aiCallLLM() / aiFetchModels(): バックエンド API ラッパー
    • renderAiTab(container): 初回のみ HTML を構築し setupAiHandlers() を呼び出す
    • renderAiSubContent(): サブタブに応じてペインを表示切替
    • setupAiHandlers(container): 翻訳・VLM・設定の全イベントハンドラ
  • injectStyles(): AI タブ CSS を追加(VLM ドロップゾーン高さ 110px を含む)

docs/6_ws_library.png

  • A タブ追加後の Library パネルスクリーンショットに更新

README.md

  • バージョンバッジを 0.3.13 に更新
  • Features に AI Tab (A) セクションを追加
  • Workflow Studio Library のタブ説明を W/N/P/M/I → W/N/P/M/I/A に更新、A タブ説明を追加
  • Requirements — Optional に LM Studio を追記
  • Project Structure に ai-tab.js を追加
  • Changelog に v0.3.13 エントリを追加

2026-05-18: v0.3.12 リリース — README スクリーンショット刷新 / workflow_analyzer 拡張

概要

  • README Screenshots を旧タブ別画像(12枚)から機能特徴別の新画像(8枚)に刷新
  • workflow_analyzer.py に CLIPLoader type フィールド対応・新モデル種別検出を追加(NewBie / Ovis / HiDream / Wan / Cosmos / Lumina 等)

変更内容

README.md

  • バージョンバッジを 0.3.12 に更新
  • Screenshots セクションを全面刷新
    • 旧: docs/screenshot_*.png 参照の 6 テーブル(タブ別 12 枚)
    • 新: docs/1_workflowtab.png〜docs/8_Customize.png の機能特徴別 8 枚を 2 カラムグリッドで表示
    • キャプションはファイル名のプレフィックスを除いて整形(例: 1_workflowtab → Workflow Tab)
  • v0.3.12 changelog エントリを新規追加(RAW JSON 検索 + workflow_analyzer 拡張)

docs/

  • 旧スクリーンショット 11 枚(screenshot_*.png)を削除
  • 新スクリーンショット 8 枚(1_workflowtab.png〜8_Customize.png)を追加

py/services/workflow_analyzer.py

_CLIP_TYPE_TO_MODEL 追加

  • CLIPLoader の type 文字列("flux", "hidream_i", "wan", "cosmos" 等)→ モデル種別名のマッピング辞書

_clip_type_from_ui_node(node) 追加

  • UI フォーマットノードの widgets_values から CLIPLoader / DualCLIPLoader / TripleCLIPLoader の type 文字列を取得

_collect_all_ui_nodes(workflow_data) 追加

  • nodes[] だけでなく definitions.subgraphs[].nodes[] も走査するジェネレータ
  • サブグラフ内のローダーノードも model_type 検出の対象になった

_detect_model_type_from_name(mn_base, model_types) 追加

  • モデルファイル名から種別を判定するヘルパー関数に切り出し(従来はインライン記述)
  • 追加対応: NewBie / Ovis / HiDream / Wan(wan-N / wan-video パターン)

analyze_workflow() 改修

  • ファイル名検出に NewBie / Ovis / HiDream / Wan を追加
  • UnetLoaderGGUF 等を含む UnetLoader 系を一括マッチ ("UnetLoader" in ntype)
  • CLIPLoader 系ノードで clip_type → _CLIP_TYPE_TO_MODEL → モデル種別を検出
  • _model_name_from_api_node に clip_name1 / clip_name を追加(CLIPLoader の API フォーマット対応)
  • node_iter のタプルに clip_type を追加((ntype, title, mn, clip_type))
  • タイトル検出に HiDream / Ovis / NewBie を追加

2026-05-17: RAW JSON パネルに検索機能を追加

概要

生成 UI タブの RAW JSON パネルに VSCode 風の検索バーを追加。常時表示で、マッチ間ナビゲーションとクリアボタンを備える。

変更内容

templates/index.html

  • #wfm-gen-rawjson-widget 内にヘッダーとエディターの間で .wfm-rawjson-search バーを追加
    • 検索入力 (#wfm-gen-raw-search)
    • マッチ数表示 (#wfm-gen-raw-search-count、例: 3/12)
    • 前へ ↑ / 次へ ↓ ボタン
    • ✕ クリアボタン
  • JSON コンテナ内に検索ハイライト専用オーバーレイ <pre id="wfm-gen-raw-json-search-overlay"> を追加(シンタックスハイライト層と textarea 層の間)

static/css/main.css

  • .wfm-rawjson-search — 検索バー全体のレイアウト
  • .wfm-rawjson-search-input — 検索入力フィールド(フォーカス時 accent カラーのボーダー)
  • .wfm-rawjson-search-count — マッチ数表示(min-width: 56px、中央揃え)
  • .wfm-rawjson-search-btn / .wfm-rawjson-search-clear — ナビ・クリアボタン
  • .wfm-json-search-overlay — 文字色を透明にし <mark> の背景のみを表示するオーバーレイ
    • .wfm-search-match — 通常マッチ: 黄色半透明
    • .wfm-search-current — 現在マッチ: オレンジ強調 + アウトライン
  • .wfm-json-editor の z-index を 2 → 3 に更新(オーバーレイが z-index: 2 を占有するため)

static/js/generate-tab.js

既存の「Raw JSON highlight sync on input/scroll」ブロックを拡張:

  • updateSearchOverlay() — 検索語でテキストを走査し matchPositions[] を構築、オーバーレイ HTML を生成
    • マッチなし時: カウント欄を赤字で "No results" 表示
    • 現在マッチへ自動スクロール + テキストエリアのカーソルをマッチ位置に移動
  • スクロール同期: エディター scroll イベントでオーバーレイの scrollTop/Left を追従
  • エディター入力時: シンタックスハイライト更新と並行して検索オーバーレイも更新
  • キーボード: Enter(次へ)/ Shift+Enter(前へ)/ Escape(クリア)対応

2026-05-17: v0.3.11 Metadata タブ対応ノードタイプ拡張 / Settings テキストサイズ UI 追加

概要

  • metadata-tab.js(Metadata タブ本体)に UnetLoaderGGUF / UNETLoaderGGUF / QuadrupleCLIPLoader 対応を追加
    • node_sets_menu.js の I タブには v0.3.10 で追加済みだったが、Metadata タブには未反映だった
  • Settings タブに「Text Size」セクションを追加 — プロンプト・チャット系の全テキストエリアのフォントサイズを一括変更するスライダー

変更内容

static/js/metadata-tab.js

extractDiffusionModels 拡張

  • UNETLoader のみ → UNETLoader / UnetLoaderGGUF / UNETLoaderGGUF に拡張(Set を使った判定に変更)
  • HiDream i1 Full GGUF など GGUF 形式の Diffusion Model が Metadata タブでも正しく読み取れるようになった

extractTextEncoders 拡張

  • QuadrupleCLIPLoader(clip_name1〜clip_name4)を追加、LiteGraph 形式・API 形式の両方に対応
  • HiDream の 4-CLIP(clip_l / clip_g / t5xxl / llama_3.1)が全件表示されるようになった

static/js/settings-tab.js

applyTextareaFontSize(size) 追加(export)

  • <style id="wfm-ta-font-size-style"> を <head> に注入
  • 対象セレクタ(対象を 8 箇所に一括適用):| 対象 | ID | | ---------------------------------------- | -------------------------- | | Generate UI — Positive Prompt | #wfm-prompt-pos-text | | Generate UI — Negative Prompt | #wfm-prompt-neg-text | | Prompt タブ — AI Assistant チャット | #wfm-ollama-input | | Prompt タブ — Preset Positive | #wfm-preset-pos | | Prompt タブ — Preset Negative | #wfm-preset-neg | | Prompt タブ — Wildcard プロンプト | #wfm-wc-prompt | | Prompt タブ — Wildcard ファイルエディタ | #wfm-wc-editor-content | | Metadata タブ — PROMPT 全文プレビュー | #wfm-meta-prompt-full |
  • 範囲: 10〜28px(clamp)、デフォルト 13px

Settings タブ HTML「Text Size」セクション追加

  • Language Settings の下、Workflow Data Folder の上に新規 <details> セクション
  • <input type="range"> スライダー (min=10, max=28, step=1) + px 値表示
  • スライダー操作でリアルタイム適用
  • 「Save Settings」ボタンで textareaFontSize を localStorage に保存

static/js/app.js

  • applyTextareaFontSize をインポート
  • ページ読み込み直後(テーマ適用と同タイミング)に localStorage の保存値を読み込んで適用

templates/index.html

  • wfm-help-metadata-5: 対応ローダーを明記(UNETLoader / UnetLoaderGGUF / UNETLoaderGGUF / CLIPLoader / DualCLIPLoader / TripleCLIPLoader / QuadrupleCLIPLoader)
  • wfm-help-settings-11 新規追加: Text Size スライダーの説明

検証済みファイル

ファイル Metadata タブでの検出内容
n-hidream_i1_full.png Diffusion Model:hidream-i1-full-Q5_0.gguf(UnetLoaderGGUF)、Text Encoder: 4 件(QuadrupleCLIPLoader)、VAE: 1 件

2026-05-17: v0.3.10 Information タブ キャンバスドラッグ対応 / ノードタイプ拡張 / バグ修正

概要

  • サイドパネル I タブ(model / lora / Prompts)からキャンバスへのドラッグ&ドロップでノードを配置できるようにした
  • Multiple LORA セクションの追加(全 LoRA を Lora Loader (LoraManager) に一括配置)
  • UnetLoaderGGUF / QuadrupleCLIPLoader 対応(HiDream 等の GGUF / 4-CLIP ワークフロー)
  • CLIPLoader のウィジェット名誤りを修正
  • プレビューエリアの高さを固定し、モデル/プロンプトリストが潰れる問題を修正

変更内容

web/comfyui/node_sets_menu.js

キャンバスドラッグ対応(新規)

  • placeClipTextEncodeNode(text, pos) 追加 — CLIPTextEncode ノードをテキスト入力済みで配置
  • placeLoraMgrNode(loras, pos) 追加 — Lora Loader (LoraManager) ノードを全 LoRA データ付きで配置
  • キャンバスドロップハンドラに新 MIME タイプを追加:
    • application/x-wfm-lora-multi → placeLoraMgrNode
    • application/x-wfm-clip-text → placeClipTextEncodeNode

renderInfoModels 更新

  • 各セクションに modelType(checkpoint / vae / unet / textencoder)を追加
  • アイテムを draggable = true に設定、dragstart で application/x-wfm-model を送信
  • ダブルクリックで placeModelNode 呼び出し

renderInfoLoras 更新

  • 各 LoRA アイテムを draggable = true に設定、dragstart で application/x-wfm-model(type: "lora")を送信
  • ダブルクリックで placeModelNode 呼び出し
  • Multiple LORA セクション追加 — LoRA が 1 件以上あれば下部に表示
    • application/x-wfm-lora-multi で全 LoRA データを転送
    • ダブルクリックで placeLoraMgrNode 呼び出し

renderInfoPrompts 更新

  • 各プロンプトアイテムを draggable = true に設定、dragstart で application/x-wfm-clip-text を送信
  • ダブルクリックで placeClipTextEncodeNode 呼び出し

_extractDiffusionModels 更新

  • UNETLoader のみ → UNETLoader / UnetLoaderGGUF / UNETLoaderGGUF に拡張(GGUF 形式対応)

_extractTextEncoders 更新

  • QuadrupleCLIPLoader(clip_name1〜clip_name4)を追加(HiDream 等 4-CLIP モデル対応)
  • LiteGraph 形式・API 形式の両方に対応

バグ修正: MODEL_NODE_MAP の textencoder ウィジェット名

  • clip_name1(DualCLIPLoader のウィジェット名)→ clip_name(CLIPLoader の正しいウィジェット名)に修正
  • これにより Text Encoder ドラッグ時に正しいモデルファイル名が設定される

プレビューエリア高さ固定

  • .wfm-nlp-info-drop を min-height: 54px(可変)→ height: 110px(固定)に変更
  • 大きな画像ロード時にモデル/プロンプトリストが圧迫される問題を解消

CSS 追加

  • .wfm-nlp-info-item--draggable — cursor: grab、ホバー背景、ドラッグ中 opacity: 0.5
  • .wfm-nlp-info-prompt-item — cursor: pointer → cursor: grab に変更、user-select: none 追加

ヘルプ更新

  • index.html: wfm-help-sidepanel-13 更新、wfm-help-sidepanel-14 / 15 新規追加
  • app.js: i18n マッピングに 14 / 15 追加
  • i18n.js: EN / JA / ZH 3 言語すべてに helpSidepanel13 更新と helpSidepanel14 / 15 追加

対応確認済みワークフロー

ファイル 検出内容
non-hidream_i1_full.png Diffusion Model:hidream-i1-full-Q5_0.gguf(UnetLoaderGGUF)、Text Encoder: 4 件(QuadrupleCLIPLoader)、VAE: 1 件

2026-05-16: v0.3.9 サイドパネル I タブ追加 / トップバーアイコン修正

概要

  • ComfyUI サイドパネルのタブを W / N / P / M / I の 5 タブ構成に変更し、I タブ(Information / Metadata)を追加
  • トップバーアイコン(Workflow Studio / カメラ / Node Sets)が表示されない不具合を修正

変更内容

web/comfyui/node_sets_menu.js

タブラベル短縮(□スタイル)

  • Workflows → W、Nodes → N、Prompts → P、Models → M
  • ホバー時に title 属性でフルネームを表示
  • CSS: 細ボーダー + アクティブ時に青ボーダーの□スタイル

I タブ(Information / Metadata)追加

  • サブタブ: model / lora / Prompts
  • ファイルドロップエリア(常時表示、クリックでファイル選択も可)
  • ファイル情報行(ファイル名・サイズ・形式、常時表示)
  • model: Checkpoint / VAE / Diffusion Model / Text Encoder(LoRA 除く)
  • lora: LoRA 名 + strength_model / strength_clip 値
  • Prompts: POS / NEG バッジ付きリスト → クリックで全文プレビュー(height: 160px)+ Copy ボタン(1.2 秒「Copied!」表示)
  • メタデータ解析ロジックは metadata-tab.js から移植(関数名に _ プレフィックス付き)
  • ドロップエリア min-height: 54px、プレビューエリア height: 160px

web/comfyui/top_menu_extension.js

バグ修正: トップバーアイコン非表示

  • 原因: applyButtonIcon() 内の if (button.querySelector("svg")) return チェックにより、 新しい ComfyUI が Iconify で MDI クラス(icon-[mdi--...])をインライン SVG に変換した際、 すでに SVG が存在すると判定されてカスタム SVG への置換がスキップされていた
  • 修正: 早期リターンチェックを削除し、常に innerHTML を上書きするよう変更
  • MutationObserver ベースの監視を廃止し、lora-manager と同パターンの requestAnimationFrame リトライ方式に統一
    • ボタン未発見時のみリトライ(発見後はリトライを停止)

2026-05-16: v0.3.8 Metadata タブ: data9 対応 / プロンプト判別不能テキスト

概要

data9 の新規ワークフロー(Flux.2 Dev fp8、Flux.2 Klein 4B Distilled、Ernie Image、 Qwen-Image-Edit 2511、WAN2.2 14B Animate)の Metadata タブ表示に対応。 あわせて、ポジティブ / ネガティブの判別ができないプロンプトを Text(バッジなし) として 区別して表示する仕様に変更した。

問題と原因

ファイル 問題
image_flux2_fp8.json SamplerCustomAdvanced に positive/negative 入力ポートがなく、リンク解決でプロンプトが空になる
image_ernie_image.json PrimitiveStringMultiline がサブグラフ内にあり、ステップ4(トップのみ対象)で見つからない
image_qwen_image_edit_2511.json TextEncodeQwenImageEditPlus → FluxKontextMultiReferenceLatentMethod → KSampler という中間ノード経由で textMap にプロンプトが登録されない
video_wan2_2_14B_animate.json トップレベルの CLIPTextEncode とサブグラフ内の KSampler がクロスレベル接続され、単一レベル解析では取得できない

変更内容

static/js/metadata-tab.js

extractPromptsFromNodeSet

  • CLIPTextEncode のテキスト入力リンクのソースノードのタイプ制限を撤廃
    • 旧: isPromptStylerNode または WFS_PromptText のみ
    • 新: PromptStyler 以外は任意ノードの widgets_values[originSlot](ComfySwitchNode、PrimitiveStringMultiline 等を包含)
  • テキスト入力名に "prompt" を追加(TextEncodeQwenImageEditPlus 対応)
  • foundSampler = true かつ pos / neg が両方空の場合(SamplerCustomAdvanced 等)、 textMap のテキストを { positives: [], negatives: [], texts: [...] } として返すよう変更 (旧: return null)

extractPromptsLiteGraph

  • ステップ4 PrimitiveStringMultiline — nodes(トップのみ)→ collectAllNodes(wf)(サブグラフ含む)に変更
    • 戻り値: { positives: primTexts, negatives: [] } → { positives: [], negatives: [], texts: primTexts }
  • ステップ7 最終フォールバック — nodes(トップのみ)→ collectAllNodes(wf)(サブグラフ含む)に変更
    • 戻り値: { positives: all, negatives: all } → { positives: [], negatives: [], texts: all }

extractPromptsAPI

  • フォールバック時の戻り値: { positives: all, negatives: all } → { positives: [], negatives: [], texts: all }

UI

  • buildPromptItem: type === "text" のときバッジ(POS/NEG)を表示しない
  • buildPromptItem: fullLabel に t("metaPromptText") を使用
  • handleFile: (meta.texts ?? []).map(p => ({ type: "text", text: p })) を allPrompts に追加

static/js/i18n.js

  • metaPromptText を EN / JA / ZH に追加("Text" / "テキスト" / "文本")

対応済みワークフロー(data/data9)

ファイル 適用ステップ プロンプト結果
image_flux2_fp8.json step5(サブグラフ + SamplerCustomAdvanced fallback) texts(バッジなし)
image_flux2_klein_image_edit_4b_distilled.json step5(サブグラフ CLIPTextEncode + KSampler) texts(バッジなし)
image_ernie_image.json step4(PrimitiveStringMultiline in subgraph) texts(バッジなし)
image_qwen_image_edit_2511.json step5(TextEncodeQwenImageEditPlus fallback) texts(バッジなし)
video_wan2_2_14B_animate.json step7(全ノード fallback) texts(バッジなし)

2026-05-15: v0.3.7 Metadata タブ: Flux2 / Qwen / Z-Image 対応

概要

ComfyUI 公式テンプレート(Flux.2 Dev/Klein、Qwen-Image-Edit、Z-Image)で使われる サブグラフ(subgraph)形式ワークフローの Metadata タブ読み込みに対応。

  • definitions.subgraphs 内の UNETLoader / CLIPLoader / VAELoader / LoraLoaderModelOnly からモデルを抽出
  • MarkdownNote ノードの **section** → - [model](url) パターンからモデルを補完(フォールバック)
  • サブグラフ内の CLIPTextEncode + KSampler リンクを辿ってプロンプトを取得
  • PrimitiveStringMultiline ノード(flux2-klein など)からプロンプトを取得

変更内容

static/js/metadata-tab.js

  • extractPromptsFromNodeSet(nodes, links) 新規追加 CLIPTextEncode + KSampler によるプロンプト抽出ロジックを汎用化。トップレベル・サブグラフの両方で再利用
  • extractMarkdownNoteModels(wf) 新規追加 MarkdownNote の **section** → - [name](url) を正規表現で解析し、text_encoders / diffusion_models / vae / loras を抽出
  • extractCheckpoints / extractVAEs を collectAllNodes() 使用に修正 サブグラフ内のノードも走査対象に
  • extractPromptsLiteGraph を 7 段階フォールバック構成に再設計
    1. ImageMetadataPromptLoader → 2. WFS_PromptText → 3. トップレベルCLIPTextEncode+KSampler →
    2. PrimitiveStringMultiline → 5. サブグラフCLIPTextEncode+KSampler(新規) →
    3. PromptStyler → 7. 全CLIPTextEncodeテキスト
  • fromWorkflow に MarkdownNote フォールバック補完を追加

対応済みワークフロー(data/data8)

ファイル モデル抽出 プロンプト抽出
image_flux2_fp8.json UNETLoader + CLIPLoader + VAELoader + LoraLoader (subgraph) CLIPTextEncode (subgraph)
image_flux2_klein_text_to_image.json UNETLoader + CLIPLoader + VAELoader (subgraph) PrimitiveStringMultiline
image_qwen_image_edit.json UNETLoader + CLIPLoader + VAELoader + LoraLoader (subgraph) なし(画像入力のみ)
image_qwen_image_edit_2511.json 同上 なし
image_qwen_image_layered.json UNETLoader + CLIPLoader + VAELoader (subgraph) PrimitiveStringMultiline
image_z_image.json UNETLoader + CLIPLoader + VAELoader (subgraph) CLIPTextEncode (subgraph)
image_z_image_turbo.json 同上 CLIPTextEncode (subgraph)

static/js/i18n.js + templates/index.html

  • metaFormatTodo を「Flux.2 / Qwen-Image / Z-Image サブグラフ形式に対応済み」に更新(EN/JA/ZH)
  • helpMetadata5 の対応フォーマット一覧を更新し Flux.2 / Qwen-Image / Z-Image を明記(EN/JA/ZH)
  • index.html フォールバックテキストも同内容に更新

2026-05-15: v0.3.6 Metadata タブ追加

概要

  • PNG / WebP / JSON からモデル・LoRA・プロンプト情報を抽出・表示する Metadata タブを追加
  • タブ位置は Prompt タブの右隣(Prompt → Metadata → Gallery)
  • プロンプト抽出ロジックを改善し SDXL Prompt Styler を使用したワークフローにも対応
  • 抽出プロンプトを GenerateUI タブ・Prompt タブのプリセットへワンクリックで転記するボタンを追加
  • 左ペインに対応フォーマット説明と今後の対応予定ノートを追加
  • ヘルプタブに Metadata タブの説明カードを追加(EN/JA/ZH i18n 対応)

変更内容

static/js/metadata-tab.js — 新規作成

メタデータ抽出ロジック(model-and-prompt-from-metadata の workflow_utils.js ベース)

  • sanitizeJSON(text) — JSON に含まれる NaN / Infinity を null に置換
  • readWebPEXIFChunk(file) — WebP RIFF チャンクから EXIF を読み取る
  • extractWorkflowFromEXIF(exifBytes) — EXIF バイト列から workflow: / prompt: キーを検索してワークフロー JSON を返す
  • readAllPNGTextChunks(file) — PNG tEXt / iTXt チャンクをすべて読み取って { keyword: text } マップを返す(IEND 以降のチャンクも読む)
  • extractCheckpoints / extractVAEs / extractDiffusionModels / extractTextEncoders / extractLoRAs — LiteGraph 形式・API 形式の両方に対応したモデル抽出関数群
  • extractPrompts(wf) — 形式を判定して extractPromptsLiteGraph / extractPromptsAPI に振り分け
  • resolveLinkedText(wf, srcId, slot) — API 形式でのリンク参照 [nodeId, slot] を解決。PromptStyler 系ノードは text_positive / text_negative を slot に応じて返す
  • extractPromptsAPI(wf) — CLIPTextEncode の text 入力がリンク参照(配列)の場合に resolveLinkedText で解決するよう修正
  • extractPromptsLiteGraph(wf) — linkSlot マップを追加(リンクの origin_slot を追跡); CLIPTextEncode の inputs にリンクがある場合に PromptStyler / WFS_PromptText の widgets_values[originSlot] を解決; フォールバック時にも PromptStyler ノードを直接スキャン
  • SD WebUI / SD Forge / Fooocus の parameters チャンク解析(parseSDAParameters, parseFooocusMetadata)
  • extractAllMetadata(file) — PNG / WebP / JSON を統合して { source, checkpoints, vaes, diffusionModels, textEncoders, loras, positives, negatives } を返す

UI

  • buildModelItem(label) — モデル名アイテム(テキストオーバーフロー省略)
  • buildLoRAItem(lora) — LoRA 名 + strength_model/strength_clip バッジ付きアイテム
  • buildPromptItem(label, type, full, ...) — POS/NEG バッジ付きクリッカブルアイテム。クリックで下部テキストエリアに全文を表示し選択ハイライト
  • renderSection(sectionEl, listEl, items, buildFn) — アイテムが空の場合は .wfm-meta-section-empty クラスを付与してリストを非表示
  • initMetadataTab() — タブ初期化
    • ドロップゾーン / クリックでファイル選択 / drag-over スタイル切り替え
    • 画像ファイルはプレビューとして表示、JSON はドロップラベルを維持
    • ファイル処理後に各セクションをレンダリング。最初の positive プロンプトを自動選択
    • セクションタイトル・フォーマットノート・ヘルプカードの i18n 適用
    • プロンプトアクションボタン:
      • Copy — navigator.clipboard.writeText でプレビュー内容をコピー
      • GenUI:P / GenUI:N — #wfm-prompt-pos-text / #wfm-prompt-neg-text に値をセット
      • Prompt:P / Prompt:N — #wfm-preset-pos / #wfm-preset-neg に値をセット
      • 成功時は緑のフラッシュアニメーション(wfm-meta-btn-flash)

templates/index.html

  • タブナビに <button class="wfm-tab" data-tab="metadata">Metadata</button> を Prompt と Gallery の間に追加
  • #wfm-tab-metadata セクションを追加(Prompt セクションの直後、Settings セクションの前):
    • .wfm-metadata-layout(3列グリッド: 220px | 1fr | 1fr)
    • Col1: ドロップゾーン(#wfm-meta-drop)+プレビュー画像+ファイル情報(#wfm-meta-file-info)+対応フォーマットノート(#wfm-meta-format-note)
    • Col2: Checkpoint / VAE / Diffusion Model / Text Encoder の各セクション
    • Col3: LoRA セクション+Prompt セクション(リスト+全文テキストエリア+アクションボタン行)
  • Help タブ: Prompt Tab カードと Settings Tab カードの間に Metadata Tab カードを追加(wfm-help-metadata-title/desc/1〜5)

static/js/app.js

  • import { initMetadataTab } from "./metadata-tab.js" を追加
  • tabMap に metadata: "tabMetadata" を追加
  • DOMContentLoaded 内に initMetadataTab() を追加(initPromptTab() の直後)

static/js/i18n.js

EN / JA / ZH の3言語に以下を追加:

  • tabMetadata — タブラベル
  • metaSectionCkpt / Vae / Diff / Te / Lora / Prompt — 各セクションタイトル
  • metaDropMain / metaDropSub — ドロップゾーンラベル
  • metaParsing / metaFileTooLarge / metaNoMetadata / metaParseError — ステータスメッセージ
  • metaPromptPositive / metaPromptNegative / metaSelectPrompt — プロンプトエリアラベル
  • metaFormatNoteTitle / metaFmtComfyui / metaFmtSdwebui / metaFmtFooocus / metaFormatTodo — フォーマットノート
  • helpMetadataTitle / helpMetadataDesc / helpMetadata1〜5 — ヘルプカード

static/css/main.css

以下のスタイルを末尾に追加:

  • #wfm-tab-metadata / .wfm-metadata-layout — 3列グリッドレイアウト
  • .wfm-metadata-col1/col2/col3 — 各列の flex レイアウト・ボーダー・スクロール
  • .wfm-metadata-drop-zone (+ .drag-over / :hover) — ドロップゾーンのボーダー・背景切り替え
  • .wfm-meta-drop-label / main / sub — ドロップラベル
  • .wfm-meta-preview-img — プレビュー画像(object-fit: contain)
  • .wfm-meta-file-info — ファイル情報テキスト
  • .wfm-meta-section (+ .wfm-meta-section-empty) — セクションコンテナ(空時はリストを非表示・タイトルを半透明)
  • .wfm-meta-section-title — セクション見出し(uppercase + letter-spacing)
  • .wfm-meta-list — スクロール可能アイテムリスト(max-height: 140px)
  • .wfm-meta-item / .wfm-meta-item-clickable (+ :hover / .selected) — アイテム行
  • .wfm-meta-item-name / badge — アイテム名・バッジ
  • .wfm-meta-badge-pos / neg — POS(緑)/ NEG(赤)バッジ
  • .wfm-meta-prompt-section / full-wrap / full-label / full — プロンプト全文エリア(min-height: 80px)
  • .wfm-meta-prompt-actions — ボタン行(flex wrap)
  • .wfm-meta-btn-flash — 成功時フラッシュアニメーション
  • .wfm-meta-format-note / note-title / format-list / format-todo — フォーマットノートスタイル

2026-05-09: v0.3.5 GenerateUI — Feeder サブタブ・ワークフロー解析精度向上

概要

  • comfyui-image-feeder カスタムノード対応の Feeder サブタブを GenerateUI タブに追加
  • analyzeWorkflow の精度向上(SDXL 多段 CONDITIONING チェーン BFS 展開、新ノード種対応、KSamplerAdvanced seed 修正)
  • Help タブに Feeder サブタブの説明カードを追加(EN/JA/ZH i18n 対応)

変更内容

static/js/feeder-tab.js — 新規作成

  • _s = { dir, images, selected (Set), presets, running } モジュール状態
  • _feederNodes() — currentWorkflow から class_type === "ImageFeeder" のノードを列挙
  • refreshFeederNodeList() — ノードセレクターを再描画(ワークフロー読み込み時に呼ばれる)
  • _loadFromNode(nodeId) — ノード inputs を左ペインの各フィールドに反映
  • _applyToNode(nodeId) — フィールド値を currentWorkflow のノード inputs に書き戻す
  • _applyToWorkflow() — Apply ボタン用(_applyToNode 呼び出し+toast)
  • フォルダツリー: _loadTree() / _renderTree() / _makeTreeRow() / _selectDir() / _highlightTreeRow() — /image_loop/tree API からツリーを描画
  • 画像グリッド: _loadImages() / _renderGrid() / _makeCard() / _refreshGridCbs() / _selectAll() / _deselectAll() — /image_loop/images API からサムネイルカードを描画、チェックボックスで選択管理
  • プレビュー: _showPreview() — カードクリックで右ペインに拡大プレビューと /image_loop/image_info の情報を表示
  • プリセット: _loadPresets() / _renderPresets() / _savePreset() / _applyPreset() / _deletePreset() — /image_feeder/presets API を使用
  • ステータスバー: _updateStatus() — 現在フォルダと選択枚数/総枚数を表示
  • Run ループ: _startRun() / _stopRun() / _setRunUI()
    • Run 前に WebSocket を接続し image_loop_node_sync メッセージを専用ハンドラで捕捉
    • 各反復で _applyToNode → comfyUI.generate() → 結果画像を右ペインに表示
    • comfyUI.generate には右ペインの seed mode/value を使用(KSampler のシードを正しく制御)
    • After gen モード (wfm-feeder-control-after):
      • loop: _lastSync.next_index で index を更新し無限継続(has_next=false でも折り返して継続)
      • increment: index を更新し has_next=false で自動停止
      • fixed: index を変えず同じ画像で繰り返し
    • 生成ごとに右ペインの seed 表示を更新
  • initFeederTab() — 全コントロールのイベントリスナーを登録し _loadTree() / _loadPresets() を並列実行

static/js/generate-tab.js

  • import { initFeederTab, refreshFeederNodeList } from "./feeder-tab.js" を追加
  • loadWorkflowIntoEditor() に refreshFeederNodeList() 呼び出しを追加
  • moveRawJsonToTab(): tabKey === "feeder" の場合は Raw JSON ウィジェットを非表示(feeder タブには rawjson-col がないため)
  • initGenerateTab() に await initFeederTab() を追加

templates/index.html

  • サブタブナビに <button data-subtab="feeder">Feeder</button> を追加
  • wfm-gen-subtab-feeder コンテンツ div を追加:
    • 左ペイン (.wfm-feeder-settings): ノードセレクター・Apply・全パラメータフィールド・プリセット管理・Run セクション(After gen セレクト + Run/Stop ボタン)
    • 中央ペイン (.wfm-feeder-library): ステータスバー + ライブラリ本体(フォルダツリー・画像グリッド・プレビューパネル)
  • Help タブ: GenerateUI Tab カードの説明を "4-tab layout" に更新
  • Help タブ: Feeder サブタブ説明カードを追加(wfm-help-feeder-title / wfm-help-feeder-desc / wfm-help-feeder-1〜9 の id 付き)

static/css/main.css

Feeder 専用スタイルを末尾に追加:

  • .wfm-feeder-layout — flex 横並び全体レイアウト
  • .wfm-feeder-settings — 左ペイン(幅 220px、縦スクロール)
  • .wfm-feeder-pane-header / .wfm-feeder-field-row / .wfm-feeder-label / .wfm-feeder-input — フォームレイアウト
  • .wfm-feeder-library / .wfm-feeder-status-bar / .wfm-feeder-library-body — 中央ペイン構造
  • .wfm-feeder-tree-pane / .wfm-feeder-tree-scroll / .wfm-feeder-tree-row (+ :hover / .active) / .wfm-feeder-tree-msg — フォルダツリー
  • .wfm-feeder-grid-pane / .wfm-feeder-grid / .wfm-feeder-grid-msg — 画像グリッドコンテナ
  • .wfm-feeder-card (+ :hover / .selected) / .wfm-feeder-card-cb / .wfm-feeder-card-img / .wfm-feeder-card-name — 画像カード
  • .wfm-feeder-preview-pane / .wfm-feeder-preview-img / .wfm-feeder-preview-name / .wfm-feeder-preview-info — プレビューパネル

static/js/comfyui-workflow.js

analyzeWorkflow を3パス構成に再構成:

  • Pass 1: KSampler / KSamplerAdvanced を検出し sampler_nodes に追加
    • seedKey: "seed" in inputs ? "seed" : "noise_seed" で KSamplerAdvanced の noise_seed に対応
    • LoraLoader 検出を追加
  • Pass 1b: BFS 展開(最大5イテレーション)で CONDITIONING チェーンの positive/negative 帰属を伝播
    • COND_PASSTHROUGH セット: ConditioningCombine / ConditioningConcat / ConditioningAverage / ConditioningZeroOut / ConditioningSetTimestepRange / ControlNetApply / ControlNetApplyAdvanced / IPAdapterApply / IPAdapterApplyFaceID / StyleModelApply
    • リンク接続入力を持つノードも帰属を伝播(CLIPTextEncodeSDXL の text_g / text_l が他ノードにリンクされている場合)
  • Pass 2: 新ノード種の検出
    • CLIPTextEncodeSDXL / CLIPTextEncodeSDXLRefiner: text_g / text_l が文字列値の場合のみ prompt_nodes に追加
    • TextEncodeQwenImageEditPlus: inputs.prompt を prompt_nodes に追加
    • SDXLPromptStyler / SDXLPromptStylerAdvanced: text_positive / text_negative をそれぞれ role 付きで追加
    • CheckpointLoader (WAS): ct === "Checkpoint Loader"(スペースあり)を含む判定に修正
    • PrimitiveStringMultiline / PrimitiveString: positive/negative ref がある場合のみ prompt_nodes に追加
    • Power Lora Loader (rgthree): lora_\d+ キーをスキャンして lora_nodes に追加

static/js/comfyui-editor.js

  • KSampler パラメータエディタの hidden input に data-seed-key 属性を追加
  • Apply 時に dataset.seedKey("seed" または "noise_seed")を参照して正しいキーに書き込む

static/js/i18n.js

  • EN / JA / ZH: helpGen3 を "4-tab layout" / "4タブ構成" / "4标签布局" に更新
  • EN / JA / ZH: helpFeederTitle / helpFeederDesc / helpFeeder1〜9 を追加

static/js/app.js

  • helpIdMap に wfm-help-feeder-title / wfm-help-feeder-desc / wfm-help-feeder-1〜9 を追加

2026-04-29: v0.3.4 GenerateUI — Checkpoint Batch 刷新・Settings 横並びレイアウト・ヘルプ i18n 修正

概要

  • Checkpoint Batch: フォルダツリー型ドロップダウン選択に刷新(テキスト入力の Include/Exclude フォルダ指定を廃止)
  • Checkpoint Batch: 第二階層以下のサブフォルダにも対応
  • Checkpoint Batch: 一時停止 / 再開(Pause/Resume)ボタンを追加
  • GenerateUI Settings タブ: KSampler と Latent Image を横並び(各50%幅)に変更
  • Help タブ: 最新機能に合わせて説明を更新し、i18n 対応漏れ(Gallery 5〜11、Gen 11)を修正

変更内容

templates/index.html

  • Checkpoint Batch パネルの Include/Exclude テキスト入力を廃止し、チェックボックス付きドロップダウンに置き換え
    • フォルダヘッダー行: チェックボックス + ▶ 展開矢印 + フォルダ名 + ファイル数
    • ファイル行: インデント + チェックボックス + ファイル名(フルパスを tooltip 表示)
    • パネル上部: Filter 検索入力 + All / None ボタン
  • バッチ進行エリアのプログレスバー下に Pause ボタンを追加(実行中のみ有効)
  • ヘルプ: wfm-help-gen-6(横並びレイアウト)・wfm-help-gen-11(新バッチ機能)の初期テキストを更新

static/js/generate-tab.js

  • _parseFolderList() / _getModelFolder() / _filterCheckpoints() を削除
  • _ckptState = { mode: "all"|"some"|"none", selected: Set<modelPath> } で選択状態を管理
    • "all": 全選択(selected は空)、"some": 部分選択、"none": 全解除
  • _buildFolderTree(models) — モデルパスをフォルダ → モデルリストの Map に変換(lastIndexOf("/") で第二階層以下にも対応)
  • _getFolderCheckState(folderModels) — "checked" / "indeterminate" / "unchecked" を返す
  • _toggleSingleModel() / _toggleFolderModels() — 単体・フォルダ一括のトグルロジック(all/none/some の状態遷移を管理)
  • _rebuildCkptList() — フォルダグループ形式の DOM を動的生成(▶ 展開・折りたたみ、checkbox.indeterminate 対応)
  • _getSelectedCheckpoints() — mode に応じた選択リストを返す
  • _ckptBatch.paused フラグと _waitIfPaused() で一時停止・再開を実装(Promise 待機 + resolve で解除)
  • _setPauseBtnState(paused) — ボタンのテキスト・スタイルを Pause ↔ Resume で切り替え
  • initCheckpointBatch() に Pause/Resume ボタンのハンドラを追加
  • _runBatchGenerate(): ループ先頭で await _waitIfPaused()、一時停止時にステータステキストを "Paused" 表示、finally ブロックで状態クリーンアップ
  • Interrupt(Stop)ボタン: _ckptBatch.paused = false と _resumeResolve() を呼んで一時停止待機を即解除

static/js/comfyui-editor.js

  • renderSettingsTab(): 外枠を flex-direction: row に変更し KSampler(左)と Latent Image(右)を横並び(各 flex:1; min-width:0)、間に縦の境界線(border-right: 1px solid var(--wfm-border))

static/css/main.css

  • .wfm-ckpt-dropdown-wrap / .wfm-ckpt-dropdown-trigger / .wfm-ckpt-dropdown-arrow / .wfm-ckpt-dropdown-panel — ドロップダウン UI スタイル
  • .wfm-ckpt-folder-group / .wfm-ckpt-folder-header / .wfm-ckpt-folder-toggle / .wfm-ckpt-folder-name / .wfm-ckpt-folder-count / .wfm-ckpt-folder-files — フォルダツリー UI スタイル
  • .wfm-ckpt-item--indented — ファイル行のインデント(padding-left: 24px)

static/js/i18n.js

  • EN / JA / ZH: helpGen6 を横並びレイアウトの説明に更新
  • EN / JA / ZH: helpGen11 を追加(フォルダツリードロップダウン、Pause/Resume、Stop の説明)
  • EN / JA / ZH: helpGallery5〜helpGallery10 を現在の HTML 構成に合わせて修正(旧版から5項目ずれていたのを解消)
  • EN / JA / ZH: helpGallery11 を追加(PNG/JPEG/WebP/GIF 対応・メタデータ抽出の説明)

static/js/app.js

  • helpIdMap に "wfm-help-gen-11": "helpGen11"、"wfm-help-gallery-11": "helpGallery11" を追加

2026-04-28: v0.3.3 Promptタブ — ワイルドカード支援パネル・Impact Packシンボリックリンク連携・サブディレクトリ対応・GenerateUI修正

概要

  • Promptタブのレイアウトを3カラム構成に変更
    • 中央ペイン(Col2): Presets / Preset Manager をタブ切り替え化
    • 右ペイン(Col3): ワイルドカード支援パネルを新設
  • ワイルドカードファイル管理(txt/yaml)の CRUD API を追加
  • Settingsタブに Wildcard Integration セクションを追加(Impact Pack ジャンクション/シンボリックリンク連携)
  • ワイルドカードフォルダのサブディレクトリファイルに対応(再帰スキャン・ディレクトリグループ表示・__folder/name__ 形式)
  • GenerateUI: ImpactWildcardEncode ノードの実行エラーを修正

変更内容

py/config.py

  • COMFYUI_ROOT = _COMFYUI_ROOT を追加(外部モジュールから参照可能に)
  • WILDCARD_DIR = DATA_DIR / "wildcard" を追加

py/services/wildcard_service.py — 新規作成

  • WildcardService(wildcard_dir) クラス
    • list_wildcards() — txt/yaml/yml ファイル一覧を返す
    • get_content(filename) — ファイル内容を取得
    • save_file(filename, content) — ファイルを保存(新規/上書き)
    • delete_file(filename) — ファイルを削除
    • _safe_path(filename) — .. / スラッシュ / 拡張子チェックによるパストラバーサル防止
  • Impact Pack 連携メソッド
    • find_impact_pack_wildcards(comfyui_root) — custom_nodes/ 以下で "impact-pack" ディレクトリを検索
    • get_link_status(comfyui_root) — インストール状況・リンク状態・パスを返す dict
    • create_link(comfyui_root) — 既存 WFS ファイルを Impact Pack dir へ移行後、ジャンクション/シンボリックリンクを作成
    • remove_link() — ジャンクション/シンボリックリンクを削除して通常ディレクトリに復元
  • プラットフォームヘルパー
    • _is_junction(path) — Windows の GetFileAttributesW + FILE_ATTRIBUTE_REPARSE_POINT でジャンクション検出(os.path.islink() では検出不可)
    • _create_junction_or_symlink(link, target) — Windows は mklink /J、他 OS は os.symlink
    • _remove_junction_or_symlink(path) — Windows は rmdir(ジャンクションを削除、中身に影響なし)、他は os.unlink
  • コンストラクタ: _is_junction() で事前確認し、ジャンクション/シンボリックリンクの場合は mkdir をスキップ

py/routes/wildcard_routes.py — 新規作成

  • GET /api/wfm/wildcards — ファイル一覧
  • GET /api/wfm/wildcards/content?filename= — ファイル内容取得
  • POST /api/wfm/wildcards/save — ファイル保存
  • POST /api/wfm/wildcards/delete — ファイル削除
  • GET /api/wfm/wildcards/link-status — Impact Pack リンク状態取得
  • POST /api/wfm/wildcards/create-link — ジャンクション/シンボリックリンク作成(移行ファイルリストを返す)
  • POST /api/wfm/wildcards/remove-link — リンク解除

py/wfm.py

  • wildcard_routes.setup_routes(app) を追加

templates/index.html

  • Promptタブ Col2 をタブ切り替え構造(.wfm-prompt-center-tabnav + .wfm-prompt-center-pane)に変更
  • Promptタブ Col3 にワイルドカード支援パネル(.wfm-wc-panel)を新設
    • ツールバー: {|}, |, __, :, ;, $, <lora:>, [], {n$|}, ファイルピッカーボタン
    • プロンプトテキストエリア(.wfm-wc-prompt-ta)
    • ファイル管理セクション(ファイル一覧 + インラインエディタ)
  • Helpタブ: wfm-help-prompt-8、wfm-help-prompt-9、wfm-help-settings-10 を追加

static/js/prompt-tab.js

  • ワイルドカード API ヘルパー: wcFetchFiles()、wcFetchContent()、wcSaveFile()、wcDeleteFile()
  • wcInsertAtCursor(textarea, open, close) — 選択テキストをラップ、または open+close を挿入してカーソルを open の末尾に移動
  • wcRenderFileList()、wcOpenEditor()、wcCloseEditor()、wcRefreshFiles()、wcUpdateFilePicker()
  • initPromptTab() に追加:
    • 中央ペインのタブ切り替えハンドラ
    • ワイルドカードツールバーボタン(data-wc-open / data-wc-close 属性で制御)
    • Loraボタン: <lora::1:LBW=;> を挿入してカーソル位置調整
    • {n$|} ボタン: prompt() で n を入力し {n$|} を挿入
    • ファイルピッカー(ポップアップ型、クリックで __filename__ 挿入)
    • ファイルマネージャのイベントハンドラ(新規作成・保存・削除)

static/js/settings-tab.js

  • Data Management セクションの直後に Wildcard Integration <details> セクションを追加
  • セクション展開時に GET /api/wfm/wildcards/link-status を呼び出して動的 UI をレンダリング
    • Impact Pack 未インストール: インストール誘導メッセージ + GitHub リンク
    • インストール済み・リンクなし: Impact Pack パス表示 + 「リンクを作成」ボタン(移行ファイル名を表示)
    • リンク済み: リンク先パス表示 + 「リンクを解除」ボタン

static/js/app.js

  • wfm-help-prompt-8、wfm-help-prompt-9、wfm-help-settings-10 の ID マッピングを追加

static/js/i18n.js

  • helpPrompt8、helpPrompt9、helpSettings10 を EN/JA/ZH に追加

static/css/main.css

  • .wfm-prompt-center-tabnav、.wfm-prompt-center-tab、.wfm-prompt-center-pane — 中央ペインタブ UI
  • .wfm-wc-panel、.wfm-wc-toolbar、.wfm-wc-btn、.wfm-wc-prompt-ta — ワイルドカードパネル
  • .wfm-wc-file-picker-wrap、.wfm-wc-file-picker、.wfm-wc-picker-item — ファイルピッカー
  • .wfm-wc-files-section、.wfm-wc-file-list、.wfm-wc-file-item、.wfm-wc-editor — ファイルマネージャ

ワイルドカードサブディレクトリ対応

py/services/wildcard_service.py

  • list_wildcards() — iterdir() → rglob("*") に変更して再帰スキャン。レスポンスに dir(サブディレクトリパス、ルートは空文字)と wc_name(拡張子なし相対パス、例: color/animals)を追加
  • _safe_path() — / 区切りの相対パスを受け付けるよう変更。各パスコンポーネントを個別に検証してパストラバーサルをブロック
  • save_file() — path.parent.mkdir(parents=True, exist_ok=True) を追加してサブディレクトリを自動作成。レスポンスに dir、wc_name を追加

static/js/prompt-tab.js

  • wcRenderFileList() — ファイルをディレクトリごとにグループ化して表示(ルートが先、サブディレクトリはヘッダ付き)。サブディレクトリ内ファイルは wfm-wc-file-item--sub クラスでインデント。挿入テキストを __f.name__ → __f.wc_name__(例: __color/animals__)に変更
  • wcUpdateFilePicker() — 同様にディレクトリグループ表示。挿入テキストを wc_name ベースに変更
  • Editor Save ハンドラ — name のバリデーションを folder/name 形式に対応。\ を / に正規化して各コンポーネントを個別検証。filename を name.ext(例: color/animals.txt)として構築

templates/index.html

  • エディタ名入力の placeholder を "name or folder/name" に更新

static/css/main.css

  • .wfm-wc-dir-header — ファイル一覧のディレクトリ区切りヘッダ
  • .wfm-wc-file-item--sub — サブディレクトリファイルの左インデント
  • .wfm-wc-picker-dir — ファイルピッカーのディレクトリ区切りラベル
  • .wfm-wc-picker-item--sub — ファイルピッカー内サブディレクトリファイルのインデント
  • Safari 向け -webkit-user-select を追加

GenerateUI: ImpactWildcardEncode バリデーションエラー修正

static/js/comfyui-workflow.js

  • convertUiToApi() — COMBO 型ウィジェット値を /object_info の選択肢リストと照合し、値が存在しない場合は先頭の有効な選択肢にフォールバック。Impact Pack の ImpactWildcardEncode ノードが "Select Wildcard 🟢 Full Cache" 等の動的プレースホルダー値を持つ場合でも "Prompt outputs failed validation" エラーが発生しなくなる
  • analyzeWorkflow() — ImpactWildcardEncode / ImpactWildcardProcessor を prompt_nodes として認識(KSampler の positive/negative 接続からロールを判定)。テキストフィールドキーを text ではなく wildcard_text として記録

static/js/comfyui-editor.js

  • renderPromptTab() — select の option に data-text-key 属性を追加(CLIPTextEncode = "text"、ImpactWildcardEncode = "wildcard_text")
  • Apply ハンドラ — analysis.prompt_nodes から textKey を取得して inputs[textKey] に書き込むよう変更(従来は inputs.text 固定)
  • syncToWorkflow() — 選択中の option の data-text-key を読んで正しいフィールドに書き込むよう変更。生成前の同期(_coreGenerate 直前の comfyEditor.syncToWorkflow() 経由)でも正しく反映される

2026-04-28: v0.3.2 生成UIタブ — Checkpoint Batch・Seed UIレイアウト修正

概要

  • 生成UIタブの右パネルに Checkpoint Batch 機能を追加
  • Seed行のレイアウトを2段構成に変更(seed値とモード選択が見切れる問題を解消)

変更内容

templates/index.html — UI要素追加・修正

Checkpoint Batch パネル追加:

  • 右パネル(Seed行の下・結果表示の上)に wfm-ckpt-batch-panel を追加
  • チェックボックス ON/OFF でバッチ設定欄を展開/折りたたみ
  • Include Folders テキスト入力 — カンマ区切りでサブフォルダ名を指定。空欄でcheckpointフォルダ内の全モデルを対象
  • Exclude Folders テキスト入力 — カンマ区切りで除外するサブフォルダ名を指定
  • 対象モデル数プレビュー表示 (wfm-ckpt-batch-info)
  • バッチ進捗エリア — 現在のモデル名、インデックス/総数、アンバー色プログレスバー (wfm-ckpt-batch-bar)

Seed レイアウト修正:

  • 横並び1行(flex-direction: row)→ 縦2行(flex-direction: column)に変更
  • 1行目: Seed: ラベル + 数値入力(flex:1 で横幅いっぱい)
  • 2行目: モード選択セレクト(width:100%)

ヘルプタブ:

  • GenerateUI タブ説明に gen-11(Checkpoint Batch の説明)を追加
  • gen-8(Seed control)に2段レイアウト化の補足を追加

static/js/generate-tab.js — バッチロジック追加・生成処理リファクタリング

新規追加:

  • _ckptBatch — { aborted: false } バッチ中断フラグ
  • _parseFolderList(str) — カンマ区切り文字列をトリム・小文字化して配列に変換
  • _getModelFolder(modelPath) — パスの最初の / より前を抽出してフォルダ名を取得。\\ を / に正規化、ルート直下は "" を返す
  • _filterCheckpoints(checkpoints, includeStr, excludeStr) — include/exclude フィルタを適用したチェックポイントリストを返す。大文字小文字を区別しない
  • _updateBatchInfo() — チェックポイントリストのフィルタ済み件数を wfm-ckpt-batch-info に即時反映
  • initCheckpointBatch() — チェックボックス・include/exclude 入力のイベントリスナーを登録
  • _coreGenerate(silent) — 1回の生成コア処理(silent=true のとき完了トーストを省略)。エラー時はスローする
  • _runBatchGenerate() — チェックポイントリストをループし _coreGenerate を順番に呼び出す。各反復でワークフロー内の全checkpointノードの ckpt_name を書き換える。エラーは件数カウントして継続。完了後に結果サマリをトースト表示

変更:

  • handleGenerate() — バッチが有効な場合は _runBatchGenerate() を、無効の場合は _coreGenerate(false) を呼び出すよう分岐。ボタン管理(disabled / Stop 表示)を共通 try/finally で処理
  • interrupt ボタンのハンドラ — _ckptBatch.aborted = true を設定してから comfyUI.interrupt() を呼び出し、単発生成とバッチの両方を停止できるように
  • Refresh Models ボタンのハンドラ — モデルリスト再取得後に _updateBatchInfo() を呼び出してバッチ件数を更新
  • initGenerateTab() — 末尾で initCheckpointBatch() を呼び出し。初期接続成功時に _updateBatchInfo() を呼び出し

2026-04-27: v0.3.1 ギャラリー拡張(フォルダ/ファイル操作・ワークフロー保存)

概要

  • ギャラリータブにフォルダ作成・削除機能を追加
  • ギャラリータブにファイル削除・移動機能を追加(単体・複数選択対応)
  • 操作後もフォルダツリーの展開状態を維持するよう改善
  • 生成UIタブで生成した画像のワークフローをギャラリーメタデータに自動保存
  • MetadataタブのワークフローJSON表示をComfyUI生成画像全般(promptキー)に対応

変更内容

py/services/gallery_metadata.py — メタデータ操作追加

  • delete(image_path) 追加 — 画像削除時にメタデータエントリを除去
  • rename_path(old_path, new_path) 追加 — ファイル移動後のパスキー付け替え
  • allowed セットに "workflow" を追加 — 生成UIからのワークフロー保存を受け付けるように

py/services/gallery_service.py — ファイル・フォルダ操作追加

  • create_folder(parent_path, name) — 無効文字チェック付きでサブフォルダを作成。作成後にフォルダキャッシュを無効化
  • delete_folder(folder_path) — shutil.rmtree で再帰削除。ルートフォルダは保護
  • delete_images(paths) — 複数画像ファイルを削除。各ファイルのメタデータ削除・キャッシュ無効化も実行
  • move_images(paths, dest_folder) — 複数画像を別フォルダへ移動。ファイル名衝突時は連番サフィックス付与。メタデータのパスキーを rename_path で引き継ぎ
  • extract_workflow_from_metadata 修正 — PNG埋め込みの参照キーを workflow → workflow / prompt の順に拡張。どちらも存在しない場合は gallery_metadata.json の workflow フィールドへフォールバック

py/routes/gallery_routes.py — エンドポイント追加

  • POST /wfm/gallery/folder — フォルダ作成
  • DELETE /wfm/gallery/folder — フォルダ削除
  • POST /wfm/gallery/images/delete — 画像削除(単体・複数)
  • POST /wfm/gallery/images/move — 画像移動(単体・複数)

static/js/gallery-tab.js — フロントエンド拡張

API定数追加:

  • folderCreate, folderDelete, imagesDelete, imagesMove

state追加:

  • folderTree — ツリー全体データを保持(移動先フォルダ一覧の生成に使用)

フォルダツリー展開状態の保持:

  • _getExpandedPaths() — 再構築前に展開済みフォルダのパスを Set で収集
  • _restoreTreeState(expandedPaths, selectedPath) — 再構築後に展開状態・選択ハイライトを復元。階層の浅い順に展開することで子ノードのDOM存在を保証
  • renderTreeNode — 各アイテムに data-path 属性を付与(復元の識別子)
  • loadFolderTree — 初回ロード判定(isFirstLoad)を追加。2回目以降は _restoreTreeState を呼び出して展開状態を維持

フォルダ操作:

  • createFolder() — prompt() でフォルダ名入力後、API呼び出し・ツリー再読み込み
  • deleteFolder() — 確認ダイアログ後、再帰削除・currentFolder リセット・ツリー再読み込み
  • フォルダラベルクリック時に Delete Folder ボタンの disabled 状態を更新(ルート選択時は削除不可)

ファイル削除・移動:

  • performDeleteImages(paths) — 削除後に state.images / state.selectedImages を即時更新。詳細パネルの選択中画像が削除された場合はパネルをリセット
  • performMoveImages(paths, dest) — 移動後に同様に state を即時更新
  • flattenFolderTree(node) — ツリーデータをフラットリストに変換(移動先選択に使用)
  • openMoveModal(paths) — 移動先フォルダ選択モーダルを動的生成。現在フォルダを除く全フォルダをドロップダウン表示
  • 詳細パネルの Move To... / Delete ボタンを画像選択時に有効化

templates/index.html — UI要素追加

  • フォルダツリーヘッダーに「+ New」「Del」ボタン(wfm-gallery-tree-actions)
  • 詳細パネル Info タブに「Move To...」「Delete」ボタン(wfm-gallery-file-actions)
  • 一括バーに「Move To...」「Delete」ボタン
  • ヘルプ項目を10項目から11項目に更新

static/css/gallery-tab.css — スタイル追加

  • .wfm-gallery-tree-header — justify-content: space-between でタイトルとボタンを両端配置
  • .wfm-gallery-tree-actions — フォルダ操作ボタンのフレックスコンテナ
  • .wfm-gallery-file-actions — 詳細パネルのファイル操作ボタン
  • .wfm-gallery-move-modal / .wfm-gallery-move-modal-title / .wfm-gallery-move-dest-sel / .wfm-gallery-move-modal-footer — 移動先選択モーダル

static/js/generate-tab.js — ワークフロー自動保存

  • _outputDir モジュール変数 — outputパスをキャッシュ
  • _fetchOutputDir() — /api/wfm/settings/output-dir からoutputパスを取得
  • saveGeneratedImagesMeta(images, workflow) — 生成完了後、各画像のフルパスを組み立てて /wfm/gallery/image/meta にワークフローを保存。type !== "output" の一時ファイルはスキップ
  • initGenerateTab — 初期化時に _fetchOutputDir() を実行。wfm-output-dir-changed イベントで設定変更時にパスを同期
  • handleGenerate — 生成成功後に saveGeneratedImagesMeta を非同期呼び出し

技術的な判断

  • ツリー展開状態の復元: innerHTML = "" による完全再構築を維持しつつ、展開パスを Set で保存して復元する方式を採用。差分DOMパッチより実装が単純で、フォルダ追加・削除後のツリー構造変化にも対応できる
  • PNG prompt キーの参照: ComfyUIのSaveImageノードはデフォルトでPNGに prompt キー(API形式)のみを埋め込む。workflow キー(UI形式)が埋め込まれるのは特定の操作時のみ。prompt を参照することで既存の全ComfyUI生成画像に対応
  • フォールバック優先順位: PNG埋め込み(workflow > prompt)> gallery_metadata.json の順とし、ComfyUI本体が埋め込んだデータを最優先に

2026-04-18: v0.3.0 モデルグループ表示改善(全タイプ横断・サイドパネル修正)

概要

  • モデルタブのグループフィルタードロップダウンを全タイプ横断表示に改善
  • ComfyUIサイドパネルのModel Groupsビューを v0.2.9 のタイプ別形式に対応させバグ修正

変更内容

static/js/models-tab.js — グループフィルター全タイプ横断表示

  • state.allModelGroups 追加 — 全タイプのグループを { type: { groupName: [models] } } 形式で保持
  • fetchAllModelGroups() 追加 — type パラメータなしで全タイプのグループをAPIから一括取得
  • renderGroupFilter() 改善 — ドロップダウンオプションを [Checkpoint] GroupName / [LoRA] GroupName 形式で全タイプ分表示。値は type::groupName としてエンコード
  • グループフィルター change ハンドラ更新 — 選択したグループが現在と異なるタイプの場合、モデルタイプタブを自動切り替えしてモデルを再ロード
  • saveModelGroups() — 保存時に state.allModelGroups[type] も更新
  • loadModelsForCurrentType() — ロード時に state.allModelGroups[type] を同期
  • loadMetadataAndModels() — 初回ロード時に fetchAllModelGroups() で全タイプのグループを一括取得

web/comfyui/node_sets_menu.js — サイドパネルのModel Groupsビュー修正

  • renderModelGroups() を新タイプ別形式 { type: { groupName: [...] } } に対応
    • 旧: groups[groupName] が配列を期待 → TypeError: .filter is not a function でクラッシュ
    • 新: flatGroups = [{modelType, groupName, members}] にフラット展開してから描画
  • グループヘッダーに [Checkpoint] / [LoRA] 等のタイプラベルを追加(wfm-nlp-model-type-badgeクラス)
  • typeOf[name] || modelType — モデルリスト未取得時のフォールバックをグループのモデルタイプに変更

2026-04-18: v0.2.9 モデル有効/無効・複数選択・一括削除

概要

  • モデルファイルの拡張子リネーム(.disabledサフィックス)でComfyUIへの表示を制御
  • モデル種別ごとのグループスコープ(checkpointグループはcheckpointタブにのみ表示)
  • 複数選択モードと一括グループ操作・ファイル削除機能を追加

変更内容

py/services/models_service.py — 有効/無効・削除ロジック追加

  • _DISABLED_SUFFIX = ".disabled" 定数追加
  • _SIDECAR_EXTENSIONS — 削除対象の付属ファイル拡張子リスト(.preview.png 〜 .webp + .json, .civitai.info, .info)
  • find_model_file(model_type, model_name) — 有効・無効両状態を検索、(Path, is_enabled) を返す
  • enable_model(model_type, model_name) — .disabled → 元の拡張子にリネーム
  • disable_model(model_type, model_name) — 元の拡張子 → .disabled にリネーム(path.name ベースでサブディレクトリ二重化バグを回避)
  • scan_disabled_models(model_type) — rglob("*.disabled") でスキャン、正規化名リストを返す
  • get_model_groups(model_type=None) — model_type パラメータ追加。旧フラット形式 { "groupName": [...] } を _groups_legacy に退避し、{ "_groups": { "checkpoint": {...} } } 形式へ自動マイグレーション
  • save_model_groups(groups, model_type) — model_type パラメータ追加、種別ごとに保存
  • delete_model(model_type, model_name) — モデルファイル(有効・無効どちらも対応)を削除。path.name から .disabled を除去してstemを正しく計算し、全サイドカーファイルを削除。メタデータエントリも削除

py/routes/models_routes.py — エンドポイント追加・変更

新エンドポイント:

  • GET /api/wfm/models/disabled?type= — 指定タイプの無効モデル名リストを返す
  • POST /api/wfm/models/toggle — { model_type, model_name, enabled } でモデル1件の有効/無効を切り替え
  • POST /api/wfm/models/group-toggle — { model_type, group_name, enabled } でグループ内全モデルを一括切り替え
  • POST /api/wfm/models/delete — { model_type, model_names: [] } で複数モデルを一括削除

変更:

  • GET /api/wfm/models/groups — ?type= クエリパラメータ追加
  • POST /api/wfm/models/groups — ボディを { "model_type": ..., "groups": {...} } 形式に変更

static/js/comfyui-client.js — Hypernetworkバグ修正

  • _fetchModelList で新ComfyUI APIフォーマット ["COMBO", {"values":[...]}] に対応
  • Array.isArray(first) チェックを追加、文字列 "COMBO" をスプレッドしてしまう問題を解消

static/js/models-tab.js — フロントエンド全体

state追加:

  • disabledModels: {} — タイプ別の無効モデルSet
  • statusFilter: "all" — "all" / "enabled" / "disabled"
  • selectMode: false, selectedModels: new Set() — 複数選択状態

新関数:

  • isModelDisabled(), fetchDisabledModels(), toggleModelEnable(), toggleGroupEnable() — 有効/無効操作
  • toggleSelectMode() — 選択モードのON/OFF切り替え、ボタンテキスト更新
  • toggleModelSelection(modelName) — DOM直接更新(再レンダリングなし)
  • clearSelection() — 選択リセット
  • renderBulkActionBar() — グループ選択・追加/削除・新規作成・削除ボタンを描画
  • bulkAddToGroup(), bulkRemoveFromGroup() — 一括グループ操作
  • bulkDeleteModels() — confirm() で確認後 /api/wfm/models/delete を呼び出し、ローカルstateを即時更新

各ビュー:

  • ThumbView / CardView: 無効オーバーレイ・⏸ボタン・selectMode時チェックボックスオーバーレイ追加
  • TableView: チェックボックス列・wfm-table-td-check 追加
  • renderSideGroup(): "全て有効化" / "全て無効化" ボタン追加

タイプ切り替え:

  • groupFilter, statusFilter, selectMode, selectedModels をリセット

templates/index.html — UI要素追加

  • <select id="wfm-models-status-filter"> — All / Enabled / Disabled フィルター
  • <button id="wfm-models-select-btn"> — 選択モード切り替え
  • <div id="wfm-models-bulk-bar"> — 一括操作バー(初期非表示)
  • <li id="wfm-help-models-11"> — ヘルプ項目追加

static/css/main.css — スタイル追加

  • .wfm-model-disabled / .wfm-disabled-overlay — 無効状態の見た目
  • .wfm-toggle-btn / .wfm-toggle-btn.wfm-toggle-disabled — ⏸ボタン
  • .wfm-badge-disabled — 無効バッジ
  • .wfm-select-check / .wfm-select-check.checked — チェックボックス
  • .wfm-card-checked — 選択済みカードのアウトライン
  • .wfm-bulk-bar / .wfm-bulk-count / .wfm-bulk-sep / .wfm-table-td-check — 一括操作バー

static/js/i18n.js — 新キー追加(3言語)

  • 有効/無効操作: modelEnable, modelDisable, modelDisabled, modelStatusAll, modelStatusEnabled, modelStatusDisabled, modelGroupEnableAll, modelGroupDisableAll, modelToggleError, modelStatusWarning
  • 複数選択: modelSelectMode, modelSelectExit, modelSelected, modelBulkAddGroup, modelBulkRemoveGroup, modelBulkCreateAdd, modelBulkAddDone, modelBulkRemoveDone
  • ファイル削除: modelBulkDelete, modelBulkDeleteConfirm, modelBulkDeleteDone, modelBulkDeleteError
  • ヘルプ: helpModels11(複数選択・一括削除の説明)、helpModels3 更新(ステータスフィルター追記)

技術的な判断

  • 拡張子リネーム方式: ComfyUIはスキャン時に認識拡張子のみ列挙するため、.disabledサフィックスを付けるだけでモデルを隠せる。ファイル移動や専用DBを使わず最もシンプルな実装
  • DOM直接更新(選択モード): 選択トグルのたびにrenderModelGrid()を呼ぶと大量再レンダリングが起きる。querySelectorAll("[data-model-name]")で対象要素を絞りclassList.toggle()のみ実行
  • stemバグの根本原因: path.stemは最後のサフィックスのみ除去する。model.safetensors.disabledのstemはmodel.safetensorsになるため、model.safetensors.pngを探してしまう。.disabledを先に除去してからPath.stemを計算する必要がある

2026-04-14: v0.2.8 データ保存先変更・エクスポート/インポート機能追加

概要

  • プラグインデータの保存先を custom_nodes/ComfyUI-Workflow-Studio/data/ から ComfyUI/user/default/Workflow-Studio/ に変更
  • user/default/ が存在しない環境(Portable版・Dockerなど)では従来の data/ にフォールバック
  • 設定タブに「データ管理」セクションを追加(エクスポート/インポート機能)

変更内容

py/config.py — DATA_DIR 保存先変更

DATA_DIR の解決ロジックを変更:

  • 変更前: DATA_DIR = PLUGIN_DIR / "data" (固定)
  • 変更後: user/default/ が存在すれば DATA_DIR = _COMFYUI_USER_DEFAULT / "Workflow-Studio"、存在しなければ PLUGIN_DIR / "data" にフォールバック
  • _COMFYUI_ROOT の定義位置を DATA_DIR の前に移動(_COMFYUI_WORKFLOWS と共用)

py/routes/settings_routes.py — エクスポート/インポートAPI追加

新エンドポイント:

  • GET /api/wfm/settings/export — settings.json, metadata.json, node_metadata.json, node_sets.json, prompts.json, model_metadata.json, gallery_metadata.json の7ファイルを1つのJSONバンドルとして返す(Content-Disposition: attachment でDL)
  • POST /api/wfm/settings/import — JSONバンドルを受け取り、含まれるファイルを個別に書き戻す。存在しないキーはスキップ

新定数:

  • _DATA_FILES — エクスポート/インポート対象ファイル名のリスト

static/js/settings-tab.js — データ管理UIセクション追加

Eagle連携セクションの直下に <!-- Data Management --> セクションを追加:

  • エクスポートボタン: クリックで wfm-data-export.json をダウンロード
  • インポートボタン: <label> でファイル選択ダイアログを開く(.json 限定)
  • ステータス表示: 成功・失敗をインラインで表示(成功: green / 失敗: red)
  • 同一ファイルの再インポートができるよう、インポート後にファイル入力をリセット

static/js/i18n.js — 新キー追加(3言語)

追加キー(英語・日本語・中国語):

  • dataManagement, dataManagementHint, exportData, importData
  • exportSuccess, exportError, importSuccess, importError

templates/index.html / static/js/app.js / static/js/i18n.js — ヘルプタブ更新

  • wfm-help-settings-8 をデータ管理の説明に変更
  • wfm-help-settings-9(アコーディオン説明)を追加(HTML・app.jsマッピング・i18n 3言語)

移行手順(既存ユーザー向け)

  1. 設定タブ →「データ管理」→「エクスポート」で wfm-data-export.json を保存
  2. ComfyUIを再起動(新パスで空の状態で起動)
  3. 設定タブ →「データ管理」→「インポート」でエクスポートしたファイルを選択
  4. ComfyUIを再起動して反映

または手動で custom_nodes/ComfyUI-Workflow-Studio/data/ の内容を ComfyUI/user/default/Workflow-Studio/ にコピーしてから再起動。


2026-04-06: v0.2.6 UI改善・バッジ保存修正・フォルダフィルター追加

概要

  • ワークフロー/モデル/ノードタブのサイドパネルを常時表示固定に変更
  • グリッドをページネーションからスクロール全件表示に変更
  • ワークフロータブのバッジが再起動後に消える問題を修正
  • バッジフィルターボタンをパレット定義バッジで統一(ワークフロータブ)
  • モデルタブにフォルダ(サブディレクトリ)フィルターを追加
  • モデルカードビューから拡張子・フォルダバッジを削除(詳細パネルで確認可能)
  • サムネイルカードサイズを 178×200px に調整、グリッドレイアウトを修正

変更内容

py/services/workflow_service.py — バッジ保存修正

save_metadata のキーリストに badges を追加:

  • 変更前: ("tags", "memo", "summary", "modelTypesOverride", "favorite")
  • 変更後: ("tags", "memo", "summary", "modelTypesOverride", "favorite", "badges")

list_workflows のレスポンスに badges フィールドを追加:

  • metadata dict に "badges": meta.get("badges", []) を追加
  • これにより再起動後もバッジが保持される

static/js/workflow-tab.js — バッジフィルター・サイドパネル・スクロール

バッジフィルターをパレット定義バッジで表示:

  • renderModelFilters(): getAllBadges()(ワークフローに付いているバッジのみ)→ getBadgePalette()(パレット全バッジ)に変更
  • フィルターボタンにバッジカラーを適用

サイドパネル常時表示化:

  • showSidePanel(): panel.style.display = "flex" を削除
  • closeSidePanel(): panel.style.display = "none" を削除、タイトルをクリアするのみ
  • ×ボタンのイベントリスナー削除

ページネーション廃止・スクロール全件表示:

  • WF_PER_PAGE 定数削除
  • updateWfPagination() 関数削除
  • renderGrid(): ページスライス処理を削除、全件描画に変更

static/js/models-tab.js — サイドパネル・スクロール・フォルダフィルター・カードビュー

サイドパネル常時表示化:

  • showSidePanel(): panel.style.display = "flex" を削除
  • closeSidePanel(): panel.style.display = "none" を削除

ページネーション廃止・スクロール全件表示:

  • MODELS_PER_PAGE 定数削除、updatePagination() 関数削除
  • renderThumbView() / renderCardView() / renderTableView(): totalPages 引数削除、全件描画に変更

フォルダフィルター追加:

  • state.dirFilter: "" 追加
  • filterModels() に dirFilter による絞り込みを追加
  • renderDirFilter() 関数追加(現在のモデルタイプのサブディレクトリ一覧を <select> に反映)
  • モデルロード時・モデルタイプ切替時に呼び出し
  • wfm-models-dir-filter change イベントリスナー追加

モデルカードビューからバッジ削除:

  • renderCardView(): dir バッジ・ext バッジを削除。ユーザーバッジ・タグのみ表示

static/js/nodes-tab.js — サイドパネル・スクロール

サイドパネル常時表示化:

  • showNodeSidePanel(): panel.style.display = "flex" を削除
  • closeNodeSidePanel(): panel.style.display = "none" を削除

ページネーション廃止・スクロール全件表示:

  • NODES_PER_PAGE / renderPagination() / clearPagination() / scrollGridToTop() 削除
  • renderNodeGrid(): 全件描画に変更

templates/index.html — サイドパネル・ページネーション・フォルダフィルター

  • 3タブ(ワークフロー/ノード/モデル)のサイドパネルから style="display:none" と×ボタンを削除
  • ページネーション用 <div> を3箇所から削除
  • モデルタブのツールバーに <select id="wfm-models-dir-filter"> を追加(TagとGroupの間)

static/css/main.css — グリッド・カードサイズ・サイドパネル

グリッド・カードサイズ変更:

  • グリッド列幅: 161px → 178px
  • カード幅: 161px → 178px
  • サムネイル: 161×162px → 178×200px
  • grid-auto-rows: max-content / align-items: start / justify-items: start 追加(行高さ圧縮防止)

カードビュー:

  • グリッド列幅: minmax(180px, 1fr) → repeat(auto-fill, 220px) 固定幅に変更(横伸び防止)
  • カード: flex-direction: row / min-height: 56px / width: 220px 固定

サイドパネル:

  • .wfm-side-panel-close スタイル削除
  • .wfm-side-panel-empty クラス追加(選択前の空状態表示用)

static/js/i18n.js — フォルダフィルター翻訳追加

  • modelsAllDirs: 英語 "All Folders" / 日本語 "すべてのフォルダ" / 中国語 "所有文件夹" を追加

2026-04-04: v0.2.5 追加修正

概要

  • サイドパネルModels→By TypeでTextEncoderが表示されない問題を修正
  • サイドパネルのWorkflowsタブでも .index.json を非表示に
  • 未使用の buildDropdown 関数を削除

変更内容

web/comfyui/node_sets_menu.js

TextEncoder取得ロジック修正:

  • 変更前: /api/wfm/models?type=textencoder(存在しないエンドポイント)
  • 変更後: DualCLIPLoader → CLIPLoader の順に /object_info/{cls} を試して clip_name1 を取得(comfyui-client.js の fetchTextEncoders() と同じロジック)

.index.json 非表示:

  • loadWfData(): state.wfList = workflows.filter(w => w.filename !== ".index.json") でサイドパネルのWorkflowsタブからも除外

未使用関数削除:

  • buildDropdown() 関数を削除(Category/Packageドロップダウンは個別実装に置き換え済みだったため不要)

2026-04-03: v0.2.5 サイドパネル Category/Package サブタブ・テーマ設定・Workflow修正

概要

  • サイドパネル(Workflow Studio Library)のNodesタブに📂 Category / 🧩 Packageサブタブを追加
  • サイドパネルのヘッダーに⚙テーマ設定ボタンを追加(背景・文字・ボーダー色カスタマイズ)
  • ワークフロータブで .index.json を非表示に

変更内容

web/comfyui/node_sets_menu.js — Nodesサブタブ追加・テーマ設定

Nodesタブのrow2サブタブ構成変更:

  • 変更前: row2 = ☰ Sets
  • 変更後: row2 = ☰ Sets / 📂 Category / 🧩 Package

state追加:

  • activeNodeCategory: "" — Categoryサブタブで選択中のカテゴリ値
  • activeNodePackage: "" — Packageサブタブで選択中のパッケージ値
  • objectInfo: {} — /object_info APIから取得した全ノード情報(パッケージ判定に使用)

loadData() 変更:

  • fetchObjectInfo() を追加して /object_info を並行取得
  • state.objectInfo に保存

新規関数:

  • fetchObjectInfo() — GET /object_info で全ノード情報を取得
  • extractPackageName(pythonModule) — python_module からパッケージ名を抽出
  • getNodeCategory(nodeType) — state.objectInfo 優先でカテゴリを取得(LiteGraphフォールバック)
  • getNodePackage(nodeType) — state.objectInfo[nodeType].python_module からパッケージ名を取得
  • renderNodesByCategory(container) — カテゴリドロップダウンをcontent上部に挿入してリスト表示
  • renderNodesByCategoryList(container) — 選択カテゴリ+検索テキストでフィルタしてカード表示
  • renderNodesByPackage(container) — パッケージドロップダウンをcontent上部に挿入してリスト表示
  • renderNodesByPackageList(container) — 選択パッケージ+検索テキストでフィルタしてカード表示

ドロップダウンのDOM挿入方式:

  • .wfm-nlp-filter-row クラスのdivを .wfm-nlp-content の直前に insertBefore で挿入
  • renderContent() 冒頭で panelEl.querySelectorAll(".wfm-nlp-filter-row").forEach(e => e.remove()) により他タブ移動時に削除

テーマ設定:

  • THEME_KEY = "wfm_nlp_theme" — localStorage保存キー
  • THEME_VARS — 設定可能な5変数: --comfy-menu-bg(背景)、--comfy-input-bg(サブヘッダーBG)、--input-text(文字色)、--border-color(ボーダー)、--descrip-text(補助テキスト色)
  • loadTheme() — localStorageから読み込み
  • applyTheme(panel, theme) — パネル要素にCSS変数をインラインsetPropertyで上書き
  • buildThemePanel(panel) — カラーピッカーUIを動的生成(ライブプレビュー・Save・Resetボタン付き)
  • パネルのHTMLに .wfm-nlp-theme-btn(⚙)と .wfm-nlp-theme-panel を追加
  • createPanel() 内でテーマボタンのイベントリスナーを登録、applyTheme(panel, loadTheme()) で起動時に保存済みテーマを適用

static/js/workflow-tab.js — .index.json 非表示

  • filterWorkflows() の先頭に if (wf.filename === ".index.json") return false; を追加

技術的な判断

  • python_module の取得元をAPIに変更: LiteGraph.registered_node_types にはバックエンド由来の python_module が存在しないため、全ノードが ComfyUI (Built-in) 判定になる問題があった。/object_info APIを loadData() 時に並行取得することで正確なパッケージ判定を実現
  • CSS変数のインライン上書き: テーマ色はパネル要素自体に style.setProperty で適用。ComfyUI本体の :root 変数を書き換えず、パネルスコープ内のみに影響させる設計
  • ドロップダウンのDOMインジェクション: Category/Packageビューのドロップダウンは renderContent の都度生成・削除する方式を採用。タブ切替時のクリーンアップを renderContent 冒頭の .wfm-nlp-filter-row 削除処理で一元管理

2026-03-29: v0.2.4 GenerateUIタブ レイアウト再設計

概要

  • GenerateUIのサブタブを5タブ(Prompt/Image/Model/Settings/RawJSON)→ 3タブ(Input/Model/Settings)に整理
  • 各タブ内にRaw JSONエディタを右列として常時表示。どのタブでもJSON変更をリアルタイム確認・直接編集・Apply可能
  • Raw JSONタブを廃止(独立タブとしての Raw JSON は不要)
  • 右列(生成コントロール)はGenerate/Seed/結果画像のみにスリム化

変更内容

templates/index.html — タブ構成刷新

  • Inputタブ: 左列=Prompt(上段)+ Image(下段)の縦2段、右列=Raw JSON(540px)
  • Modelタブ: 左列=モデル選択フォーム(スクロール)、右列=Raw JSON(540px)
  • Settingsタブ: 左列=KSampler(上段)+ LatentImage(下段)の縦2段、右列=Raw JSON(540px)
  • Raw JSONのDOMノード(wfm-gen-rawjson-widget)はページ内に1つ定義し、タブ切替時にJSで対応する列へ移動
  • 右列(.wfm-gen-right): 280px幅、Generate/Stop/Progress/Seed+結果画像のみ

static/js/generate-tab.js — タブ切替時にRaw JSONを移動

  • moveRawJsonToTab(tabKey) 追加: wfm-gen-rawjson-widget を wfm-gen-rawjson-col-{tabKey} へ appendChild
  • 初期表示(Input)とタブクリック時に自動移動

static/js/comfyui-editor.js — Settings縦2段化

  • renderSettingsTab: KSampler / LatentImageを横2列グリッド → 縦2段(flex-direction:column)に変更
  • renderAll の末尾に _syncRawJson() 追加(ワークフロー読込直後にRaw JSONが即時反映)

static/css/main.css — 新レイアウト用スタイル

  • .wfm-gen-tab-cols: タブ内2列レイアウト(flex行)
  • .wfm-gen-params-col: 左サブ列(flex:1、縦積み)
  • .wfm-gen-params-col--scroll: Model/Settings用スクロール変種
  • .wfm-gen-params-section: Input用上下2段の各セクション(flex:1、overflow-y:auto)
  • .wfm-gen-rawjson-col: Raw JSON列(540px固定幅)
  • #wfm-gen-rawjson-widget / .wfm-gen-rawjson-header / .wfm-gen-rawjson-editor: Raw JSONウィジェットスタイル

技術的な判断

  • Raw JSONをDOMで移動: タブごとにtextareaのIDは1つである必要があるため、DOMノードをappendChildで移動する方式を採用。イベントリスナーはノードに付いたまま引き継がれる
  • Raw JSON列幅540px: テキストエディタとして十分な視認性を確保しつつ、パラメータ列にも十分な幅を残す設定

2026-03-29: v0.2.3 バッジ統一・GenUI Model・サイドパネルModels拡充

概要

  • ワークフローとモデルのバッジ体系を統一(共有パレット wfm_models_badge_palette、自動解析廃止)
  • ModelsタブにGenUI Modelボタン追加(詳細モーダル・サイドパネル両対応)
  • ComfyUIサイドパネルのModelsタブにFavorites/Groups/By Typeサブタブを追加
  • サイドパネルのWorkflows・Nodesサブタブ名を「All」に統一、お気に入りに★表示
  • サイドパネル幅を280px→310pxに拡張

変更内容

web/comfyui/node_sets_menu.js — サイドパネルModels拡充

  • Modelsタブのサブタブ構成を2段に変更:
    • row1: All / ★ Favorites / 📁 Groups
    • row2: ◦ By Type
    • state.modelSubTab2 で行2の選択を管理
  • renderModelFavorites(container) — 全モデルを取得し state.modelMetadata[name]?.favorite === true のものを表示
  • renderModelGroups(container) — state.modelGroups からコラプス式セクションを生成
  • Workflowsサブタブ名: "Workflows" → "All"
  • Nodesサブタブ名: "Nodes" → "All"
  • Allビューでお気に入りに★表示:
    • createDraggableWfItem: wf.metadata?.favorite に応じて <span class="wfm-nlp-fav-star">★</span> を追加
    • renderAllNodes: state.metadata[name]?.favorite に応じて★を表示
  • .wfm-nlp-fav-star スタイル追加(color: #f5c518; font-size: 11px; margin-right: 3px)
  • サイドパネル幅: width: 280px → width: 310px
  • getWfModelTypes → getWfBadges にリネーム(wf.metadata?.badges ベースに変更)
  • wfModelTypes state → wfBadgeTypes にリネーム
  • renderWfModelType がバッジ表示に state.wfBadgeTypes を使用

static/js/models-tab.js — GenUI Modelボタン・バッジ表示改善

  • 型バッジの削除: ThumbView・CardView・SidePanelの renderSideInfo から typeBadge = badgeHtml(typeLabel) を削除。ユーザー定義バッジのみ表示

  • GENUI_TYPE_MAP 追加:

    const GENUI_TYPE_MAP = {
        checkpoint:  { key: "checkpoints",     inputKey: "ckpt_name" },
        lora:        { key: "loras",           inputKey: "lora_name" },
        vae:         { key: "vaes",            inputKey: "vae_name" },
        controlnet:  { key: "controlNets",     inputKey: "control_net_name" },
        unet:        { key: "diffusionModels", inputKey: "unet_name" },
        textencoder: { key: "textEncoders",    inputKey: "clip_name1" },
    };
    

    hypernetwork / embedding は対象外

  • applyToGenUI(modelName, modelType) 追加:

    • comfyUI.currentWorkflow からターゲット inputKey を持つノードを検索
    • node.inputs[inputKey] = modelName で直接セット
    • <select id="wfm-model-${key}"> の表示値も同期
    • <textarea id="wfm-gen-raw-json"> も同期
    • 成功トースト表示
  • 詳細モーダルの actions に「GenUI Model」ボタン追加(GENUI_TYPE_MAP に対応するタイプのみ表示)

  • サイドパネルの Save ボタン右隣に「GenUI Model」ボタン追加(同上)

  • export function openBadgeEditModal(onPaletteChange = null) — workflow-tab.js から呼び出せるよう export 化

  • bindBadgeModalEvents(onPaletteChange) — パレット変更後に renderBadgeFilter(), renderModelGrid(), onPaletteChange() を実行

static/js/workflow-tab.js — バッジ体系をモデルタブと統一

  • import { openBadgeEditModal } from "./models-tab.js" 追加
  • 自動解析によるバッジ体系を廃止:
    • state.badgeColors, BADGE_DEFAULT_COLORS 削除
    • 「Analysis」「Override」セクションを詳細モーダルから完全削除
    • getAllModelTypes() → getAllBadges() (wf.metadata?.badges ベース)
    • state.activeModel → state.activeBadge
  • 共有パレット関数を追加: getBadgePalette(), saveBadgePalette() (wfm_models_badge_palette localStorage キー共有)
  • badgeHtml(label) が共有パレットを参照するように変更
  • wfBadgesHtml(wf) ヘルパー追加(wf.metadata?.badges から表示用バッジHTML生成)
  • filterWorkflows() が wf.metadata?.badges でフィルタリング
  • 詳細モーダルにバッジチェックボックスUI追加(Modelsタブと同じ管理UI)
  • ⚙ボタン → renderViewSettings()(ビュー設定のみ)
  • 「⚙ Badge」ボタン追加 → openBadgeEditModal(() => renderGrid())

templates/index.html — Workflowツールバー更新

  • <button id="wfm-reanalyze-btn">Reanalyze All</button> 削除
  • <button id="wfm-badge-btn">⚙ Badge</button> 追加
  • ⚙設定ボタンの title を "View settings" に変更

static/js/i18n.js — 翻訳追加(EN/JA/ZH)

  • GenUI Model関連(3言語):
    • modelsGenUIBtn: "GenUI Model"
    • modelsGenUITitle: ボタンのツールチップ
    • modelsGenUIUnsupported: 非対応タイプへの警告
    • modelsGenUINoWorkflow: GenUIにワークフローが未読み込みの警告
    • modelsGenUINoNode(type): 対応ノードが見つからない場合の警告(アロー関数)

技術的な判断

  • バッジ統一の方針: 自動解析モデルタイプはユーザー操作が必要なオーバーライドが多く UX が複雑だったため廃止。完全にユーザー定義バッジへ移行し、ワークフロー/モデル間で同じパレットを共有する設計に統一
  • GenUI連携方式: comfyUI.currentWorkflow を直接ミューテーション。<select> 要素の DOM 同期も行うことで視覚的なフィードバックを確保
  • openBadgeEditModal のexport: モデルタブとワークフロータブが同じパレット管理 UI を共有するため、export function に変更。onPaletteChange コールバックで各タブが独立してグリッドを再描画

2026-03-28: v0.2.2 Modelsタブ・CivitAI連携

概要

  • ComfyUIにインストールされたモデル(Checkpoint、LoRA、VAE、ControlNet、UNET、TextEncoder)を一覧・管理する「Models」タブを新規追加
  • CivitAI API連携(SHA256ハッシュによるモデル情報取得、一括取得)
  • モデルグループ管理、プレビュー画像表示・変更、メタデータ永続化
  • ノードカードビュー改善、全タブのページネーションをツールバーに移動

新規ファイル

py/services/models_service.py — モデルメタデータサービス

  • _get_model_dirs(): ComfyUIのfolder_paths.get_folder_paths()でモデルディレクトリを解決
  • ModelsService: メタデータのCRUD(お気に入り、タグ、メモ、SHA256)
  • find_preview_image(): {stem}.preview.png等のプレビュー画像を自動検出
  • get_model_groups() / save_model_groups(): グループの永続化(model_metadata.jsonの_groupsキー)

py/services/civitai_service.py — CivitAI API連携

  • calculate_sha256(): モデルファイルのSHA256ハッシュ計算
  • fetch_by_hash(): CivitAI APIからモデル情報取得(キャッシュ付き)
  • batch_fetch(): 複数モデルの一括取得(プログレスコールバック対応)
  • _extract_info(): APIレスポンスから必要フィールドを抽出(modelName, baseModel, trainedWords, images等)
  • キャッシュ: civitai_cache.jsonに保存

py/routes/models_routes.py — モデル管理APIルート

  • GET/POST /api/wfm/models/metadata — メタデータCRUD
  • GET /api/wfm/models/preview — プレビュー画像配信
  • GET/POST /api/wfm/models/groups — グループ管理
  • POST /api/wfm/models/civitai/fetch — 個別CivitAI取得
  • GET /api/wfm/models/civitai/cache — CivitAIキャッシュ取得
  • POST /api/wfm/models/civitai/batch — SSEストリーミング一括取得
  • POST /api/wfm/models/change-preview — プレビュー画像アップロード
  • GET /api/wfm/models/filepath — モデルファイルのフルパス取得

static/js/models-tab.js — フロントエンドモジュール

  • state管理: modelsByType, modelMetadata, modelGroups, civitaiCache, activeModelType
  • サブタブ切り替え(6タイプ)、サムネイル/カード/テーブル表示
  • サイドパネル: Info(ファイルパス表示、タグ、メモ)、Group管理、CivitAI情報表示
  • 詳細モーダル: プレビュー画像、CivitAI情報、サムネイル変更
  • 一括CivitAI取得ボタン(SSEプログレス付き)
  • テーブルビューにメモ列表示

変更ファイル

py/config.py

  • MODEL_METADATA_FILE = DATA_DIR / "model_metadata.json" 追加

py/wfm.py

  • models_routesのインポートとルート登録追加

templates/index.html

  • Modelsタブボタン・セクション追加(サブタブナビ、ツールバー、グリッド、サイドパネル)
  • ヘルプタブにModels Tabセクション追加

static/js/app.js

  • models-tab.jsのimportとinitModelsTab()呼び出し追加
  • i18nマッピングにModelsタブ・ヘルプ用キー追加

static/js/nodes-tab.js

  • カードビュー: パッケージバッジ削除、カード左端にパッケージ色のボーダー追加
  • カードビュー: 入出力カウント表示を削除
  • ページネーションをツールバーに移動

static/js/workflow-tab.js

  • ページネーションをツールバーに移動
  • 1ページ24件表示

static/css/main.css

  • .wfm-grid: grid-template-columns: repeat(auto-fill, 161px)、カード161×162px固定
  • .wfm-node-card: 左ボーダー3pxスタイル追加
  • .wfm-pagination-inline: ツールバー内ページネーション
  • サブタブナビ(.wfm-models-type-nav)、テーブル列幅調整

static/js/i18n.js — 翻訳追加(EN/JA/ZH)

  • Modelsタブ関連: ~50キー(モデルタイプ、フィルタ、サイドパネル、CivitAI、グループ等)
  • ヘルプModelsセクション: helpModels1〜helpModels7
  • ファイルパス関連: modelsFilePath, modelsCopyPath, modelsCopiedPath

技術的な判断

  • プレビュー画像判定: HEADリクエストではなくimg.onload/onerrorパターンを採用(404コンソールスパム回避)
  • CivitAI一括取得: ブロッキングリクエストではなくSSE(Server-Sent Events)でリアルタイムプログレス表示
  • aiohttp HEAD自動登録: add_getが自動でHEADも登録するため、明示的なHEADルート追加は不要(重複RuntimeError回避)
  • テーブルビューメモ列: max-width: 200px+text-overflow: ellipsisで長文を省略表示
  • ファイルパス表示: バックエンドAPIでフルパスを解決し、クリックでクリップボードコピー

2026-03-27: テーマカスタマイズ機能・設定タブ改善

概要

  • テーマのカラーカスタマイズ、背景パターン、フォント選択機能を追加
  • 設定タブの各セクションをアコーディオン式折りたたみに変更
  • ヘルプタブのSettings Tab説明を更新

変更内容

static/js/settings-tab.js — テーマカスタマイズ機能追加

  • カラーオーバーライド: 6つのCSS変数(背景、サブ背景、サーフェス、テキスト、プライマリ、アクセント)をカラーピッカーでリアルタイム調整、localStorageに保存
  • 背景パターン: 7種類(なし、横/縦/斜めストライプ、ポルカドット、チェック、SVGファイル)を選択可能
    • パターンオプション: 色、不透明度、サイズ、間隔(gap)スライダー
    • SVGタイリング: FileReader APIでクライアント側読込、色の置換(fill/stroke属性+<style>要素注入)、::after疑似要素でopacity制御、gap対応はviewBox拡張ラッパーSVG方式
  • フォント選択: 16種のGoogle Fonts(日本語デザインフォント含む)を動的ロード、クリックで即時プレビュー
    • ゴシック系: Noto Sans JP, Zen丸ゴシック, M PLUS Rounded, 小杉丸ゴシック, さわらびゴシック, BIZ UDPゴシック
    • デザイン系: ドットゴシック16, はちまるポップ, デラゴシック, レゲエ One, ロックンロール, ステッキ, トレイン One
    • 等幅: Space Mono, Fira Code
  • 保存ロジック修正: saveLocalSettings()のスプレッドマージではdeleteしたキーが消えない問題を修正、リセット時はlocalStorage.setItem()で直接上書き
  • SVGリカラー強化: <style>ブロックをSVGに注入し、fill属性がない要素にも色を適用

static/css/main.css — UIスタイル追加

  • 設定アコーディオン: <details>/<summary>要素用のスタイル(.wfm-settings-section, .wfm-settings-summary)— 三角矢印回転アニメーション付き
  • テーマカスタマイザー: カラーピッカーグリッド、パターン選択グリッド(プレビューSVG付き)、パターンオプション行、フォント選択グリッド
  • パターンプレビュー改善: SVGプレビューのコントラストを大幅強化(60x60サイズ、太い線、高opacity、青系前景色)

static/js/i18n.js — 翻訳追加(EN/JA/ZH)

  • テーマカスタマイズ関連: カラー調整、背景パターン、パターンオプション(色、不透明度、サイズ、間隔)、フォント
  • フォント名: 16種のフォントラベル
  • ヘルプSettings項目: helpSettings7, helpSettings8追加

static/js/app.js — i18nマッピング追加

  • helpSettings7, helpSettings8 のHTML id→i18nキーマッピングを追加

templates/index.html — ヘルプタブ更新

  • Settings Tabセクション: テーマカスタマイズ機能の説明を追加(項目2)、Eagle連携を項目7に、アコーディオン折りたたみを項目8に追加

技術的な判断

  • SVGカラー変更: 属性置換だけでは不十分なSVG(CSSクラスやデフォルト黒を使用)に対応するため、<style>ブロック注入方式を採用
  • SVG間隔(gap): viewBoxを拡張するラッパーSVGでタイル間に余白を作成
  • フォント読込: Google Fonts CDNから動的にlinkタグを挿入、一度読み込んだフォントはSetで管理し重複読込を防止
  • アコーディオン: <details>/<summary>ネイティブHTML要素を使用、JSなしで開閉動作

2026-03-26: トップバーボタンアイコン修正・Appモードバッジ調査

概要

  • トップバーの3つのボタン(Workflow Studio / Snapshot / Library)のアイコンが表示されない問題を修正
  • ComfyUI Appモード(linearMode)のワークフローJSON構造を調査、バッジ表示対応は保留

変更内容

web/comfyui/top_menu_extension.js — アイコン置換ロジック修正

  • 問題: setup() 内の replaceButtonIcon() が requestAnimationFrame でボタンを見つけてSVGを注入するが、ComfyUIのVueフレームワークが再レンダリングして元の <i class="icon-[mdi--...]"> に戻してしまう。リトライ条件が wfmButtons.length === 0(ボタン未検出時のみ)のため、一度見つかった後は再注入されなかった
  • 修正: MutationObserver でボタンの親コンテナ(.actionbar-container)を監視し、DOMが変更されるたびにSVGアイコンを再注入するように変更
  • applyButtonIcon(): ボタンに既にSVGがあればスキップ(無限ループ防止)、なければSVG注入
  • waitAndObserve(): ボタンがDOMに現れるまで requestAnimationFrame で待機し、見つかったらアイコン置換+MutationObserver監視開始

Appモードバッジ表示について(保留)

  • ComfyUI Appモード(BETA機能)のワークフローJSON構造を調査
  • extra.linearMode: true と extra.linearData (inputs/outputs配列)でappモード情報が格納される
  • appモード削除後も linearData/linearMode キーは残り中身が空配列になるだけ、拡張子 .app.json も維持される
  • 結論: Appモード自体がBETA機能のため、バッジ表示対応は現状維持とする

2026-03-25: v0.2.0 プロンプトプリセット機能・Workflow Studio Library

概要

  • プロンプトプリセット機能を追加:サイドパネルからドラッグ&ドロップでWFS_PromptTextカスタムノードを作成
  • Workflow Studio SPAのPromptタブを3カラムレイアウトに刷新(AIアシスタント|プリセット編集|プリセットマネージャー)
  • プリセットのバックエンドAPI化(localStorage→サーバーサイド保存)
  • サイドパネル名を「WF & Node Library」→「Workflow Studio Library」に変更

変更内容

py/nodes/prompt_text.py — WFS_PromptTextカスタムノード(新規)

  • WFS_PromptText: ポジティブ/ネガティブの2つのSTRING入力とSTRING出力を持つカスタムノード
  • INPUT_TYPES: positive(multiline), negative(multiline)
  • RETURN_TYPES: ("STRING", "STRING"), RETURN_NAMES: ("positive", "negative")
  • CATEGORY: "Workflow Studio"

py/nodes/__init__.py — パッケージ初期化(新規)

  • 空ファイル(Pythonパッケージ認識用)

__init__.py — カスタムノード登録

  • _NODE_MODULES に WFS_PromptText を追加(分離読み込みパターン)
  • NODE_DISPLAY_NAME_MAPPINGS に "Prompt Text (WFS)" を追加

py/services/prompts_service.py — プリセットCRUDサービス(新規)

  • list_prompts(): 全プリセット一覧取得
  • create_prompt(data): 新規作成(name, text, negText, category, tags, favorite)
  • update_prompt(id, updates): 更新(name, text, negText, category, tags, favorite)
  • delete_prompt(id): 削除
  • list_categories(): カテゴリ一覧取得
  • データ永続化: data/prompts.json

py/routes/prompts_routes.py — プリセットAPIエンドポイント(新規)

  • GET /api/wfm/prompts — 一覧取得
  • POST /api/wfm/prompts — 新規作成
  • POST /api/wfm/prompts/update — 更新
  • POST /api/wfm/prompts/delete — 削除

py/config.py — 設定追加

  • PROMPTS_FILE = DATA_DIR / "prompts.json" を追加

py/wfm.py — ルート登録

  • prompts_routes のインポートと prompts_routes.setup_routes(app) を追加

web/comfyui/node_sets_menu.js — サイドパネルにPromptsタブ追加

  • トップタブ: Workflows / Nodes / Prompts の3タブ構成
  • Promptsサブタブ: All / ★ Favorites / 📁 Categories
  • State追加: promptSubTab, promptList, promptFavorites, promptCategories, promptLoaded
  • API関数: fetchPrompts(), loadPromptData()
  • レンダリング: renderPromptAll(), renderPromptFavorites(), renderPromptCategories()
  • ドラッグ&ドロップ: application/x-wfm-prompt MIMEタイプ、placePromptNode(posText, negText, promptName, pos) でWFS_PromptTextノード作成
  • P/Nコピーボタン: 各プリセットアイテムにポジティブ(P)・ネガティブ(N)の個別コピーボタン(Nはテキスト空の場合非表示)
  • パネル名変更: ツールチップ・タイトルを「Workflow Studio Library」に更新
  • CSS追加: .wfm-nlp-copy-btns, .wfm-nlp-copy-pos(緑ホバー), .wfm-nlp-copy-neg(赤ホバー)

templates/index.html — Promptタブ3カラムレイアウト

  • 2カラム(AI Assistant | Presets)→ 3カラム(AI Assistant | Presets | Preset Manager)に変更
  • Presetsパネル: Deleteボタン削除、Apply→「GenUI Set」、Category入力欄追加、下部にグループ管理行(Select group + Add to Group + + Group + Del Group)
  • Preset Managerパネル: All / ★ / Groupsタブ、検索、スクロール可能なリスト
  • ヘルプタブ: helpPrompt5〜7追加、helpSidepanel4・7〜10追加(Promptsタブ・ドラッグ&ドロップ・P/Nコピー)
  • サイドパネルタイトルを「Workflow Studio Library」に更新

static/css/main.css — スタイル追加(約150行)

  • .wfm-prompt-split-3col — 3カラムFlexboxレイアウト
  • .wfm-prompt-split-col — 各カラムのスタイル
  • .wfm-preset-manager — プリセットマネージャーパネル
  • .wfm-pm-tabs, .wfm-pm-tab — マネージャータブ
  • .wfm-pm-list, .wfm-pm-item — アイテムリスト
  • .wfm-pm-item-actions, .wfm-pm-action-btn — お気に入り(★)・削除(✕)ボタン
  • .wfm-pm-group-header — グループヘッダー

static/js/prompt-tab.js — 全面リライト

  • API化: fetchPresets(), apiCreatePreset(), apiUpdatePreset(), apiDeletePreset()
  • localStorage移行: migrateLocalStoragePresets() — 一回限りの自動移行
  • プリセット管理: renderPresetSelect(), renderGroupSelect(), selectPresetInEditor(preset)
  • Preset Manager: renderPresetManager(), renderPmAll(), renderPmFavorites(), renderPmGroups()
  • createPmItem(): お気に入りトグル(★)と削除(✕)ボタン(コピーボタンなし — Presetsパネル側にPP/NP Copyあり)
  • グループ管理: Presetsパネル下部のnew-group, add-to-group, del-groupボタン
  • Save: API経由でnegTextフィールドを含むプリセットの作成/更新

static/js/i18n.js — 翻訳更新

  • applyPreset → "GenUI Set"(英/日/中 全3言語)
  • deletePreset キー削除(全3言語)
  • ヘルプ: helpPrompt5〜helpPrompt7, helpSidepanel8〜helpSidepanel10 追加(全3言語)
  • サイドパネルタイトル: helpSidepanelTitle → "Workflow Studio Library" に更新(全3言語)

static/js/app.js — i18nマッピング更新

  • helpIdMap に helpPrompt5〜7, helpSidepanel8〜10 を追加
  • deletePreset 適用コード削除

README.md — v0.2.0更新

  • バージョンバッジ: 0.1.9 → 0.2.0
  • Prompt Tab: 3カラムレイアウト、Preset Manager、グループ管理、GenUI Set
  • Workflow Studio Library: Promptsタブ、ドラッグ&ドロップ、P/Nコピー
  • Project Structure: py/nodes/, prompts_routes.py, prompts_service.py 追加
  • Changelog: v0.2.0エントリ追加
  • スクリーンショット全8枚差し替え

2026-03-24: v0.1.9 サイドパネルUI改善・保存ダイアログ・バグ修正

概要

  • サイドパネルのサブタブを2段構成に変更、全ワークフロー/全ノード表示タブを追加
  • キャンバススナップショット保存時にファイル名編集ダイアログを表示
  • API/App形式バッジをワークフロー一覧に表示
  • グループ内の削除済みワークフロー(Not found)を自動クリーンアップ

変更内容

web/comfyui/node_sets_menu.js — サイドパネルサブタブ2段化

  • Workflows サブタブ:
    • 1段目: Workflows(全WF一覧)/ ★ Favorites / 📁 Groups
    • 2段目: ◦ Model Type
  • Nodes サブタブ:
    • 1段目: Nodes(全ノード一覧)/ ✳ Favorites / 📁 Groups
    • 2段目: ☰ Sets
  • State 変更: wfSubTab2, activeTab2 を追加(2段目タブの排他制御用)
  • 新レンダリング関数:
    • renderWfAll() — 全ワークフロー一覧表示
    • renderWfFavorites() — お気に入りワークフロー一覧(タブとして復活)
    • renderAllNodes() — LiteGraph.registered_node_types から全ノード一覧表示
  • API/App形式バッジ: createDraggableWfItem() でAPI形式(赤)・App形式(オレンジ)のバッジをファイル名前に表示
  • グループ自動クリーンアップ: loadWfData() でワークフロー一覧取得時に、存在しないファイルをグループから自動除去し localStorage を更新
  • Not found 表示削除: renderWfGroups() から削除済みワークフローの灰色表示コードを除去
  • CSS 追加:
    • .wfm-nlp-subtabs-row2 — 2段目サブタブ行
    • .wfm-nlp-fmt-badge, .wfm-nlp-fmt-api, .wfm-nlp-fmt-app — 形式バッジスタイル

web/comfyui/top_menu_extension.js — 保存ダイアログ追加

  • showSaveDialog(defaultName) — モーダルダイアログでファイル名を編集可能に
    • タイムスタンプ形式のデフォルト名がプレースホルダーとして全選択状態で表示
    • Enter/Saveボタンで確定、Escape/Cancel/オーバーレイクリックでキャンセル
    • .json 拡張子は自動付与
  • saveCanvasToWorkflowStudio() — 自動保存からダイアログ経由の保存に変更

py/services/workflow_analyzer.py — ワークフロー形式検出

  • analyze_workflow() の返却値に format フィールドを追加
    • "app": definitions(サブグラフ)キー存在 or extra.linearMode === true、フォールバックで .app.json ファイル名
    • "ui": nodes 配列 + links 存在(App特徴なし)
    • "api": 全トップレベル値に class_type 存在
    • "unknown": 判定不能

static/js/comfyui-workflow.js — フロントエンド形式検出も構造ベースに変更

  • detectFormat() をファイル名依存から definitions / extra.linearMode による構造判定に変更

static/js/workflow-tab.js — グループクリーンアップ

  • loadWorkflows() でワークフロー取得後にグループの不要エントリを自動削除

2026-03-24: v0.1.8 WF & Node Library サイドパネル拡張

概要

  • ComfyUI キャンバス右側の「Node Library」サイドパネルを「WF & Node Library」に拡張
  • ワークフローをサイドパネルからキャンバスにドラッグ&ドロップで読み込み可能に
  • ヘルプタブに Nodes Tab / WF & Node Library の機能一覧を追加

変更内容

web/comfyui/node_sets_menu.js — サイドパネル全面改修

  • パネル名変更: "Node Library" → "WF & Node Library"
  • 2階層タブ構造: トップレベル(Workflows / Nodes)+ サブタブ
    • Workflows サブタブ: Favorites(お気に入り)、Model Type(モデル種別)、Groups(グループ)
    • Nodes サブタブ: 既存の Favorites / Sets / Groups をそのまま維持
  • State 拡張: topTab, wfSubTab, wfList, wfFavorites, wfModelTypes, wfGroups, wfLoaded を追加
  • API 追加:
    • fetchWorkflows() — GET /api/wfm/workflows(既存エンドポイント流用)
    • fetchWorkflowRaw(filename) — GET /api/wfm/workflows/raw(既存エンドポイント流用)
    • loadWfData() — ワークフロー一覧取得 + favorites/modelTypes/groups 抽出
  • ワークフロー読み込み:
    • loadWorkflowOnCanvas(filename) — raw JSON 取得 → app.loadGraphData(data) でキャンバスに読み込み
    • 新 MIME タイプ application/x-wfm-workflow によるドラッグ&ドロップ
    • ダブルクリックでもワークフロー読み込み可能
  • ドロップハンドラー拡張: installCanvasDropHandler() で application/x-wfm-workflow を追加受付
  • レンダリング関数追加:
    • createDraggableWfItem(wf) — ワークフロー名 + モデルタイプバッジ表示
    • renderWfFavorites() — お気に入りワークフロー一覧
    • renderWfModelType() — モデルタイプ別折りたたみセクション(初期: 折りたたみ状態)
    • renderWfGroups() — localStorage グループ別折りたたみセクション(初期: 折りたたみ状態)
    • matchesWfSearch() — filename, tags, memo, summary に対する検索フィルタ
  • 折りたたみ初期状態: Model Type / Groups(WF・Node 両方)を初期状態で折りたたみ表示に変更
  • CSS 追加:
    • .wfm-nlp-subtabs — サブタブ行(背景色で視覚区別)
    • .wfm-nlp-top-tab — トップタブ(大きめフォント 12px, font-weight 600)
    • .wfm-nlp-sub-tab — サブタブ(小さめフォント 10px)
    • .wfm-nlp-item-label に text-overflow: ellipsis 追加(長いファイル名対応)
  • tooltip 更新: NODE_SETS_TOOLTIP → "WF & Node Library – Browse & drag workflows/nodes onto canvas"

web/comfyui/top_menu_extension.js — 変更なし

  • NODE_SETS_TOOLTIP を import しているため、tooltip 変更が自動反映

ヘルプタブ更新

  • templates/index.html:
    • Nodes Tab カード追加(6項目: ノードブラウザ、表示モード、お気に入り/タグ、グループ、サイドパネル、ノードセット)
    • WF & Node Library カード追加(7項目: ボタン起動、WFタブ、Nodesタブ、WFドラッグ、ノードドラッグ、ダブルクリック、検索)
    • About 説明文にノード管理を追記
  • static/js/i18n.js:
    • EN/JA/ZH に helpNodes1〜helpNodes6(6キー × 3言語)追加
    • EN/JA/ZH に helpSidepanel1〜helpSidepanel7 + helpSidepanelTitle(8キー × 3言語)追加
    • 各言語の helpAboutDesc にノード管理の記述を追記
  • static/js/app.js:
    • helpIdMap に Nodes(6エントリ)+ Sidepanel(8エントリ)のマッピング追加

README.md 更新

  • バージョンバッジ: 0.1.7 → 0.1.8
  • Screenshots セクション: Nodes Tab + WF & Node Library の行を追加(3行→4行、4枚→6枚)
  • ComfyUI Integration 画像をツールバー3ボタンにフォーカスした画像に差し替え
  • Features セクション: 新セクション「WF & Node Library (ComfyUI Side Panel) (v0.1.8)」追加
  • Changelog: v0.1.8 エントリ追加、v0.1.7 のサイドパネル記載を元に戻し
  • Project Structure: node_sets_menu.js の説明を更新

スクリーンショット追加

  • docs/screenshot_nodes.png — Nodes タブ(カードビュー + サイドパネル)
  • docs/screenshot_wf_node_library.png — ComfyUI 上の WF & Node Library サイドパネル
  • docs/screenshot_comfyui_topbar.png — ツールバー3ボタンにフォーカスした画像に差し替え

バックエンド変更

  • なし(既存の /api/wfm/workflows と /api/wfm/workflows/raw を流用)

2026-03-23: v0.1.7 Nodes タブ追加

概要

  • ComfyUI にインストールされた全ノードをブラウジング・整理・管理する「Nodes」タブを追加
  • ノードセット機能:複数ノード+接続情報をセットとして保存し、再利用可能に

変更内容

新規ファイル

  • py/services/nodes_service.py — ノードメタデータ(お気に入り、タグ、グループ)とノードセットの CRUD サービス
  • py/routes/nodes_routes.py — 9つの API エンドポイント(metadata, groups, node-sets の GET/POST/update/delete/export)
  • static/js/nodes-tab.js — Nodes タブのメインロジック(Node Browser + Node Sets サブビュー)
  • web/comfyui/node_sets_menu.js — ComfyUI キャンバス内サイドパネル(Favorites/Sets/Groups タブ、ドラッグ&ドロップ配置)

既存ファイル修正

  • py/wfm.py — nodes_routes.setup_routes(app) 追加
  • py/config.py — NODE_METADATA_FILE, NODE_SETS_FILE 定数追加
  • static/js/app.js — Nodes タブ登録、initNodesTab() 呼び出し
  • static/js/i18n.js — EN/JA/ZH に Nodes タブ関連 ~35 キー追加
  • static/js/comfyui-client.js — fetchAllObjectInfo() メソッド追加
  • static/css/main.css — ノードタブ固有スタイル(カード、テーブル、バッジ、ページネーション等)
  • templates/index.html — Nodes タブの HTML 構造追加
  • web/comfyui/top_menu_extension.js — Node Library ボタン+右クリック "Save as Node Set" コンテキストメニュー追加

機能詳細

Node Browser(Sub-view 1)

  • ComfyUI /object_info API から全ノード取得(遅延読み込み)
  • Card / Table の2ビュー切り替え
  • フィルタ: カテゴリ、パッケージ、タグ、グループ、お気に入り
  • 全文検索: name, display_name, description, search_aliases, tags
  • パッケージ名カラーバッジ(ハッシュベース色生成)
  • サイドパネル: ノード詳細(I/O仕様テーブル、タグ編集、グループ管理)
  • 1ページ50ノード表示(ページネーション)

Node Sets(Sub-view 2)

  • ComfyUI キャンバスで選択したノード+接続をセットとして保存
  • 右クリックコンテキストメニュー "Save as Node Set" から保存
  • セット一覧表示(名前、説明、ノード数、タグ)
  • 作成・編集・削除
  • ComfyUI 互換 JSON としてクリップボードにコピー

ComfyUI トップバー統合

  • 3ボタン: Workflow Studio / Snapshot / Node Library
  • Node Library ボタンでサイドパネル開閉

バグ修正

  • node_sets_menu.js で const saveSelectedAsNodeSet が二重定義されていた ESM SyntaxError を修正(Vite preload wrapper により完全にサイレントだった)
  • デバッグ用 console.log 削除

2026-03-23: v0.1.6 パストラバーサル脆弱性修正

概要

  • ComfyUI-Manager PR #2706 のレビューで指摘されたセキュリティ脆弱性を修正
  • workflow_service.py で任意のファイルパスへのアクセスが可能だった問題を解消

背景

  • ltdrdata(ComfyUI-Manager メンテナー)から import_files メソッド内のパス構築が安全でないとの指摘
  • original_name に ../../etc/passwd のようなパストラバーサル文字列を送ることで、workflows_dir 外のファイルを読み書きできる状態だった

変更内容

py/services/workflow_service.py

  • _validate_filename() 強化 — ".", "..", null バイト (\x00) のチェックを追加
  • _safe_path() メソッド新規追加 — ファイル名バリデーション + resolve() でパスを正規化し、workflows_dir 配下にあることを検証。違反時は ValueError を送出
  • 全公開メソッドに _safe_path() 適用:
    • import_files — ループ冒頭で _validate_filename チェック追加、パス構築を _safe_path に変更
    • get_raw — _safe_path に変更
    • rename — old/new 両パスを _safe_path に変更
    • delete — _safe_path に変更
    • analyze — _safe_path に変更
    • change_thumbnail — _safe_path に変更

2026-03-22: v0.1.5 テーマシステム追加

概要

  • 13種類のビルトインテーマを設定タブから切り替え可能に
  • テーマごとにカラー・フォント・角丸・影・特殊効果を変更
  • 選択テーマはlocalStorageに保存、ページ読み込み時にフラッシュなく復元

変更内容

テーマ定義(CSS)

  • static/css/main.css — 12テーマの[data-theme="xxx"]変数定義を末尾に追加
    • Pop & Vibrant / Light Minimalist / Cyberpunk / Glassmorphism / Neumorphism / Retro 8-bit / Pastel Dream / Brutalism / Earthy / Material UI / Monotone + Accent / Corporate Trust
  • テーマ固有の特殊効果CSS:
    • Cyberpunk: ネオン発光ボーダー(box-shadow)、JSON構文ハイライト色変更
    • Glassmorphism: backdrop-filter: blur(12px) + メッシュグラデーション背景
    • Neumorphism: ダブルシャドウ(凸凹表現)、insetシャドウボタン
    • Retro-pixel / Brutalism: 太い黒ボーダー(border: 3px solid #000)
  • .wfm-theme-grid / .wfm-theme-card / .wfm-theme-swatch — テーマ選択UIスタイル

テーマ選択UI(JavaScript)

  • static/js/settings-tab.js:
    • THEMES 配列: テーマID・i18nキー・プレビュー用4色スウォッチを定義
    • applyTheme(themeId) — <html>のdata-theme属性を設定/解除
    • getSavedTheme() — localStorageから保存テーマを取得
    • 設定タブにカラースウォッチ付きグリッドUIを生成(言語設定の直下)
    • クリックで即座にテーマ適用 + localStorage保存 + activeクラス更新
  • static/js/app.js:
    • applyTheme / getSavedTheme をインポート
    • DOMContentLoadedの前にテーマ復元を実行(テーマフラッシュ防止)

多言語対応

  • static/js/i18n.js — 3言語(EN/JA/ZH)に themeLabel + 13テーマ名の翻訳キー追加

その他

  • templates/index.html — ヘルプページのGitHubリンクURL修正
  • README.md — v0.1.5更新、テーマ機能記載、git clone URL修正

スクリーンショット

Workflow Tab (Pastel Dream) Settings Tab (Theme)
Workflow Settings

2026-03-22: v0.1.4 App形式対応・プリセットコピー・分析バグ修正

概要

  • ComfyUI App形式(.app.json)ワークフローの識別・表示対応
  • プリセットタブにポジティブ/ネガティブプロンプトのクリップボードコピーボタン追加
  • ワークフロー分析のクラッシュバグ修正

変更内容

App形式ワークフロー対応

  • 背景: ComfyUIの最新アップデートでAppモード(ノードを簡略化したWebアプリ風UI)が追加され、.app.json拡張子で保存される
  • 対応:
    • static/js/comfyui-workflow.js — detectFormat()にファイル名ベースのApp形式判定を追加
    • static/js/workflow-tab.js — サイドパネルJSONタブのフォーマットバッジに「App形式」表示を追加
    • static/js/generate-tab.js — App形式ワークフローの生成UI読み込みをブロックし、「ComfyUIで開く」への誘導メッセージを表示
    • static/css/main.css — .wfm-format-badge--app スタイル追加(オレンジ色)
    • static/js/i18n.js — appFormat / appFormatNotSupported 翻訳キー追加(EN/JA/ZH)

プリセット クリップボードコピーボタン

  • 課題: プリセットのプロンプトをComfyUI本体で使う際に手動コピーが必要だった
  • 対応:
    • templates/index.html — プリセット保存ボタン行に「PP コピー」「NP コピー」ボタンを追加(flex:2/2/1比率)
    • static/js/prompt-tab.js — クリップボードコピー処理(navigator.clipboard.writeText)追加
    • static/js/app.js — コピーボタンのi18n適用
    • static/js/i18n.js — copyPositivePrompt / copyNegativePrompt / copiedToClipboard / noTextToCopy 翻訳キー追加

ワークフロー分析バグ修正

  • 問題: UI形式ワークフローのwidgets_values[0]が整数(例: EmptyLatentImageの768)の場合、.lower()でAttributeErrorが発生し、例外キャッチにより全分析結果がゼロになる
  • 修正: py/services/workflow_analyzer.py — _model_name_from_ui_node()にisinstance(val, str)型チェックを追加

2026-03-21: v0.1.3 ヘルプ&サポートタブ追加

概要

  • ヘルプ&サポートタブを新規追加(機能一覧 + サポートリンク)

変更内容

ヘルプ&サポートタブ

  • 課題: プラグインの機能概要を確認する場所がなく、サポートへの導線もなかった
  • 対応: 新しいメインタブ「Help」を追加し、機能一覧とサポートセクションを設置
機能一覧セクション
  • 各タブ(Workflow / GenerateUI / Prompt / Settings)の主要機能をカード形式で表示
  • Tipsセクション(ドラッグ&ドロップ、お気に入り、デフォルトワークフロー)
サポートセクション
  • GitHubリンク(バグ報告・機能リクエスト・コントリビュート)
  • Ko-fiリンク(開発支援)
  • SVGアイコン付きのリンクカード

変更ファイル

  • templates/index.html — Helpタブボタン追加、ヘルプ&サポートセクションのHTML構造
  • static/js/i18n.js — 3言語(EN/JA/ZH)のヘルプ関連翻訳キー30項目追加
  • static/js/app.js — タブマップにhelp追加、applyI18nToHtml()にヘルプタブのi18n適用ロジック追加
  • static/css/main.css — .wfm-help-* スタイル追加(コンテナ、セクション、カード、リンク)

2026-03-19: v0.1.2 リリース

概要

  • キャンバススナップショット機能追加(ComfyUIトップバーにカメラボタン)
  • ワークフローサイドパネル・モーダルにサムネイルプレビュー追加

変更内容

Canvas Snapshot(キャンバススナップショット)

  • 課題: ワークフローのキャンバス画像を保存するにはComfyUIのエクスポート機能を使い手動でファイル管理する必要があった
  • 対応: ComfyUIトップバーにカメラボタンを追加し、ワンクリックでキャンバスをPNGキャプチャ→ワークフローデータフォルダに自動保存
  • web/comfyui/top_menu_extension.js:
    • カメラアイコンボタン追加(actionBarButtons API / legacy ComfyButtonGroup 両対応)
    • captureCanvasSnapshot() — LiteGraphキャンバスの状態保存→ノード範囲計算→レンダリング→Blob化→復元
    • PNG tEXtチャンク埋め込み (n2b, joinArrayBuffer, crc32, embedWorkflowInPng) — ComfyUIのドラッグ&ドロップインポート互換
    • タイムスタンプファイル名 wf_YYYYMMDDHHmmss.json 形式で自動生成
  • py/routes/workflow_routes.py:
    • POST /api/wfm/workflows/save-canvas-image ルート追加
    • import_files を利用してPNGからワークフローJSON抽出+サムネイル保存+自動インポート
  • 参考実装: pythongosssss/ComfyUI-Custom-Scripts の workflowImage.js

サムネイルプレビュー

  • templates/index.html — サイドパネルにThumbnailタブ追加(デフォルトアクティブ)
  • static/js/workflow-tab.js:
    • sidePanelThumbUpdate() — 選択ワークフローのサムネイル・メタ情報表示
    • モーダル上部にサムネイルセクション追加
    • サムネイル変更時にサイドパネル・モーダル両方を同期更新
  • static/css/main.css — .wfm-side-thumb-* / .wfm-modal-thumb-* スタイル追加

スクリーンショット

Workflow Tab ComfyUI Top Bar
Workflow TopBar

リリース


2026-03-19: v0.1.1 リリース

概要

  • ファビコン追加
  • プロンプトタブをサブタブ切り替え方式から左右2分割レイアウトに変更

変更内容

ファビコン追加

  • static/favicon.svg — 起動ボタンと同じ W + S Wave デザインの SVG ファビコンを新規作成
  • templates/index.html — <link rel="icon"> タグ追加

プロンプトタブ 2分割レイアウト

  • 課題: Assistant / Presets のサブタブ切り替え方式では画面幅が広い場合に視点移動が大きく使いづらい
  • 対応: サブタブナビゲーションを廃止し、左パネル(AIアシスタント)・右パネル(プリセット)の同時表示レイアウトに変更
  • templates/index.html — サブタブ構造を .wfm-prompt-split 左右分割構造に変更
  • static/css/main.css — .wfm-prompt-subtab-* スタイルを .wfm-prompt-split / .wfm-prompt-split-header に置換
  • static/js/prompt-tab.js — サブタブ切り替えイベントリスナー削除
  • static/js/app.js — 翻訳対象をサブタブボタンからパネルヘッダーに変更

スクリーンショット

Prompt Tab


2026-03-18: プロジェクトリネーム & アイコン適用

概要

  • ComfyUI-Workflow-Manager → ComfyUI-Workflow-Studio にリネーム
  • 起動ボタンのアイコンを W + S Wave デザインに差し替え

背景

  • 既存の ComfyUI-WorkflowManager (yichengup) と名前が衝突するため、GitHub公開・ComfyUI Manager登録に向けてリネーム
  • アイコンは9種類のWSモノグラムバリエーションから「W + S Wave」(W文字 + S字カーブ) を選定

変更内容

  • ディレクトリ名: ComfyUI-Workflow-Manager/ → ComfyUI-Workflow-Studio/
  • 全ファイルの "Workflow Manager" → "Workflow Studio" 置換
  • top_menu_extension.js:
    • 関数名 openWorkflowManager → openWorkflowStudio
    • 拡張名 WorkflowManager.TopMenu → WorkflowStudio.TopMenu
    • getWfmIcon() を W + S Wave SVGに差し替え

2026-03-18: デフォルトビュー設定追加

概要

ワークフロータブの歯車アイコン設定パネルに「デフォルトビュー」設定を追加

変更ファイル

  • static/js/workflow-tab.js - ラジオボタンUI追加、localStorage連携
  • static/js/i18n.js - defaultView/viewThumbnail/viewCard/viewTable キー追加
  • static/css/main.css - .wfm-settings-radio-group スタイル追加

2026-03-18: MarkdownNote表示専用ノード除外

概要

MarkdownNote等の表示専用ノード(入出力接続なし、object_info未登録)がAPI変換に含まれエラーになる問題を修正

変更ファイル

  • static/js/comfyui-workflow.js - _isDisplayOnlyNode() ヘルパー追加、convertUiToApiでスキップ

2026-03-18: サブグラフワークフロー対応 & 変換精度修正

概要

ComfyUIサブグラフ(LongCat Image等)を含むワークフローの生成時エラーを修正

問題

  1. サブグラフノードのUUID型typeがComfyUIバックエンドで認識されない
  2. リンク済み入力のwidgets_values消費漏れでbatch_sizeが1024になる
  3. "COMBO" 文字列型がウィジェット型として認識されない

修正内容 (static/js/comfyui-workflow.js)

  • _flattenSubgraphs(workflow) 関数追加: サブグラフを parentId:internalId 形式に展開
  • リンク済み入力でも wIdx をインクリメントするよう修正
  • _getWidgetInputNames / _getWidgetInputTypes に "COMBO" 文字列型を追加

2026-03-18: Raw JSON同期修正

概要

GenerateUIの各タブでApply操作後、Raw JSONテキストエリアが更新されない問題を修正

変更ファイル

  • static/js/comfyui-editor.js - _syncRawJson() ヘルパー追加、全Apply箇所から呼び出し

プロジェクト構成 (現在)

ComfyUI-Workflow-Studio/
├── __init__.py              # ComfyUIエントリーポイント
├── py/
│   ├── wfm.py               # メインクラス (WorkflowStudio)
│   ├── config.py             # パス設定
│   ├── routes/               # APIルート
│   └── services/             # ビジネスロジック
├── templates/index.html      # SPA (Workflow/GenerateUI/Prompt/Settings/Help)
├── static/                   # CSS/JS
├── web/comfyui/              # ComfyUIメニュー拡張
└── data/                     # ワークフロー・メタデータ・設定

2026-03-18: JSONシンタックスハイライト

概要

ワークフロータブ・生成UIタブのJSON表示にシンタックスハイライトを追加

色分け (eagle_comic_creater_webと同じOne Atomテーマ風)

  • 黄 #e5c07b — name, scheduler
  • ピンク #c678dd — title
  • 緑 #98c379 — width, height
  • 水色 #61afef — text, prompt
  • 赤 #e06c75 — image, file
  • ベース #abb2bf — その他

変更ファイル

  • static/js/json-highlight.js — 新規: highlightJSON, syncJsonHighlight, syncScroll
  • static/js/workflow-tab.js — サイドパネルJSON表示に highlightJSON 適用
  • templates/index.html — Raw JSONを <pre> + <textarea> レイヤー構造に変更
  • static/js/generate-tab.js — ハイライト同期、input/scrollイベント追加
  • static/js/comfyui-editor.js — _syncRawJson にハイライト同期追加
  • static/css/main.css — .wfm-json-container, .wfm-json-highlight, .json-key-* スタイル追加

2026-03-18: プロンプトタブ 中国語↔英語翻訳追加

概要

プロンプトタブの翻訳機能に ZH→EN / EN→ZH を追加

変更ファイル

  • templates/index.html — ZH→EN / EN→ZH ボタン追加
  • static/js/prompt-tab.js — sendTranslate に zh2en / en2zh プロンプト追加、イベントリスナー追加

2026-03-18: v0.1.0 リリース

概要

GitHub公開・ComfyUI Manager登録・初回リリース

実施内容


概要

  • ギャラリータブ(Phase 1)実装:6800枚超の画像ブラウザ
  • Workflow / Nodes / Gallery の Group UI を Models タブ基準に統一
  • Settings タブを2カラムレイアウトに変更(テーマを右固定)
  • 3カラム表示 — サムネイル / テーブル切り替え、Fav列を左端に
  • サーバーサイドフィルタ — グループ・お気に入り・タグによる server-side filtering
  • マルチセレクト — Ctrl+クリックで複数選択、Bulk Bar でまとめて操作
  • グループ管理 — 作成・Rename・削除(Models パネルと同一UIパターン)
  • os.scandir() + フォルダキャッシュ — 6800枚環境でのパフォーマンス改善
  • 出力フォルダ設定 — Settings タブからギャラリー参照フォルダを変更可能

Group UI 統一

  • Workflow タブ — renderSideGroup() を新設し Models 4 セクションパターンに変更(Current Groups / Add to Group / Create New Group / Manage Groups)。冗長なヘッダーブロック削除
  • Nodes タブ — renderSideGroups() を全面書き換え。× ボタン・"Add" ボタン・"Rename (✎)" 追加
  • Gallery タブ — renderDetailGroup() を新設、同パターン適用

Settings 2カラムレイアウト

  • テーマパネルを右側(float:right; width:50%)に固定
  • 左側に UI言語〜保存ボタンを連続配置(ギャップなし)
  • position:sticky; top:0 でテーマパネルをスクロールに追従

変更ファイル

  • py/services/gallery_metadata.py — rename_group(), get_group_member_set() 追加
  • py/services/gallery_service.py — _FolderCache, _scan_folder(), group_filter 対応
  • py/routes/gallery_routes.py — PUT /wfm/gallery/groups/{name} 追加
  • static/js/gallery-tab.js — マルチセレクト・サーバーサイドフィルタ・renderDetailGroup
  • static/js/nodes-tab.js — renderSideGroups() 全面書き換え
  • static/js/workflow-tab.js — renderSideGroup() 新設、旧ハンドラ削除
  • static/js/settings-tab.js — 2カラムレイアウト HTML 構造変更
  • static/css/gallery-tab.css — multi-selected・bulk-bar・セクションタイトルスタイル
  • static/css/main.css — Settings レイアウト CSS(float:right ベース 50:50)
  • templates/index.html — Gallery "Group"→"Groups"、各タブ static HTML → JS動的生成、Gallery ヘルプカード追加