Files
Will Miao f473678004 feat(workflows): restore a workflow to its downloaded state
New card context menu entry 'Restore Downloaded Version' discards local
edits made via the write-back flow:

- Container artifacts (.zip/images) were never modified — restore deletes
  the derived .workflow.json sidecar and re-extracts from the pristine
  archive, then re-syncs analysis metadata. Purely local, works for
  self-hosted workflows too.
- Bare .json artifacts are rewritten in place by write-backs, so restore
  re-downloads the recorded CivitAI version file (API-key auth, temp file
  + workflow validation + SHA256 check before the atomic replace).

The menu entry is disabled for local-only .json workflows that have no
restore source. On success the card is refreshed in place with the
shared 'Updated' cue.
2026-10-29 12:12:52 +08:00

94 lines
3.7 KiB
JavaScript

import { BaseModelApiClient } from './baseModelApi.js';
/**
* Workflow-specific API client (ComfyUI workflow JSON resources)
*/
export class WorkflowApiClient extends BaseModelApiClient {
/**
* Push a library workflow into the running ComfyUI graph.
* POST /api/lm/workflows/send-to-comfy { file_path }
*/
async sendToComfy(filePath) {
const response = await fetch(this.apiConfig.endpoints.specific.sendToComfy, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_path: filePath }),
});
const data = await response.json().catch(() => ({}));
if (!response.ok || data.success === false) {
throw new Error(data.error || data.message || `HTTP ${response.status}`);
}
return data;
}
/**
* Fetch the extracted workflow document for a library entry.
* GET /api/lm/workflows/content?file_path=... (works in standalone mode).
* @returns {Promise<object>} The workflow JSON document.
*/
async fetchWorkflowContent(filePath) {
const url = `${this.apiConfig.endpoints.specific.content}?file_path=${encodeURIComponent(filePath)}`;
const response = await fetch(url);
const data = await response.json().catch(() => ({}));
if (!response.ok || data.success === false) {
throw new Error(data.error || data.message || `HTTP ${response.status}`);
}
return data.workflow;
}
/**
* List LM-managed workflows currently open in live ComfyUI tabs.
* GET /api/lm/workflows/comfyui-state
* @returns {Promise<Array>} [{client_id, file_path, sha256, dirty}, ...]
*/
async fetchComfyuiState() {
const response = await fetch(this.apiConfig.endpoints.specific.comfyuiState);
const data = await response.json().catch(() => ({}));
if (!response.ok || data.success === false) {
throw new Error(data.error || data.message || `HTTP ${response.status}`);
}
return data.states || [];
}
/**
* Ask the ComfyUI tab holding this workflow to write its graph back.
* POST /api/lm/workflows/update-from-comfy { file_path }
* The request resolves with the write-back outcome; `error` carries the
* failure kind ('not_open', 'no_changes', 'cancelled', 'timeout', ...).
*/
async updateFromComfy(filePath) {
const response = await fetch(this.apiConfig.endpoints.specific.updateFromComfy, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_path: filePath }),
});
const data = await response.json().catch(() => ({}));
if (!response.ok || data.success === false) {
const error = new Error(data.message || data.error || `HTTP ${response.status}`);
error.kind = data.error || 'unknown';
throw error;
}
return data;
}
/**
* Restore a workflow to its downloaded state, discarding local edits.
* POST /api/lm/workflows/restore { file_path }
* `error` carries the failure kind ('no_source', 'download_failed', ...).
*/
async restore(filePath) {
const response = await fetch(this.apiConfig.endpoints.specific.restore, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_path: filePath }),
});
const data = await response.json().catch(() => ({}));
if (!response.ok || data.success === false) {
const error = new Error(data.message || data.error || `HTTP ${response.status}`);
error.kind = data.error || 'unknown';
throw error;
}
return data;
}
}