flowscale 2.1.0-beta.0 → 2.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/dist/local-pods.d.ts +67 -0
- package/dist/local-pods.js +319 -0
- package/dist/local-types.d.ts +143 -0
- package/dist/local-types.js +5 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -73,7 +73,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
73
73
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
74
74
|
};
|
|
75
75
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76
|
-
exports.FlowscalePlatformAPI = exports.FlowscaleAPI = void 0;
|
|
76
|
+
exports.LocalPodsAPI = exports.FlowscalePlatformAPI = exports.FlowscaleAPI = void 0;
|
|
77
77
|
var axios_1 = __importDefault(require("axios"));
|
|
78
78
|
var FlowscaleAPI = /** @class */ (function () {
|
|
79
79
|
function FlowscaleAPI(config) {
|
|
@@ -1297,4 +1297,8 @@ __exportStar(require("./types"), exports);
|
|
|
1297
1297
|
// Platform API — JWT-based control of the full Flowscale platform
|
|
1298
1298
|
var platform_1 = require("./platform");
|
|
1299
1299
|
Object.defineProperty(exports, "FlowscalePlatformAPI", { enumerable: true, get: function () { return platform_1.FlowscalePlatformAPI; } });
|
|
1300
|
+
// Local Pods API — for managing local ComfyUI instances in Electron
|
|
1301
|
+
var local_pods_1 = require("./local-pods");
|
|
1302
|
+
Object.defineProperty(exports, "LocalPodsAPI", { enumerable: true, get: function () { return local_pods_1.LocalPodsAPI; } });
|
|
1303
|
+
__exportStar(require("./local-types"), exports);
|
|
1300
1304
|
__exportStar(require("./platform-types"), exports);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { LocalPodsTransport, LocalPod, LocalPodInstance, LocalPodInstanceStatus, SystemInfo, ComfyUISystemStats, ComfyUIWorkflow, ComfyUIWorkflowResult, PodWorkflowHandle, ExecuteWorkflowOptions, CreateLocalPodRequest, UpdateLocalPodRequest, StartInstanceRequest } from './local-types';
|
|
2
|
+
/**
|
|
3
|
+
* LocalPodsAPI — manages local ComfyUI pods via a pluggable transport.
|
|
4
|
+
*
|
|
5
|
+
* In the Electron app, pass an ElectronPodsTransport that delegates to IPC.
|
|
6
|
+
* This class contains no logic; it is a typed facade over the transport.
|
|
7
|
+
*/
|
|
8
|
+
export declare class LocalPodsAPI {
|
|
9
|
+
private transport;
|
|
10
|
+
constructor(transport: LocalPodsTransport);
|
|
11
|
+
create(data: CreateLocalPodRequest): Promise<LocalPod>;
|
|
12
|
+
list(): Promise<LocalPod[]>;
|
|
13
|
+
get(id: string): Promise<LocalPod | null>;
|
|
14
|
+
update(id: string, data: UpdateLocalPodRequest): Promise<LocalPod>;
|
|
15
|
+
delete(id: string): Promise<void>;
|
|
16
|
+
/** Scan common OS directories for a ComfyUI installation path. */
|
|
17
|
+
scanPaths(): Promise<string | null>;
|
|
18
|
+
/** Probe ports 8188–9188 and associate any found ComfyUI instances with podId. */
|
|
19
|
+
scanPorts(podId: string): Promise<LocalPodInstance[]>;
|
|
20
|
+
/** Re-probe a single instance port and return the updated instance. */
|
|
21
|
+
refreshInstance(instanceId: string): Promise<LocalPodInstance>;
|
|
22
|
+
startInstance(req: StartInstanceRequest): Promise<LocalPodInstance>;
|
|
23
|
+
stopInstance(instanceId: string): Promise<void>;
|
|
24
|
+
restartInstance(instanceId: string): Promise<void>;
|
|
25
|
+
deleteInstance(instanceId: string): Promise<void>;
|
|
26
|
+
getSystemInfo(): Promise<SystemInfo>;
|
|
27
|
+
getLogs(instanceId: string): Promise<string[]>;
|
|
28
|
+
onInstanceStatusChanged(cb: (instanceId: string, status: LocalPodInstanceStatus, info?: ComfyUISystemStats) => void): () => void;
|
|
29
|
+
onLog(cb: (instanceId: string, line: string) => void): () => void;
|
|
30
|
+
installRocmPyTorch(podId: string): void;
|
|
31
|
+
onRocmSetupDone(cb: (data: {
|
|
32
|
+
podId: string;
|
|
33
|
+
success: boolean;
|
|
34
|
+
}) => void): () => void;
|
|
35
|
+
/**
|
|
36
|
+
* Execute a workflow on a pod. The SDK automatically picks the
|
|
37
|
+
* least-loaded running ComfyUI instance inside the pod.
|
|
38
|
+
*
|
|
39
|
+
* Returns a handle with a `wait()` method to get the outputs.
|
|
40
|
+
* Use `executeWorkflowAndWait` if you want a single awaitable call.
|
|
41
|
+
*
|
|
42
|
+
* @param podId ID of the pod to run the workflow on
|
|
43
|
+
* @param workflow ComfyUI workflow graph (API format)
|
|
44
|
+
* @param options Optional clientId and extraData
|
|
45
|
+
*/
|
|
46
|
+
executeWorkflow(podId: string, workflow: ComfyUIWorkflow | Record<string, unknown>, options?: ExecuteWorkflowOptions): Promise<PodWorkflowHandle>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a workflow on a pod and wait for it to complete.
|
|
49
|
+
* The SDK automatically picks the least-loaded running instance.
|
|
50
|
+
*
|
|
51
|
+
* @param podId ID of the pod to run the workflow on
|
|
52
|
+
* @param workflow ComfyUI workflow graph (API format)
|
|
53
|
+
* @param options Optional clientId and extraData
|
|
54
|
+
* @param timeoutMs Maximum wait time in ms (default 5 minutes)
|
|
55
|
+
*/
|
|
56
|
+
executeWorkflowAndWait(podId: string, workflow: ComfyUIWorkflow | Record<string, unknown>, options?: ExecuteWorkflowOptions, timeoutMs?: number): Promise<ComfyUIWorkflowResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Find the port of the least-loaded running instance in a pod.
|
|
59
|
+
* Checks ComfyUI's /queue endpoint on all running instances in parallel
|
|
60
|
+
* and picks the one with the fewest queued jobs.
|
|
61
|
+
*/
|
|
62
|
+
private _findFreeInstancePort;
|
|
63
|
+
/** Returns total number of jobs (running + pending) on a ComfyUI instance. */
|
|
64
|
+
private _getQueueDepth;
|
|
65
|
+
/** Poll /history until the prompt completes, then return the result. */
|
|
66
|
+
private _pollHistory;
|
|
67
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.LocalPodsAPI = void 0;
|
|
40
|
+
/**
|
|
41
|
+
* LocalPodsAPI — manages local ComfyUI pods via a pluggable transport.
|
|
42
|
+
*
|
|
43
|
+
* In the Electron app, pass an ElectronPodsTransport that delegates to IPC.
|
|
44
|
+
* This class contains no logic; it is a typed facade over the transport.
|
|
45
|
+
*/
|
|
46
|
+
var LocalPodsAPI = /** @class */ (function () {
|
|
47
|
+
function LocalPodsAPI(transport) {
|
|
48
|
+
this.transport = transport;
|
|
49
|
+
}
|
|
50
|
+
// ── Pod CRUD ──────────────────────────────────────────────
|
|
51
|
+
LocalPodsAPI.prototype.create = function (data) {
|
|
52
|
+
return this.transport.create(data);
|
|
53
|
+
};
|
|
54
|
+
LocalPodsAPI.prototype.list = function () {
|
|
55
|
+
return this.transport.list();
|
|
56
|
+
};
|
|
57
|
+
LocalPodsAPI.prototype.get = function (id) {
|
|
58
|
+
return this.transport.get(id);
|
|
59
|
+
};
|
|
60
|
+
LocalPodsAPI.prototype.update = function (id, data) {
|
|
61
|
+
return this.transport.update(id, data);
|
|
62
|
+
};
|
|
63
|
+
LocalPodsAPI.prototype.delete = function (id) {
|
|
64
|
+
return this.transport.delete(id);
|
|
65
|
+
};
|
|
66
|
+
// ── Detection & scanning ──────────────────────────────────
|
|
67
|
+
/** Scan common OS directories for a ComfyUI installation path. */
|
|
68
|
+
LocalPodsAPI.prototype.scanPaths = function () {
|
|
69
|
+
return this.transport.scanPaths();
|
|
70
|
+
};
|
|
71
|
+
/** Probe ports 8188–9188 and associate any found ComfyUI instances with podId. */
|
|
72
|
+
LocalPodsAPI.prototype.scanPorts = function (podId) {
|
|
73
|
+
return this.transport.scanPorts(podId);
|
|
74
|
+
};
|
|
75
|
+
/** Re-probe a single instance port and return the updated instance. */
|
|
76
|
+
LocalPodsAPI.prototype.refreshInstance = function (instanceId) {
|
|
77
|
+
return this.transport.refreshInstance(instanceId);
|
|
78
|
+
};
|
|
79
|
+
// ── Instance lifecycle ────────────────────────────────────
|
|
80
|
+
LocalPodsAPI.prototype.startInstance = function (req) {
|
|
81
|
+
return this.transport.startInstance(req);
|
|
82
|
+
};
|
|
83
|
+
LocalPodsAPI.prototype.stopInstance = function (instanceId) {
|
|
84
|
+
return this.transport.stopInstance(instanceId);
|
|
85
|
+
};
|
|
86
|
+
LocalPodsAPI.prototype.restartInstance = function (instanceId) {
|
|
87
|
+
return this.transport.restartInstance(instanceId);
|
|
88
|
+
};
|
|
89
|
+
LocalPodsAPI.prototype.deleteInstance = function (instanceId) {
|
|
90
|
+
return this.transport.deleteInstance(instanceId);
|
|
91
|
+
};
|
|
92
|
+
// ── System info ───────────────────────────────────────────
|
|
93
|
+
LocalPodsAPI.prototype.getSystemInfo = function () {
|
|
94
|
+
return this.transport.getSystemInfo();
|
|
95
|
+
};
|
|
96
|
+
// ── Logs ──────────────────────────────────────────────────
|
|
97
|
+
LocalPodsAPI.prototype.getLogs = function (instanceId) {
|
|
98
|
+
return this.transport.getLogs(instanceId);
|
|
99
|
+
};
|
|
100
|
+
// ── Events ────────────────────────────────────────────────
|
|
101
|
+
LocalPodsAPI.prototype.onInstanceStatusChanged = function (cb) {
|
|
102
|
+
return this.transport.onInstanceStatusChanged(cb);
|
|
103
|
+
};
|
|
104
|
+
LocalPodsAPI.prototype.onLog = function (cb) {
|
|
105
|
+
return this.transport.onLog(cb);
|
|
106
|
+
};
|
|
107
|
+
// ── AMD GPU setup ─────────────────────────────────────────
|
|
108
|
+
LocalPodsAPI.prototype.installRocmPyTorch = function (podId) {
|
|
109
|
+
return this.transport.installRocmPyTorch(podId);
|
|
110
|
+
};
|
|
111
|
+
LocalPodsAPI.prototype.onRocmSetupDone = function (cb) {
|
|
112
|
+
return this.transport.onRocmSetupDone(cb);
|
|
113
|
+
};
|
|
114
|
+
// ── Workflow execution ────────────────────────────────────
|
|
115
|
+
// External apps only need to know about pods — the SDK handles
|
|
116
|
+
// routing to a free ComfyUI instance internally.
|
|
117
|
+
/**
|
|
118
|
+
* Execute a workflow on a pod. The SDK automatically picks the
|
|
119
|
+
* least-loaded running ComfyUI instance inside the pod.
|
|
120
|
+
*
|
|
121
|
+
* Returns a handle with a `wait()` method to get the outputs.
|
|
122
|
+
* Use `executeWorkflowAndWait` if you want a single awaitable call.
|
|
123
|
+
*
|
|
124
|
+
* @param podId ID of the pod to run the workflow on
|
|
125
|
+
* @param workflow ComfyUI workflow graph (API format)
|
|
126
|
+
* @param options Optional clientId and extraData
|
|
127
|
+
*/
|
|
128
|
+
LocalPodsAPI.prototype.executeWorkflow = function (podId_1, workflow_1) {
|
|
129
|
+
return __awaiter(this, arguments, void 0, function (podId, workflow, options) {
|
|
130
|
+
var pod, port, clientId, body, res, text, prompt_id;
|
|
131
|
+
var _this = this;
|
|
132
|
+
var _a;
|
|
133
|
+
if (options === void 0) { options = {}; }
|
|
134
|
+
return __generator(this, function (_b) {
|
|
135
|
+
switch (_b.label) {
|
|
136
|
+
case 0: return [4 /*yield*/, this.transport.get(podId)];
|
|
137
|
+
case 1:
|
|
138
|
+
pod = _b.sent();
|
|
139
|
+
if (!pod)
|
|
140
|
+
throw new Error("Pod \"".concat(podId, "\" not found"));
|
|
141
|
+
return [4 /*yield*/, this._findFreeInstancePort(pod)];
|
|
142
|
+
case 2:
|
|
143
|
+
port = _b.sent();
|
|
144
|
+
clientId = (_a = options.clientId) !== null && _a !== void 0 ? _a : crypto.randomUUID();
|
|
145
|
+
body = { prompt: workflow, client_id: clientId };
|
|
146
|
+
if (options.extraData)
|
|
147
|
+
body.extra_data = options.extraData;
|
|
148
|
+
return [4 /*yield*/, fetch("http://localhost:".concat(port, "/prompt"), {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: { 'Content-Type': 'application/json' },
|
|
151
|
+
body: JSON.stringify(body)
|
|
152
|
+
})];
|
|
153
|
+
case 3:
|
|
154
|
+
res = _b.sent();
|
|
155
|
+
if (!!res.ok) return [3 /*break*/, 5];
|
|
156
|
+
return [4 /*yield*/, res.text().catch(function () { return res.statusText; })];
|
|
157
|
+
case 4:
|
|
158
|
+
text = _b.sent();
|
|
159
|
+
throw new Error("ComfyUI /prompt failed (".concat(res.status, "): ").concat(text));
|
|
160
|
+
case 5: return [4 /*yield*/, res.json()];
|
|
161
|
+
case 6:
|
|
162
|
+
prompt_id = (_b.sent()).prompt_id;
|
|
163
|
+
return [2 /*return*/, {
|
|
164
|
+
promptId: prompt_id,
|
|
165
|
+
podId: podId,
|
|
166
|
+
wait: function (timeoutMs) { return _this._pollHistory(port, prompt_id, podId, timeoutMs); }
|
|
167
|
+
}];
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Execute a workflow on a pod and wait for it to complete.
|
|
174
|
+
* The SDK automatically picks the least-loaded running instance.
|
|
175
|
+
*
|
|
176
|
+
* @param podId ID of the pod to run the workflow on
|
|
177
|
+
* @param workflow ComfyUI workflow graph (API format)
|
|
178
|
+
* @param options Optional clientId and extraData
|
|
179
|
+
* @param timeoutMs Maximum wait time in ms (default 5 minutes)
|
|
180
|
+
*/
|
|
181
|
+
LocalPodsAPI.prototype.executeWorkflowAndWait = function (podId_1, workflow_1) {
|
|
182
|
+
return __awaiter(this, arguments, void 0, function (podId, workflow, options, timeoutMs) {
|
|
183
|
+
var handle;
|
|
184
|
+
if (options === void 0) { options = {}; }
|
|
185
|
+
if (timeoutMs === void 0) { timeoutMs = 300000; }
|
|
186
|
+
return __generator(this, function (_a) {
|
|
187
|
+
switch (_a.label) {
|
|
188
|
+
case 0: return [4 /*yield*/, this.executeWorkflow(podId, workflow, options)];
|
|
189
|
+
case 1:
|
|
190
|
+
handle = _a.sent();
|
|
191
|
+
return [2 /*return*/, handle.wait(timeoutMs)];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
// ── Private helpers ───────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Find the port of the least-loaded running instance in a pod.
|
|
199
|
+
* Checks ComfyUI's /queue endpoint on all running instances in parallel
|
|
200
|
+
* and picks the one with the fewest queued jobs.
|
|
201
|
+
*/
|
|
202
|
+
LocalPodsAPI.prototype._findFreeInstancePort = function (pod) {
|
|
203
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
204
|
+
var running, withLoad;
|
|
205
|
+
var _this = this;
|
|
206
|
+
return __generator(this, function (_a) {
|
|
207
|
+
switch (_a.label) {
|
|
208
|
+
case 0:
|
|
209
|
+
running = pod.instances.filter(function (i) { return i.status === 'running'; });
|
|
210
|
+
if (running.length === 0) {
|
|
211
|
+
throw new Error("No running ComfyUI instances in pod \"".concat(pod.name, "\". Start an instance first."));
|
|
212
|
+
}
|
|
213
|
+
return [4 /*yield*/, Promise.all(running.map(function (inst) { return __awaiter(_this, void 0, void 0, function () {
|
|
214
|
+
var load;
|
|
215
|
+
return __generator(this, function (_a) {
|
|
216
|
+
switch (_a.label) {
|
|
217
|
+
case 0: return [4 /*yield*/, this._getQueueDepth(inst.port)];
|
|
218
|
+
case 1:
|
|
219
|
+
load = _a.sent();
|
|
220
|
+
return [2 /*return*/, { port: inst.port, load: load }];
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}); }))
|
|
224
|
+
// Sort by total load (running + pending jobs) and pick the least busy
|
|
225
|
+
];
|
|
226
|
+
case 1:
|
|
227
|
+
withLoad = _a.sent();
|
|
228
|
+
// Sort by total load (running + pending jobs) and pick the least busy
|
|
229
|
+
withLoad.sort(function (a, b) { return a.load - b.load; });
|
|
230
|
+
return [2 /*return*/, withLoad[0].port];
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
/** Returns total number of jobs (running + pending) on a ComfyUI instance. */
|
|
236
|
+
LocalPodsAPI.prototype._getQueueDepth = function (port) {
|
|
237
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
238
|
+
var res, data, _a;
|
|
239
|
+
return __generator(this, function (_b) {
|
|
240
|
+
switch (_b.label) {
|
|
241
|
+
case 0:
|
|
242
|
+
_b.trys.push([0, 3, , 4]);
|
|
243
|
+
return [4 /*yield*/, fetch("http://localhost:".concat(port, "/queue"), {
|
|
244
|
+
signal: AbortSignal.timeout(2000)
|
|
245
|
+
})];
|
|
246
|
+
case 1:
|
|
247
|
+
res = _b.sent();
|
|
248
|
+
if (!res.ok)
|
|
249
|
+
return [2 /*return*/, Infinity];
|
|
250
|
+
return [4 /*yield*/, res.json()];
|
|
251
|
+
case 2:
|
|
252
|
+
data = _b.sent();
|
|
253
|
+
return [2 /*return*/, data.queue_running.length + data.queue_pending.length];
|
|
254
|
+
case 3:
|
|
255
|
+
_a = _b.sent();
|
|
256
|
+
return [2 /*return*/, Infinity]; // treat unreachable instances as fully loaded
|
|
257
|
+
case 4: return [2 /*return*/];
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
/** Poll /history until the prompt completes, then return the result. */
|
|
263
|
+
LocalPodsAPI.prototype._pollHistory = function (port_1, promptId_1, podId_1) {
|
|
264
|
+
return __awaiter(this, arguments, void 0, function (port, promptId, podId, timeoutMs, pollMs) {
|
|
265
|
+
var start, res, history_1, entry, err_1;
|
|
266
|
+
if (timeoutMs === void 0) { timeoutMs = 300000; }
|
|
267
|
+
if (pollMs === void 0) { pollMs = 1000; }
|
|
268
|
+
return __generator(this, function (_a) {
|
|
269
|
+
switch (_a.label) {
|
|
270
|
+
case 0:
|
|
271
|
+
start = Date.now();
|
|
272
|
+
_a.label = 1;
|
|
273
|
+
case 1:
|
|
274
|
+
if (!(Date.now() - start < timeoutMs)) return [3 /*break*/, 8];
|
|
275
|
+
return [4 /*yield*/, new Promise(function (r) { return setTimeout(r, pollMs); })];
|
|
276
|
+
case 2:
|
|
277
|
+
_a.sent();
|
|
278
|
+
_a.label = 3;
|
|
279
|
+
case 3:
|
|
280
|
+
_a.trys.push([3, 6, , 7]);
|
|
281
|
+
return [4 /*yield*/, fetch("http://localhost:".concat(port, "/history/").concat(promptId), {
|
|
282
|
+
signal: AbortSignal.timeout(5000)
|
|
283
|
+
})];
|
|
284
|
+
case 4:
|
|
285
|
+
res = _a.sent();
|
|
286
|
+
if (!res.ok)
|
|
287
|
+
return [3 /*break*/, 1];
|
|
288
|
+
return [4 /*yield*/, res.json()];
|
|
289
|
+
case 5:
|
|
290
|
+
history_1 = _a.sent();
|
|
291
|
+
entry = history_1[promptId];
|
|
292
|
+
if (!entry)
|
|
293
|
+
return [3 /*break*/, 1];
|
|
294
|
+
if (entry.status.status_str === 'error') {
|
|
295
|
+
throw new Error("Workflow \"".concat(promptId, "\" failed on pod \"").concat(podId, "\""));
|
|
296
|
+
}
|
|
297
|
+
if (entry.status.completed) {
|
|
298
|
+
return [2 /*return*/, {
|
|
299
|
+
promptId: promptId,
|
|
300
|
+
podId: podId,
|
|
301
|
+
outputs: entry.outputs,
|
|
302
|
+
elapsedMs: Date.now() - start
|
|
303
|
+
}];
|
|
304
|
+
}
|
|
305
|
+
return [3 /*break*/, 7];
|
|
306
|
+
case 6:
|
|
307
|
+
err_1 = _a.sent();
|
|
308
|
+
if (err_1 instanceof Error && err_1.message.startsWith('Workflow'))
|
|
309
|
+
throw err_1;
|
|
310
|
+
return [3 /*break*/, 7];
|
|
311
|
+
case 7: return [3 /*break*/, 1];
|
|
312
|
+
case 8: throw new Error("Workflow \"".concat(promptId, "\" timed out after ").concat(timeoutMs, "ms"));
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
return LocalPodsAPI;
|
|
318
|
+
}());
|
|
319
|
+
exports.LocalPodsAPI = LocalPodsAPI;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export type LocalPodDevice = {
|
|
2
|
+
type: 'cpu';
|
|
3
|
+
} | {
|
|
4
|
+
type: 'gpu';
|
|
5
|
+
index: number;
|
|
6
|
+
name: string;
|
|
7
|
+
vram?: number;
|
|
8
|
+
vendor?: string;
|
|
9
|
+
};
|
|
10
|
+
export type LocalPodInstanceStatus = 'running' | 'idle';
|
|
11
|
+
export interface ComfyUIDevice {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
index: number;
|
|
15
|
+
vram_total: number;
|
|
16
|
+
vram_free: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ComfyUISystemStats {
|
|
19
|
+
system: {
|
|
20
|
+
os: string;
|
|
21
|
+
python_version: string;
|
|
22
|
+
};
|
|
23
|
+
devices: ComfyUIDevice[];
|
|
24
|
+
}
|
|
25
|
+
export interface LocalPodInstance {
|
|
26
|
+
id: string;
|
|
27
|
+
podId: string;
|
|
28
|
+
port: number;
|
|
29
|
+
device: LocalPodDevice;
|
|
30
|
+
status: LocalPodInstanceStatus;
|
|
31
|
+
/** null = externally running (not spawned by app) */
|
|
32
|
+
pid: number | null;
|
|
33
|
+
lastDetectedAt: number | null;
|
|
34
|
+
comfyuiInfo?: ComfyUISystemStats;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
}
|
|
37
|
+
export interface LocalPod {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
/** null if not found/provided */
|
|
42
|
+
comfyuiPath: string | null;
|
|
43
|
+
instances: LocalPodInstance[];
|
|
44
|
+
createdAt: number;
|
|
45
|
+
updatedAt: number;
|
|
46
|
+
}
|
|
47
|
+
export interface GpuInfo {
|
|
48
|
+
index: number;
|
|
49
|
+
name: string;
|
|
50
|
+
vram?: number;
|
|
51
|
+
vendor?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface CpuInfo {
|
|
54
|
+
model: string;
|
|
55
|
+
cores: number;
|
|
56
|
+
threads: number;
|
|
57
|
+
}
|
|
58
|
+
export interface SystemInfo {
|
|
59
|
+
gpus: GpuInfo[];
|
|
60
|
+
cpu: CpuInfo;
|
|
61
|
+
}
|
|
62
|
+
export interface CreateLocalPodRequest {
|
|
63
|
+
name: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
comfyuiPath?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface UpdateLocalPodRequest {
|
|
68
|
+
name?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
comfyuiPath?: string | null;
|
|
71
|
+
}
|
|
72
|
+
export interface StartInstanceRequest {
|
|
73
|
+
podId: string;
|
|
74
|
+
/** The device (CPU or specific GPU) to start ComfyUI on */
|
|
75
|
+
device: LocalPodDevice;
|
|
76
|
+
}
|
|
77
|
+
/** A ComfyUI workflow graph — map of node ID to node definition */
|
|
78
|
+
export type ComfyUIWorkflow = Record<string, {
|
|
79
|
+
class_type: string;
|
|
80
|
+
inputs: Record<string, unknown>;
|
|
81
|
+
_meta?: Record<string, unknown>;
|
|
82
|
+
}>;
|
|
83
|
+
export interface ExecuteWorkflowOptions {
|
|
84
|
+
/** ComfyUI client ID for WebSocket tracking. Auto-generated if omitted. */
|
|
85
|
+
clientId?: string;
|
|
86
|
+
/** Extra data passed to ComfyUI alongside the prompt */
|
|
87
|
+
extraData?: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
export interface ComfyUIOutputFile {
|
|
90
|
+
filename: string;
|
|
91
|
+
subfolder: string;
|
|
92
|
+
type: 'output' | 'temp' | 'input';
|
|
93
|
+
}
|
|
94
|
+
export interface ComfyUINodeOutput {
|
|
95
|
+
images?: ComfyUIOutputFile[];
|
|
96
|
+
gifs?: ComfyUIOutputFile[];
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
export interface ComfyUIWorkflowResult {
|
|
100
|
+
promptId: string;
|
|
101
|
+
podId: string;
|
|
102
|
+
/** Map of node ID → its outputs (images, etc.) */
|
|
103
|
+
outputs: Record<string, ComfyUINodeOutput>;
|
|
104
|
+
/** Total elapsed time in ms */
|
|
105
|
+
elapsedMs: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Handle returned by `executeWorkflow`. Lets you track or await the result
|
|
109
|
+
* without caring which instance inside the pod is running it.
|
|
110
|
+
*/
|
|
111
|
+
export interface PodWorkflowHandle {
|
|
112
|
+
/** ComfyUI prompt ID */
|
|
113
|
+
promptId: string;
|
|
114
|
+
podId: string;
|
|
115
|
+
/** Wait for the workflow to complete and return its outputs */
|
|
116
|
+
wait(timeoutMs?: number): Promise<ComfyUIWorkflowResult>;
|
|
117
|
+
}
|
|
118
|
+
export interface LocalPodsTransport {
|
|
119
|
+
create(data: CreateLocalPodRequest): Promise<LocalPod>;
|
|
120
|
+
list(): Promise<LocalPod[]>;
|
|
121
|
+
get(id: string): Promise<LocalPod | null>;
|
|
122
|
+
update(id: string, data: UpdateLocalPodRequest): Promise<LocalPod>;
|
|
123
|
+
delete(id: string): Promise<void>;
|
|
124
|
+
/** Scan common OS directories for a ComfyUI installation. Returns path or null. */
|
|
125
|
+
scanPaths(): Promise<string | null>;
|
|
126
|
+
/** Scan ports 8188–9188 in parallel; associate discovered instances with podId. */
|
|
127
|
+
scanPorts(podId: string): Promise<LocalPodInstance[]>;
|
|
128
|
+
/** Re-probe a single instance's port and update its status. */
|
|
129
|
+
refreshInstance(instanceId: string): Promise<LocalPodInstance>;
|
|
130
|
+
startInstance(req: StartInstanceRequest): Promise<LocalPodInstance>;
|
|
131
|
+
stopInstance(instanceId: string): Promise<void>;
|
|
132
|
+
restartInstance(instanceId: string): Promise<void>;
|
|
133
|
+
deleteInstance(instanceId: string): Promise<void>;
|
|
134
|
+
getSystemInfo(): Promise<SystemInfo>;
|
|
135
|
+
getLogs(instanceId: string): Promise<string[]>;
|
|
136
|
+
onInstanceStatusChanged(cb: (instanceId: string, status: LocalPodInstanceStatus, info?: ComfyUISystemStats) => void): () => void;
|
|
137
|
+
onLog(cb: (instanceId: string, line: string) => void): () => void;
|
|
138
|
+
installRocmPyTorch(podId: string): void;
|
|
139
|
+
onRocmSetupDone(cb: (data: {
|
|
140
|
+
podId: string;
|
|
141
|
+
success: boolean;
|
|
142
|
+
}) => void): () => void;
|
|
143
|
+
}
|