Files
DemonGatanjieuandClaude Opus 5.5 c890627961 Hand the canvas or a node to an AI from the right-click menu
"Hand to AI..." in a node's and the empty canvas's menu opens ready wishes (run 4
and pick, find what is wrong, explain each step, add an enlargement and face and
hand fixes; explain or tune a node) and the line to change. It goes to the task an
AI is on as a message, or starts a task about the canvas: the task's reads, runs
and copy start from that tab even with another in front. A node's saved pictures
can go along, and a failed run offers "Ask the AI" with ComfyUI's words. The AI's
job sheet now follows the task's tab like a run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:09:27 +08:00

77 lines
4.0 KiB
JavaScript

/**
* The task's own canvas tab (api/ai_task_canvas.py), on the page: before an AI's canvas action
* (mcp_bridge.js), with the task set to "new", the task's tab is brought to the front the way a
* click on it does (ComfyUI's store alone would not load its graph); with no tab yet, the first
* change copies the open workflow into a new tab named after the task and says so in a toast,
* so the user's own tab is never changed. Reads and runs follow the task's tab when it is open
* and use the canvas as it is before it has one; new_workflow and open_workflow make the tab
* they open the task's. An existing task tab that cannot be opened stops the action. A canvas the user handed to the
* task (`spec.from`, ui_ai_canvas_hand.js) comes to the front first while the task has no tab: it is the one read,
* run and copied; closed since, the canvas open now is.
*/
import { app } from '../../../scripts/app.js';
import { translate as t } from './locales.js';
import { showWorkbenchToast } from './ui_prompt_toast.js';
import { tabName } from './mcp_canvas_edit.js';
const CHANGES = new Set(['set_prompt', 'set_model', 'add_lora', 'place_combo', 'fix_models', 'add_node', 'add_nodes', 'connect_nodes', 'set_values', 'remove_nodes']);
const FOLLOWS = new Set(['describe_canvas', 'check_models', 'run_workflow', 'download_missing', 'fill_slots', 'job_sheet']);
const OPENS = new Set(['new_workflow', 'open_workflow']);
const SWITCH_WAIT_MS = 4000;
const store = () => app.extensionManager?.workflow || null;
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const shown = path => store()?.activeWorkflow?.path === path && !store()?.activeWorkflow?.isLoading;
function frontTab(made = false) {
const workflow = store()?.activeWorkflow;
return workflow ? { path: workflow.path, name: String(workflow.filename || workflow.path).replace(/\.json$/i, ''), made } : null;
}
async function settled(path, limit = SWITCH_WAIT_MS) {
for (let waited = 0; waited < limit && !shown(path); waited += 50) await wait(50);
if (shown(path)) await new Promise(resolve => requestAnimationFrame(resolve));
return shown(path);
}
/** Brings open tab `path` to the front as a click on it does; false when it is not open. */
export async function showTab(path) {
const workflows = store()?.openWorkflows || [];
const index = workflows.findIndex(workflow => workflow.path === path);
if (index < 0) return false;
if (shown(path)) return true;
document.querySelectorAll('.workflow-tabs > button')[index]?.click();
if (await settled(path)) return true;
// Tabs not on screen (another layout): step through the open ones, as Ctrl+Tab does.
for (let step = 0; step < workflows.length && !shown(path); step++) {
await app.extensionManager?.command?.execute?.('Workspace.NextOpenedWorkflow');
await settled(path, 600);
}
return shown(path);
}
/** Runs page action `action` (`run()`) on the task's tab per `spec` ({mode, tab, title, from}); `opened(tab)`
* hears of a tab made or opened for the task ({path, name, made}). Returns what `run()` does. */
export async function onTaskTab(action, spec, run, opened) {
if (spec?.mode !== 'new' || !(CHANGES.has(action) || FOLLOWS.has(action) || OPENS.has(action))) return run();
if (OPENS.has(action)) {
const value = await run();
opened(frontTab());
return value;
}
const path = spec.tab?.path;
if (path) {
if (!await showTab(path)) throw new Error('The task canvas tab is closed or could not be activated. '
+ 'Reopen that workflow, or explicitly use open_workflow/new_workflow for this task before retrying. Nothing was run or changed.');
return run();
}
if (spec.from) await showTab(spec.from);
if (!CHANGES.has(action)) return run();
const name = tabName(spec.title || t('aiTaskTabDefault'));
await app.loadGraphData(app.graph.serialize(), true, true, name);
opened(frontTab(true));
showWorkbenchToast(t('aiTaskTabMade', { name }));
return run();
}