Compare commits

...
2 Commits
Author SHA1 Message Date
ketle-manandClaude Sonnet 4.6 f25c042c39 v0.2.8: Move data dir to user/default/Workflow-Studio, add export/import
- py/config.py: DATA_DIR now resolves to ComfyUI/user/default/Workflow-Studio/
  with fallback to data/ when user/default/ does not exist
- py/routes/settings_routes.py: add GET /api/wfm/settings/export and
  POST /api/wfm/settings/import endpoints for full data bundle backup
- static/js/settings-tab.js: add Data Management section (export/import UI)
- static/js/i18n.js: add 8 new keys in EN/JA/ZH
- templates/index.html, static/js/app.js: add helpSettings8/9 to Help tab
- README.md, DEVLOG.md: updated for v0.2.8

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:11:40 +09:00
ketle-manandClaude Sonnet 4.6 1c9ec7ac19 Fix: Gallery tab auto-selects root folder on load
Previously the gallery showed "Select a folder to browse images."
until the user manually clicked a folder. Now the root folder is
automatically selected when the folder tree loads.

Also update docs/screenshot_gallery.png and screenshot_settings.png.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 10:09:12 +09:00
11 changed files with 263 additions and 11 deletions
+61
View File
@@ -1,5 +1,66 @@
# DEVLOG - ComfyUI-Workflow-Studio
## 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改善・バッジ保存修正・フォルダフィルター追加
### 概要
+7 -2
View File
@@ -5,7 +5,7 @@ A comprehensive workflow management and generation UI plugin for [ComfyUI](https
Browse, organize, and execute workflows directly from a dedicated studio interface — without switching between windows or manually editing JSON.
![Workflow Studio](https://img.shields.io/badge/ComfyUI-Custom_Node-blue)
![Version](https://img.shields.io/badge/version-0.2.7-green)
![Version](https://img.shields.io/badge/version-0.2.8-green)
## Screenshots
@@ -82,6 +82,7 @@ Browse, organize, and execute workflows directly from a dedicated studio interfa
- **Eagle connection** — set Eagle API endpoint for auto-save
- **Ollama connection** — configure Ollama server URL
- **Default workflow** — set a workflow to auto-load on startup
- **Data Management** — export all plugin data (settings, metadata, prompts, etc.) to a single JSON file; import to restore data (useful when migrating or reinstalling)
- **Language** — English / Japanese / Chinese
### Gallery Tab (v0.2.7)
@@ -207,6 +208,10 @@ Click the **camera icon** (next to the W button) in ComfyUI's top bar to capture
## Changelog
### v0.2.8
- **Data storage moved** — plugin data (settings, metadata, prompts, etc.) is now stored in `ComfyUI/user/default/Workflow-Studio/` instead of `custom_nodes/ComfyUI-Workflow-Studio/data/`; falls back to the old `data/` directory if `user/default/` does not exist
- **Data Management section** — new Settings tab section to export all plugin data to a single JSON file and import it back; useful for migration, backup, and reinstallation
### v0.2.7
- **Gallery tab** — new image browser for ComfyUI output folder; Thumbnail/Table views, multi-select with Bulk Bar, server-side group/tag/favorite filtering, group management (rename support), performance-optimized with + mtime folder cache
- **Group UI unified** — Workflow, Nodes, and Gallery group panels rebuilt to match the Models tab 4-section pattern (Current Groups / Add to Group / Create New Group / Manage Groups); Rename support added to Workflow and Nodes tabs
@@ -350,7 +355,7 @@ ComfyUI-Workflow-Studio/
├── web/comfyui/
│ ├── top_menu_extension.js # ComfyUI menu bar integration
│ └── node_sets_menu.js # Workflow Studio Library side panel
└── data/ # Metadata & settings storage (incl. prompts.json, model_metadata.json, civitai_cache.json)
└── data/ # Fallback data dir (used when ComfyUI user/default/ is not found)
```
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

+12 -4
View File
@@ -10,13 +10,21 @@ PLUGIN_DIR = Path(__file__).resolve().parent.parent
# Directory paths
TEMPLATES_DIR = PLUGIN_DIR / "templates"
STATIC_DIR = PLUGIN_DIR / "static"
DATA_DIR = PLUGIN_DIR / "data"
# Default workflows directory: ComfyUI's user/default/workflows
# PLUGIN_DIR is custom_nodes/ComfyUI-Workflow-Studio
# ComfyUI root is custom_nodes/../../ = two levels up
_COMFYUI_ROOT = PLUGIN_DIR.parent.parent
_COMFYUI_WORKFLOWS = _COMFYUI_ROOT / "user" / "default" / "workflows"
# Data directory: prefer ComfyUI's user/default/Workflow-Studio/, fallback to plugin data/
_COMFYUI_USER_DEFAULT = _COMFYUI_ROOT / "user" / "default"
if _COMFYUI_USER_DEFAULT.is_dir():
DATA_DIR = _COMFYUI_USER_DEFAULT / "Workflow-Studio"
logger.info("Workflow Studio: Using user data dir: %s", DATA_DIR)
else:
DATA_DIR = PLUGIN_DIR / "data"
logger.info("Workflow Studio: user/default not found, using plugin data dir: %s", DATA_DIR)
# Default workflows directory: ComfyUI's user/default/workflows
_COMFYUI_WORKFLOWS = _COMFYUI_USER_DEFAULT / "workflows"
# Use ComfyUI workflows dir if it exists, otherwise fallback to plugin data dir
if _COMFYUI_WORKFLOWS.is_dir():
+84 -1
View File
@@ -1,18 +1,30 @@
"""Settings API routes."""
import asyncio
import json
import logging
from pathlib import Path
from aiohttp import web
from ..services.settings_service import SettingsService
from ..config import DEFAULT_WORKFLOWS_DIR
from ..config import DEFAULT_WORKFLOWS_DIR, DATA_DIR
logger = logging.getLogger(__name__)
_service = SettingsService()
# Data files included in export/import
_DATA_FILES = [
"settings.json",
"metadata.json",
"node_metadata.json",
"node_sets.json",
"prompts.json",
"model_metadata.json",
"gallery_metadata.json",
]
def setup_routes(app: web.Application):
"""Register settings API routes."""
@@ -22,6 +34,8 @@ def setup_routes(app: web.Application):
app.router.add_post("/api/wfm/settings/workflows-dir", handle_set_workflows_dir)
app.router.add_get("/api/wfm/settings/output-dir", handle_get_output_dir)
app.router.add_post("/api/wfm/settings/output-dir", handle_set_output_dir)
app.router.add_get("/api/wfm/settings/export", handle_export)
app.router.add_post("/api/wfm/settings/import", handle_import)
async def handle_get(request: web.Request) -> web.Response:
@@ -151,3 +165,72 @@ async def handle_set_output_dir(request: web.Request) -> web.Response:
except Exception as e:
logger.error("Error setting output dir: %s", e)
return web.json_response({"error": str(e)}, status=500)
def _load_data_file(filename: str):
"""Load a single data file, return empty dict/list if missing."""
path = DATA_DIR / filename
if not path.exists():
return None
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return None
def _build_export_bundle() -> dict:
"""Collect all data files into a single export bundle."""
bundle = {"__version": 1, "__source": "ComfyUI-Workflow-Studio"}
for filename in _DATA_FILES:
data = _load_data_file(filename)
if data is not None:
bundle[filename] = data
return bundle
async def handle_export(request: web.Request) -> web.Response:
"""GET /api/wfm/settings/export - Download all data as a single JSON bundle."""
try:
bundle = await asyncio.to_thread(_build_export_bundle)
body = json.dumps(bundle, ensure_ascii=False, indent=2)
return web.Response(
body=body.encode("utf-8"),
content_type="application/json",
headers={"Content-Disposition": 'attachment; filename="wfm-data-export.json"'},
)
except Exception as e:
logger.error("Error exporting data: %s", e)
return web.json_response({"error": str(e)}, status=500)
def _apply_import_bundle(bundle: dict) -> dict:
"""Write imported bundle back to individual data files. Returns summary."""
summary = {"imported": [], "skipped": []}
DATA_DIR.mkdir(parents=True, exist_ok=True)
for filename in _DATA_FILES:
if filename not in bundle:
summary["skipped"].append(filename)
continue
try:
path = DATA_DIR / filename
with open(path, "w", encoding="utf-8") as f:
json.dump(bundle[filename], f, ensure_ascii=False, indent=2)
summary["imported"].append(filename)
except Exception as e:
logger.error("Import failed for %s: %s", filename, e)
summary["skipped"].append(filename)
return summary
async def handle_import(request: web.Request) -> web.Response:
"""POST /api/wfm/settings/import - Upload and restore a JSON bundle."""
try:
bundle = await request.json()
if not isinstance(bundle, dict):
return web.json_response({"error": "Invalid bundle format"}, status=400)
summary = await asyncio.to_thread(_apply_import_bundle, bundle)
return web.json_response({"status": "ok", **summary})
except Exception as e:
logger.error("Error importing data: %s", e)
return web.json_response({"error": str(e)}, status=500)
+1
View File
@@ -273,6 +273,7 @@ function applyI18nToHtml() {
"wfm-help-settings-3": "helpSettings3", "wfm-help-settings-4": "helpSettings4",
"wfm-help-settings-5": "helpSettings5", "wfm-help-settings-6": "helpSettings6",
"wfm-help-settings-7": "helpSettings7", "wfm-help-settings-8": "helpSettings8",
"wfm-help-settings-9": "helpSettings9",
"wfm-help-nodes-1": "helpNodes1", "wfm-help-nodes-2": "helpNodes2",
"wfm-help-nodes-3": "helpNodes3", "wfm-help-nodes-4": "helpNodes4",
"wfm-help-nodes-5": "helpNodes5", "wfm-help-nodes-6": "helpNodes6",
+5
View File
@@ -76,6 +76,11 @@ async function loadFolderTree() {
}
tree.innerHTML = "";
renderTreeNode(data, tree, 0, true);
// root フォルダを自動選択(フォルダ未選択の場合のみ)
if (!state.currentFolder) {
const firstLabel = tree.querySelector(".wfm-gallery-tree-label");
if (firstLabel) firstLabel.click();
}
} catch (e) {
tree.innerHTML = `<p class="wfm-placeholder">Error: ${e.message}</p>`;
}
+30 -3
View File
@@ -340,7 +340,8 @@ const LANGUAGES = {
helpSettings5: "Ollama URL, default model, and connection test",
helpSettings6: "Default workflow setting",
helpSettings7: "Eagle integration URL and auto-save toggle",
helpSettings8: "All sections are collapsible accordion panels",
helpSettings8: "Data Management: export all plugin data (settings, metadata, prompts, etc.) to a JSON file, or import to restore. After importing, restart ComfyUI.",
helpSettings9: "All sections are collapsible accordion panels",
helpNodes1: "Node Browser: search by name/description/alias/tag, filter by category, package, tag, group, favorites",
helpNodes2: "Card and Table view modes with pagination",
helpNodes3: "Favorite nodes and add custom tags for organization",
@@ -433,6 +434,14 @@ const LANGUAGES = {
eagleIntegration: "Eagle Integration (Optional)",
eagleUrl: "Eagle URL",
eagleAutoSave: "Auto-save generated images to Eagle",
dataManagement: "Data Management",
dataManagementHint: "Export all plugin data (settings, metadata, prompts, etc.) to a JSON file, or import a previously exported file to restore data. After importing, restart ComfyUI to apply changes.",
exportData: "Export",
importData: "Import",
exportSuccess: "Export complete",
exportError: "Export failed",
importSuccess: "Import complete",
importError: "Import failed",
saveSettings: "Save Settings",
settingsSaved: "Settings saved",
ollamaSaved: "Ollama settings saved",
@@ -830,7 +839,8 @@ const LANGUAGES = {
helpSettings5: "Ollama URL、デフォルトモデル、接続テスト",
helpSettings6: "デフォルトワークフローの設定・解除",
helpSettings7: "Eagle連携URL、自動保存の有効/無効",
helpSettings8: "各セクションはアコーディオン式で折りたたみ可能",
helpSettings8: "データ管理:設定・メタデータ・プロンプトなど全データをJSONにエクスポート、またはインポートして復元。インポート後はComfyUIを再起動してください。",
helpSettings9: "各セクションはアコーディオン式で折りたたみ可能",
helpNodes1: "ノードブラウザ:名前/説明/エイリアス/タグで検索、カテゴリ・パッケージ・タグ・グループ・お気に入りフィルタ",
helpNodes2: "カード / テーブル表示モード切り替え(ページネーション付き)",
helpNodes3: "ノードのお気に入り登録、カスタムタグの追加で整理",
@@ -923,6 +933,14 @@ const LANGUAGES = {
eagleIntegration: "Eagle 連携 (オプション)",
eagleUrl: "Eagle URL",
eagleAutoSave: "生成画像をEagleに自動保存",
dataManagement: "データ管理",
dataManagementHint: "設定・メタデータ・プロンプトなど全データをJSONファイルにエクスポート、またはインポートして復元できます。インポート後はComfyUIを再起動してください。",
exportData: "エクスポート",
importData: "インポート",
exportSuccess: "エクスポート完了",
exportError: "エクスポート失敗",
importSuccess: "インポート完了",
importError: "インポート失敗",
saveSettings: "設定を保存",
settingsSaved: "設定を保存しました",
ollamaSaved: "Ollama設定を保存しました",
@@ -1316,7 +1334,8 @@ const LANGUAGES = {
helpSettings5: "Ollama URL、默认模型、连接测试",
helpSettings6: "默认工作流设置/取消",
helpSettings7: "Eagle集成URL、自动保存开关",
helpSettings8: "各部分可折叠展开",
helpSettings8: "数据管理:将所有插件数据(设置、元数据、提示词等)导出为JSON文件,或导入以恢复。导入后请重启ComfyUI。",
helpSettings9: "各部分可折叠展开",
helpNodes1: "节点浏览器:按名称/描述/别名/标签搜索,按分类、包、标签、组、收藏筛选",
helpNodes2: "卡片和表格视图模式切换(带分页)",
helpNodes3: "收藏节点,添加自定义标签进行整理",
@@ -1409,6 +1428,14 @@ const LANGUAGES = {
eagleIntegration: "Eagle 集成 (可选)",
eagleUrl: "Eagle URL",
eagleAutoSave: "自动保存生成图片到Eagle",
dataManagement: "数据管理",
dataManagementHint: "将所有插件数据(设置、元数据、提示词等)导出为JSON文件,或导入之前导出的文件以恢复数据。导入后请重启ComfyUI以使更改生效。",
exportData: "导出",
importData: "导入",
exportSuccess: "导出完成",
exportError: "导出失败",
importSuccess: "导入完成",
importError: "导入失败",
saveSettings: "保存设置",
settingsSaved: "设置已保存",
ollamaSaved: "Ollama设置已保存",
+61
View File
@@ -606,6 +606,20 @@ export async function initSettingsTab() {
</div>
</details>
<!-- Data Management -->
<details class="wfm-settings-section">
<summary class="wfm-settings-summary">${t("dataManagement")}</summary>
<p style="font-size:12px;color:var(--wfm-text-secondary);margin-bottom:12px;">${t("dataManagementHint")}</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button class="wfm-btn wfm-btn-sm" id="wfm-settings-export">${t("exportData")}</button>
<label class="wfm-btn wfm-btn-sm" style="cursor:pointer;margin:0;">
${t("importData")}
<input type="file" id="wfm-settings-import-file" accept=".json" style="display:none;">
</label>
</div>
<div id="wfm-settings-data-status" style="font-size:12px;margin-top:8px;"></div>
</details>
<!-- Save Button -->
<button class="wfm-btn wfm-btn-primary" id="wfm-settings-save" style="min-width:120px;">${t("saveSettings")}</button>
@@ -1036,4 +1050,51 @@ export async function initSettingsTab() {
if (savedUrl) {
comfyUI.updateUrl(savedUrl);
}
// --- Data Export ---
document.getElementById("wfm-settings-export")?.addEventListener("click", async () => {
const statusEl = document.getElementById("wfm-settings-data-status");
try {
const res = await fetch("/api/wfm/settings/export");
if (!res.ok) throw new Error(await res.text());
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "wfm-data-export.json";
a.click();
URL.revokeObjectURL(url);
statusEl.textContent = t("exportSuccess");
statusEl.style.color = "var(--wfm-success, #4caf50)";
} catch (e) {
statusEl.textContent = t("exportError") + ": " + e.message;
statusEl.style.color = "var(--wfm-error, #f44336)";
}
});
// --- Data Import ---
document.getElementById("wfm-settings-import-file")?.addEventListener("change", async (e) => {
const statusEl = document.getElementById("wfm-settings-data-status");
const file = e.target.files?.[0];
if (!file) return;
try {
const text = await file.text();
const bundle = JSON.parse(text);
const res = await fetch("/api/wfm/settings/import", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(bundle),
});
const result = await res.json();
if (!res.ok) throw new Error(result.error || res.statusText);
const imported = result.imported?.join(", ") || "-";
statusEl.textContent = t("importSuccess") + ": " + imported;
statusEl.style.color = "var(--wfm-success, #4caf50)";
} catch (e) {
statusEl.textContent = t("importError") + ": " + e.message;
statusEl.style.color = "var(--wfm-error, #f44336)";
}
// Reset file input so same file can be re-selected
e.target.value = "";
});
}
+2 -1
View File
@@ -530,7 +530,8 @@
<li id="wfm-help-settings-5">Ollama URL, default model, and connection test</li>
<li id="wfm-help-settings-6">Default workflow setting</li>
<li id="wfm-help-settings-7">Eagle integration URL and auto-save toggle</li>
<li id="wfm-help-settings-8">All sections are collapsible accordion panels</li>
<li id="wfm-help-settings-8">Data Management: export all plugin data to JSON / import to restore</li>
<li id="wfm-help-settings-9">All sections are collapsible accordion panels</li>
</ul>
</div>