@sogni-ai/sogni-client 4.0.0-alpha.5 → 4.0.0-alpha.50
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/CHANGELOG.md +345 -0
- package/README.md +295 -58
- package/dist/Account/index.d.ts +18 -16
- package/dist/Account/index.js +42 -21
- package/dist/Account/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.d.ts +66 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js +332 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js.map +1 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +28 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +203 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -0
- package/dist/ApiClient/WebSocketClient/events.d.ts +12 -0
- package/dist/ApiClient/WebSocketClient/index.d.ts +2 -2
- package/dist/ApiClient/WebSocketClient/index.js +13 -3
- package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/types.d.ts +13 -0
- package/dist/ApiClient/index.d.ts +4 -4
- package/dist/ApiClient/index.js +23 -4
- package/dist/ApiClient/index.js.map +1 -1
- package/dist/Projects/Job.d.ts +44 -4
- package/dist/Projects/Job.js +83 -16
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/Project.d.ts +18 -0
- package/dist/Projects/Project.js +38 -10
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.d.ts +2 -1
- package/dist/Projects/createJobRequestMessage.js +173 -14
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +114 -11
- package/dist/Projects/index.js +504 -47
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/ComfySamplerParams.d.ts +0 -0
- package/dist/Projects/types/ComfySamplerParams.js +2 -0
- package/dist/Projects/types/ComfySamplerParams.js.map +1 -0
- package/dist/Projects/types/EstimationResponse.d.ts +2 -0
- package/dist/Projects/types/ModelOptions.d.ts +31 -0
- package/dist/Projects/types/ModelOptions.js +56 -0
- package/dist/Projects/types/ModelOptions.js.map +1 -0
- package/dist/Projects/types/ModelTiersRaw.d.ts +67 -0
- package/dist/Projects/types/ModelTiersRaw.js +15 -0
- package/dist/Projects/types/ModelTiersRaw.js.map +1 -0
- package/dist/Projects/types/events.d.ts +5 -1
- package/dist/Projects/types/index.d.ts +201 -42
- package/dist/Projects/types/index.js +8 -0
- package/dist/Projects/types/index.js.map +1 -1
- package/dist/Projects/utils/index.d.ts +20 -0
- package/dist/Projects/utils/index.js +82 -0
- package/dist/Projects/utils/index.js.map +1 -0
- package/dist/Projects/utils/samplers.d.ts +6 -0
- package/dist/Projects/utils/samplers.js +39 -0
- package/dist/Projects/utils/samplers.js.map +1 -0
- package/dist/Projects/utils/scheduler.d.ts +6 -0
- package/dist/Projects/utils/scheduler.js +30 -0
- package/dist/Projects/utils/scheduler.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/AuthManager/TokenAuthManager.js +0 -2
- package/dist/lib/AuthManager/TokenAuthManager.js.map +1 -1
- package/dist/lib/DataEntity.js +4 -2
- package/dist/lib/DataEntity.js.map +1 -1
- package/dist/lib/RestClient.js +15 -2
- package/dist/lib/RestClient.js.map +1 -1
- package/dist/lib/{utils.js → utils/index.js} +1 -1
- package/dist/lib/utils/index.js.map +1 -0
- package/dist/lib/validation.d.ts +31 -2
- package/dist/lib/validation.js +80 -13
- package/dist/lib/validation.js.map +1 -1
- package/package.json +4 -4
- package/src/Account/index.ts +39 -20
- package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.ts +426 -0
- package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +237 -0
- package/src/ApiClient/WebSocketClient/events.ts +14 -0
- package/src/ApiClient/WebSocketClient/index.ts +15 -5
- package/src/ApiClient/WebSocketClient/types.ts +16 -0
- package/src/ApiClient/index.ts +30 -8
- package/src/Projects/Job.ts +97 -16
- package/src/Projects/Project.ts +46 -13
- package/src/Projects/createJobRequestMessage.ts +234 -34
- package/src/Projects/index.ts +533 -51
- package/src/Projects/types/ComfySamplerParams.ts +0 -0
- package/src/Projects/types/EstimationResponse.ts +2 -0
- package/src/Projects/types/ModelOptions.ts +92 -0
- package/src/Projects/types/ModelTiersRaw.ts +86 -0
- package/src/Projects/types/events.ts +6 -0
- package/src/Projects/types/index.ts +235 -45
- package/src/Projects/utils/index.ts +77 -0
- package/src/Projects/utils/samplers.ts +36 -0
- package/src/Projects/utils/scheduler.ts +27 -0
- package/src/index.ts +36 -9
- package/src/lib/AuthManager/TokenAuthManager.ts +0 -2
- package/src/lib/DataEntity.ts +4 -2
- package/src/lib/RestClient.ts +16 -2
- package/src/lib/validation.ts +90 -17
- package/dist/Projects/types/SamplerParams.d.ts +0 -15
- package/dist/Projects/types/SamplerParams.js +0 -21
- package/dist/Projects/types/SamplerParams.js.map +0 -1
- package/dist/Projects/types/SchedulerParams.d.ts +0 -13
- package/dist/Projects/types/SchedulerParams.js +0 -19
- package/dist/Projects/types/SchedulerParams.js.map +0 -1
- package/dist/Projects/utils.d.ts +0 -2
- package/dist/Projects/utils.js +0 -14
- package/dist/Projects/utils.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
- package/src/Projects/types/SamplerParams.ts +0 -19
- package/src/Projects/types/SchedulerParams.ts +0 -17
- package/src/Projects/utils.ts +0 -12
- /package/dist/lib/{utils.d.ts → utils/index.d.ts} +0 -0
- /package/src/lib/{utils.ts → utils/index.ts} +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { IWebSocketClient, SupernetType } from '../types';
|
|
2
|
+
import { AuthManager, TokenAuthManager } from '../../../lib/AuthManager';
|
|
3
|
+
import { Logger } from '../../../lib/DefaultLogger';
|
|
4
|
+
import WebSocketClient from '../index';
|
|
5
|
+
import RestClient from '../../../lib/RestClient';
|
|
6
|
+
import { SocketEventMap } from '../events';
|
|
7
|
+
import { MessageType, SocketMessageMap } from '../messages';
|
|
8
|
+
import ChannelCoordinator from './ChannelCoordinator';
|
|
9
|
+
|
|
10
|
+
interface SocketSend<T extends MessageType = MessageType> {
|
|
11
|
+
type: 'socket-send';
|
|
12
|
+
payload: { type: T; data: SocketMessageMap[T] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SocketConnect {
|
|
16
|
+
type: 'connect';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SocketDisconnect {
|
|
20
|
+
type: 'disconnect';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SwitchNetwork {
|
|
24
|
+
type: 'switchNetwork';
|
|
25
|
+
payload: SupernetType;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type Message = SocketConnect | SocketDisconnect | SocketSend | SwitchNetwork;
|
|
29
|
+
|
|
30
|
+
interface EventNotification<T extends keyof SocketEventMap = keyof SocketEventMap> {
|
|
31
|
+
type: 'socket-event';
|
|
32
|
+
payload: { type: T; data: SocketEventMap[T] };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface AuthStateChanged {
|
|
36
|
+
type: 'auth-state-changed';
|
|
37
|
+
payload: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type Notification = EventNotification | AuthStateChanged;
|
|
41
|
+
|
|
42
|
+
type EventInterceptor<T extends keyof SocketEventMap = keyof SocketEventMap> = (
|
|
43
|
+
eventType: T,
|
|
44
|
+
payload: SocketEventMap[T]
|
|
45
|
+
) => void;
|
|
46
|
+
|
|
47
|
+
class WrappedClient extends WebSocketClient {
|
|
48
|
+
private interceptor: EventInterceptor | undefined = undefined;
|
|
49
|
+
intercept(interceptor: EventInterceptor) {
|
|
50
|
+
this.interceptor = interceptor;
|
|
51
|
+
}
|
|
52
|
+
protected emit<T extends keyof SocketEventMap>(event: T, data: SocketEventMap[T]) {
|
|
53
|
+
super.emit(event, data);
|
|
54
|
+
if (this.interceptor) {
|
|
55
|
+
this.interceptor(event, data);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class BrowserWebSocketClient extends RestClient<SocketEventMap> implements IWebSocketClient {
|
|
61
|
+
appId: string;
|
|
62
|
+
baseUrl: string;
|
|
63
|
+
private socketClient: WrappedClient;
|
|
64
|
+
private coordinator: ChannelCoordinator<Message, Notification>;
|
|
65
|
+
private _isConnected = false;
|
|
66
|
+
private _supernetType: SupernetType;
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
baseUrl: string,
|
|
70
|
+
auth: AuthManager,
|
|
71
|
+
appId: string,
|
|
72
|
+
supernetType: SupernetType,
|
|
73
|
+
logger: Logger
|
|
74
|
+
) {
|
|
75
|
+
const socketClient = new WrappedClient(baseUrl, auth, appId, supernetType, logger);
|
|
76
|
+
super(socketClient.baseUrl, auth, logger);
|
|
77
|
+
this.socketClient = socketClient;
|
|
78
|
+
this.appId = appId;
|
|
79
|
+
this.baseUrl = socketClient.baseUrl;
|
|
80
|
+
this._supernetType = supernetType;
|
|
81
|
+
this.coordinator = new ChannelCoordinator({
|
|
82
|
+
callbacks: {
|
|
83
|
+
onRoleChange: this.handleRoleChange.bind(this),
|
|
84
|
+
onMessage: this.handleMessage.bind(this),
|
|
85
|
+
onNotification: this.handleNotification.bind(this)
|
|
86
|
+
},
|
|
87
|
+
logger
|
|
88
|
+
});
|
|
89
|
+
this.auth.on('updated', this.handleAuthUpdated.bind(this));
|
|
90
|
+
this.socketClient.intercept(this.handleSocketEvent.bind(this));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get isConnected() {
|
|
94
|
+
return this.coordinator.isPrimary ? this.socketClient.isConnected : this._isConnected;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get supernetType() {
|
|
98
|
+
return this.coordinator.isPrimary ? this.socketClient.supernetType : this._supernetType;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async connect(): Promise<void> {
|
|
102
|
+
await this.coordinator.isReady();
|
|
103
|
+
if (this.coordinator.isPrimary) {
|
|
104
|
+
await this.socketClient.connect();
|
|
105
|
+
} else {
|
|
106
|
+
return this.coordinator.sendMessage({
|
|
107
|
+
type: 'connect'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async disconnect() {
|
|
113
|
+
await this.coordinator.isReady();
|
|
114
|
+
if (this.coordinator.isPrimary) {
|
|
115
|
+
this.socketClient.disconnect();
|
|
116
|
+
} else {
|
|
117
|
+
this.coordinator.sendMessage({
|
|
118
|
+
type: 'disconnect'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async switchNetwork(supernetType: SupernetType): Promise<SupernetType> {
|
|
124
|
+
await this.coordinator.isReady();
|
|
125
|
+
if (this.coordinator.isPrimary) {
|
|
126
|
+
return this.socketClient.switchNetwork(supernetType);
|
|
127
|
+
}
|
|
128
|
+
await this.coordinator.sendMessage({
|
|
129
|
+
type: 'switchNetwork',
|
|
130
|
+
payload: supernetType
|
|
131
|
+
});
|
|
132
|
+
this._supernetType = supernetType;
|
|
133
|
+
return supernetType;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async send<T extends MessageType>(messageType: T, data: SocketMessageMap[T]): Promise<void> {
|
|
137
|
+
await this.coordinator.isReady();
|
|
138
|
+
if (this.coordinator.isPrimary) {
|
|
139
|
+
if (!this.socketClient.isConnected) {
|
|
140
|
+
await this.socketClient.connect();
|
|
141
|
+
}
|
|
142
|
+
return this.socketClient.send(messageType, data);
|
|
143
|
+
}
|
|
144
|
+
return this.coordinator.sendMessage({
|
|
145
|
+
type: 'socket-send',
|
|
146
|
+
payload: { type: messageType, data }
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async handleMessage(message: Message) {
|
|
151
|
+
this._logger.debug('Received control message', message);
|
|
152
|
+
switch (message.type) {
|
|
153
|
+
case 'socket-send': {
|
|
154
|
+
if (!this.socketClient.isConnected) {
|
|
155
|
+
await this.socketClient.connect();
|
|
156
|
+
}
|
|
157
|
+
return this.socketClient.send(message.payload.type, message.payload.data);
|
|
158
|
+
}
|
|
159
|
+
case 'connect': {
|
|
160
|
+
if (!this.socketClient.isConnected) {
|
|
161
|
+
await this.socketClient.connect();
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
case 'disconnect': {
|
|
166
|
+
if (this.socketClient.isConnected) {
|
|
167
|
+
this.socketClient.disconnect();
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
case 'switchNetwork': {
|
|
172
|
+
await this.switchNetwork(message.payload);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
default: {
|
|
176
|
+
this._logger.error('Received unknown message type:', message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private async handleNotification(notification: Notification) {
|
|
182
|
+
this._logger.debug('Received notification', notification.type, notification.payload);
|
|
183
|
+
switch (notification.type) {
|
|
184
|
+
case 'socket-event': {
|
|
185
|
+
this.emit(notification.payload.type, notification.payload.data);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
case 'auth-state-changed': {
|
|
189
|
+
this.handleAuthChanged(notification.payload);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
default: {
|
|
193
|
+
this._logger.error('Received unknown notification type:', notification);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private handleAuthChanged(isAuthenticated: boolean) {
|
|
199
|
+
if (this.auth instanceof TokenAuthManager) {
|
|
200
|
+
throw new Error('TokenAuthManager is not supported in multi client mode');
|
|
201
|
+
}
|
|
202
|
+
if (this.auth.isAuthenticated !== isAuthenticated) {
|
|
203
|
+
if (isAuthenticated) {
|
|
204
|
+
this.auth.authenticate();
|
|
205
|
+
} else {
|
|
206
|
+
this.auth.clear();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private handleSocketEvent(eventType: keyof SocketEventMap, payload: any) {
|
|
212
|
+
if (this.coordinator.isPrimary) {
|
|
213
|
+
this.coordinator.notify({
|
|
214
|
+
type: 'socket-event',
|
|
215
|
+
payload: { type: eventType, data: payload }
|
|
216
|
+
});
|
|
217
|
+
this.emit(eventType, payload);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private handleAuthUpdated(isAuthenticated: boolean) {
|
|
222
|
+
this.coordinator.notify({
|
|
223
|
+
type: 'auth-state-changed',
|
|
224
|
+
payload: isAuthenticated
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private handleRoleChange(isPrimary: boolean) {
|
|
229
|
+
if (isPrimary && !this.socketClient.isConnected && this.isConnected) {
|
|
230
|
+
this.socketClient.connect();
|
|
231
|
+
} else if (!isPrimary && this.socketClient.isConnected) {
|
|
232
|
+
this.socketClient.disconnect();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default BrowserWebSocketClient;
|
|
@@ -48,6 +48,12 @@ export type JobProgressData = {
|
|
|
48
48
|
stepCount: number;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
export type JobETAData = {
|
|
52
|
+
jobID: string;
|
|
53
|
+
imgID?: string;
|
|
54
|
+
etaSeconds: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
51
57
|
export type JobResultData = {
|
|
52
58
|
jobID: string;
|
|
53
59
|
imgID: string;
|
|
@@ -55,6 +61,7 @@ export type JobResultData = {
|
|
|
55
61
|
lastSeed: string;
|
|
56
62
|
userCanceled: boolean;
|
|
57
63
|
triggeredNSFWFilter: boolean;
|
|
64
|
+
resultUrl?: string;
|
|
58
65
|
};
|
|
59
66
|
|
|
60
67
|
export type JobStateData =
|
|
@@ -121,6 +128,11 @@ export type SocketEventMap = {
|
|
|
121
128
|
* @event WebSocketClient#jobProgress - Job progress update
|
|
122
129
|
*/
|
|
123
130
|
jobProgress: JobProgressData;
|
|
131
|
+
/**
|
|
132
|
+
* @event WebSocketClient#jobETA - Job ETA update (sent every second during inference by ComfyUI workers)
|
|
133
|
+
* Note: Only available for ComfyUI-based workers during video generation
|
|
134
|
+
*/
|
|
135
|
+
jobETA: JobETAData;
|
|
124
136
|
/**
|
|
125
137
|
* @event WebSocketClient#jobResult - Job result received
|
|
126
138
|
*/
|
|
@@ -148,3 +160,5 @@ export type SocketEventMap = {
|
|
|
148
160
|
|
|
149
161
|
artistCancelConfirmation: ArtistCancelConfirmation;
|
|
150
162
|
};
|
|
163
|
+
|
|
164
|
+
export type SocketEventName = keyof SocketEventMap;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MessageType, SocketMessageMap } from './messages';
|
|
2
2
|
import { SocketEventMap } from './events';
|
|
3
3
|
import RestClient from '../../lib/RestClient';
|
|
4
|
-
import { SupernetType } from './types';
|
|
4
|
+
import { IWebSocketClient, SupernetType } from './types';
|
|
5
5
|
import WebSocket, { CloseEvent, ErrorEvent, MessageEvent } from 'isomorphic-ws';
|
|
6
6
|
import { base64Decode, base64Encode } from '../../lib/base64';
|
|
7
7
|
import isNodejs from '../../lib/isNodejs';
|
|
@@ -13,7 +13,7 @@ const PROTOCOL_VERSION = '3.0.0';
|
|
|
13
13
|
|
|
14
14
|
const PING_INTERVAL = 15000;
|
|
15
15
|
|
|
16
|
-
class WebSocketClient extends RestClient<SocketEventMap> {
|
|
16
|
+
class WebSocketClient extends RestClient<SocketEventMap> implements IWebSocketClient {
|
|
17
17
|
appId: string;
|
|
18
18
|
baseUrl: string;
|
|
19
19
|
private socket: WebSocket | null = null;
|
|
@@ -86,7 +86,7 @@ class WebSocketClient extends RestClient<SocketEventMap> {
|
|
|
86
86
|
socket.onmessage = null;
|
|
87
87
|
socket.onopen = null;
|
|
88
88
|
this.stopPing();
|
|
89
|
-
socket.close();
|
|
89
|
+
socket.close(1000, 'Client disconnected');
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
private startPing(socket: WebSocket) {
|
|
@@ -148,9 +148,16 @@ class WebSocketClient extends RestClient<SocketEventMap> {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
private handleClose(e: CloseEvent) {
|
|
151
|
-
|
|
151
|
+
const socket = e.target;
|
|
152
|
+
socket.onerror = null;
|
|
153
|
+
socket.onmessage = null;
|
|
154
|
+
socket.onopen = null;
|
|
155
|
+
if (socket === this.socket || !this.socket) {
|
|
152
156
|
this._logger.info('WebSocket disconnected, cleanup', e);
|
|
153
|
-
this.
|
|
157
|
+
if (socket === this.socket) {
|
|
158
|
+
this.stopPing();
|
|
159
|
+
this.socket = null;
|
|
160
|
+
}
|
|
154
161
|
this.emit('disconnected', {
|
|
155
162
|
code: e.code,
|
|
156
163
|
reason: e.reason
|
|
@@ -193,6 +200,9 @@ class WebSocketClient extends RestClient<SocketEventMap> {
|
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
async send<T extends MessageType>(messageType: T, data: SocketMessageMap[T]) {
|
|
203
|
+
if (!this.isConnected) {
|
|
204
|
+
await this.connect();
|
|
205
|
+
}
|
|
196
206
|
await this.waitForConnection();
|
|
197
207
|
this._logger.debug('WebSocket send:', messageType, data);
|
|
198
208
|
this.socket!.send(
|
|
@@ -1 +1,17 @@
|
|
|
1
|
+
import { MessageType, SocketMessageMap } from './messages';
|
|
2
|
+
import RestClient from '../../lib/RestClient';
|
|
3
|
+
import { SocketEventMap } from './events';
|
|
4
|
+
|
|
1
5
|
export type SupernetType = 'relaxed' | 'fast';
|
|
6
|
+
|
|
7
|
+
export interface IWebSocketClient extends RestClient<SocketEventMap> {
|
|
8
|
+
appId: string;
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
isConnected: boolean;
|
|
11
|
+
supernetType: SupernetType;
|
|
12
|
+
|
|
13
|
+
connect(): Promise<void>;
|
|
14
|
+
disconnect(): void;
|
|
15
|
+
send<T extends MessageType>(messageType: T, data: SocketMessageMap[T]): Promise<void>;
|
|
16
|
+
switchNetwork(supernetType: SupernetType): Promise<SupernetType>;
|
|
17
|
+
}
|
package/src/ApiClient/index.ts
CHANGED
|
@@ -3,12 +3,14 @@ import WebSocketClient from './WebSocketClient';
|
|
|
3
3
|
import TypedEventEmitter from '../lib/TypedEventEmitter';
|
|
4
4
|
import { ApiClientEvents } from './events';
|
|
5
5
|
import { ServerConnectData, ServerDisconnectData } from './WebSocketClient/events';
|
|
6
|
-
import { isNotRecoverable } from './WebSocketClient/ErrorCode';
|
|
6
|
+
import { ErrorCode, isNotRecoverable } from './WebSocketClient/ErrorCode';
|
|
7
7
|
import { JSONValue } from '../types/json';
|
|
8
|
-
import { SupernetType } from './WebSocketClient/types';
|
|
8
|
+
import { IWebSocketClient, SupernetType } from './WebSocketClient/types';
|
|
9
9
|
import { Logger } from '../lib/DefaultLogger';
|
|
10
10
|
import CookieAuthManager from '../lib/AuthManager/CookieAuthManager';
|
|
11
11
|
import { AuthManager, TokenAuthManager } from '../lib/AuthManager';
|
|
12
|
+
import isNodejs from '../lib/isNodejs';
|
|
13
|
+
import BrowserWebSocketClient from './WebSocketClient/BrowserWebSocketClient';
|
|
12
14
|
|
|
13
15
|
const WS_RECONNECT_ATTEMPTS = 5;
|
|
14
16
|
|
|
@@ -42,13 +44,14 @@ export interface ApiClientOptions {
|
|
|
42
44
|
logger: Logger;
|
|
43
45
|
authType: 'token' | 'cookies';
|
|
44
46
|
disableSocket?: boolean;
|
|
47
|
+
multiInstance?: boolean;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
class ApiClient extends TypedEventEmitter<ApiClientEvents> {
|
|
48
51
|
readonly appId: string;
|
|
49
52
|
readonly logger: Logger;
|
|
50
53
|
private _rest: RestClient;
|
|
51
|
-
private _socket:
|
|
54
|
+
private _socket: IWebSocketClient;
|
|
52
55
|
private _auth: AuthManager;
|
|
53
56
|
private _reconnectAttempts = WS_RECONNECT_ATTEMPTS;
|
|
54
57
|
private _disableSocket: boolean = false;
|
|
@@ -60,7 +63,8 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
|
|
|
60
63
|
networkType,
|
|
61
64
|
authType,
|
|
62
65
|
logger,
|
|
63
|
-
disableSocket = false
|
|
66
|
+
disableSocket = false,
|
|
67
|
+
multiInstance = false
|
|
64
68
|
}: ApiClientOptions) {
|
|
65
69
|
super();
|
|
66
70
|
this.appId = appId;
|
|
@@ -68,7 +72,13 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
|
|
|
68
72
|
this._auth =
|
|
69
73
|
authType === 'token' ? new TokenAuthManager(baseUrl, logger) : new CookieAuthManager(logger);
|
|
70
74
|
this._rest = new RestClient(baseUrl, this._auth, logger);
|
|
71
|
-
|
|
75
|
+
const supportMultiInstance = !isNodejs && this._auth instanceof CookieAuthManager;
|
|
76
|
+
if (supportMultiInstance && multiInstance) {
|
|
77
|
+
// Use coordinated WebSocket client to share single connection between tabs
|
|
78
|
+
this._socket = new BrowserWebSocketClient(socketUrl, this._auth, appId, networkType, logger);
|
|
79
|
+
} else {
|
|
80
|
+
this._socket = new WebSocketClient(socketUrl, this._auth, appId, networkType, logger);
|
|
81
|
+
}
|
|
72
82
|
this._disableSocket = disableSocket;
|
|
73
83
|
this._auth.on('updated', this.handleAuthUpdated.bind(this));
|
|
74
84
|
this._socket.on('connected', this.handleSocketConnect.bind(this));
|
|
@@ -83,7 +93,7 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
|
|
|
83
93
|
return this._auth;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
|
-
get socket():
|
|
96
|
+
get socket(): IWebSocketClient {
|
|
87
97
|
return this._socket;
|
|
88
98
|
}
|
|
89
99
|
|
|
@@ -101,14 +111,26 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
|
|
|
101
111
|
}
|
|
102
112
|
|
|
103
113
|
handleSocketDisconnect(data: ServerDisconnectData) {
|
|
114
|
+
// If user is not authenticated, we don't need to reconnect
|
|
115
|
+
if (!this.auth.isAuthenticated || data.code === 1000) {
|
|
116
|
+
this.emit('disconnected', data);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
104
119
|
if (!data.code || isNotRecoverable(data.code)) {
|
|
120
|
+
// If this is browser, another tab is probably claiming the connection, so we don't need to reconnect
|
|
121
|
+
if (
|
|
122
|
+
this._socket instanceof BrowserWebSocketClient &&
|
|
123
|
+
data.code === ErrorCode.SWITCH_CONNECTION
|
|
124
|
+
) {
|
|
125
|
+
this.logger.debug('Switching network connection, not reconnecting');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
105
128
|
this.auth.clear();
|
|
106
129
|
this.emit('disconnected', data);
|
|
107
130
|
this.logger.error('Not recoverable socket error', data);
|
|
108
131
|
return;
|
|
109
132
|
}
|
|
110
133
|
if (this._reconnectAttempts <= 0) {
|
|
111
|
-
this.auth.clear();
|
|
112
134
|
this.emit('disconnected', data);
|
|
113
135
|
this._reconnectAttempts = WS_RECONNECT_ATTEMPTS;
|
|
114
136
|
return;
|
|
@@ -122,7 +144,7 @@ class ApiClient extends TypedEventEmitter<ApiClientEvents> {
|
|
|
122
144
|
if (this.socket.isConnected) {
|
|
123
145
|
this.socket.disconnect();
|
|
124
146
|
}
|
|
125
|
-
} else if (!this._disableSocket) {
|
|
147
|
+
} else if (!this._disableSocket && !this.socket.isConnected) {
|
|
126
148
|
this.socket.connect();
|
|
127
149
|
}
|
|
128
150
|
}
|
package/src/Projects/Job.ts
CHANGED
|
@@ -9,6 +9,7 @@ import Project from './Project';
|
|
|
9
9
|
import { SupernetType } from '../ApiClient/WebSocketClient/types';
|
|
10
10
|
import { getEnhacementStrength } from './utils';
|
|
11
11
|
import { TokenType } from '../types/token';
|
|
12
|
+
import { has } from 'lodash';
|
|
12
13
|
|
|
13
14
|
export const enhancementDefaults = {
|
|
14
15
|
network: 'fast' as SupernetType,
|
|
@@ -19,7 +20,7 @@ export const enhancementDefaults = {
|
|
|
19
20
|
startingImageStrength: 0.5,
|
|
20
21
|
steps: 5,
|
|
21
22
|
guidance: 1,
|
|
22
|
-
|
|
23
|
+
numberOfMedia: 1,
|
|
23
24
|
numberOfPreviews: 0
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -61,6 +62,17 @@ export interface JobData {
|
|
|
61
62
|
positivePrompt?: string;
|
|
62
63
|
negativePrompt?: string;
|
|
63
64
|
jobIndex?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Estimated time remaining in seconds (for long-running jobs like video generation).
|
|
67
|
+
* Updated by ComfyUI workers during inference.
|
|
68
|
+
* @deprecated Use `eta` instead.
|
|
69
|
+
*/
|
|
70
|
+
etaSeconds?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Estimate completion time of the job (for long-running jobs like video generation).
|
|
73
|
+
* Updated by ComfyUI workers during inference.
|
|
74
|
+
*/
|
|
75
|
+
eta?: Date;
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
export interface JobEventMap extends EntityEvents {
|
|
@@ -178,10 +190,21 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
178
190
|
return this.data.error;
|
|
179
191
|
}
|
|
180
192
|
|
|
181
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Whether this job has a result media file available for download.
|
|
195
|
+
* Returns true if completed and not NSFW filtered.
|
|
196
|
+
*/
|
|
197
|
+
get hasResultMedia() {
|
|
182
198
|
return this.status === 'completed' && !this.isNSFW;
|
|
183
199
|
}
|
|
184
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Whether this job produces video output (based on the model used)
|
|
203
|
+
*/
|
|
204
|
+
get type(): 'image' | 'video' {
|
|
205
|
+
return this._api.isVideoModelId(this._project.params.modelId) ? 'video' : 'image';
|
|
206
|
+
}
|
|
207
|
+
|
|
185
208
|
get enhancedImage() {
|
|
186
209
|
if (!this._enhancementProject) {
|
|
187
210
|
return null;
|
|
@@ -199,17 +222,27 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
199
222
|
|
|
200
223
|
/**
|
|
201
224
|
* Get the result URL of the job. This method will make a request to the API to get signed URL.
|
|
202
|
-
* IMPORTANT: URL expires after 30 minutes, so make sure to download the
|
|
225
|
+
* IMPORTANT: URL expires after 30 minutes, so make sure to download the result as soon as possible.
|
|
226
|
+
* For video jobs, this returns a video URL. For image jobs, this returns an image URL.
|
|
203
227
|
*/
|
|
204
228
|
async getResultUrl(): Promise<string> {
|
|
205
229
|
if (this.data.status !== 'completed') {
|
|
206
230
|
throw new Error('Job is not completed yet');
|
|
207
231
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
232
|
+
let url: string;
|
|
233
|
+
if (this.type === 'video') {
|
|
234
|
+
url = await this._api.mediaDownloadUrl({
|
|
235
|
+
jobId: this.projectId,
|
|
236
|
+
id: this.id,
|
|
237
|
+
type: 'complete'
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
url = await this._api.downloadUrl({
|
|
241
|
+
jobId: this.projectId,
|
|
242
|
+
imageId: this.id,
|
|
243
|
+
type: 'complete'
|
|
244
|
+
});
|
|
245
|
+
}
|
|
213
246
|
this._update({ resultUrl: url });
|
|
214
247
|
return url;
|
|
215
248
|
}
|
|
@@ -230,6 +263,26 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
230
263
|
return this.data.workerName;
|
|
231
264
|
}
|
|
232
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Estimated time remaining in seconds for long-running jobs (e.g., video generation).
|
|
268
|
+
* Only available for ComfyUI-based workers during inference.
|
|
269
|
+
* Returns undefined if no ETA has been received.
|
|
270
|
+
* @deprecated Use `timeLeft` instead.
|
|
271
|
+
*/
|
|
272
|
+
get etaSeconds() {
|
|
273
|
+
return this.data.etaSeconds;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Estimate completion time of the job.
|
|
278
|
+
* Only available for ComfyUI-based workers during inference.
|
|
279
|
+
* Is useful when data is persisted
|
|
280
|
+
* Returns undefined if no ETA has been received.
|
|
281
|
+
*/
|
|
282
|
+
get eta() {
|
|
283
|
+
return this.data.eta;
|
|
284
|
+
}
|
|
285
|
+
|
|
233
286
|
/**
|
|
234
287
|
* Syncs the job data with the data received from the REST API.
|
|
235
288
|
* @internal
|
|
@@ -247,11 +300,19 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
247
300
|
}
|
|
248
301
|
if (!this.data.resultUrl && delta.status === 'completed' && !data.triggeredNSFWFilter) {
|
|
249
302
|
try {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
303
|
+
if (this.type === 'video') {
|
|
304
|
+
delta.resultUrl = await this._api.mediaDownloadUrl({
|
|
305
|
+
jobId: this.projectId,
|
|
306
|
+
id: this.id,
|
|
307
|
+
type: 'complete'
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
delta.resultUrl = await this._api.downloadUrl({
|
|
311
|
+
jobId: this.projectId,
|
|
312
|
+
imageId: this.id,
|
|
313
|
+
type: 'complete'
|
|
314
|
+
});
|
|
315
|
+
}
|
|
255
316
|
} catch (error) {
|
|
256
317
|
this._logger.error(error);
|
|
257
318
|
}
|
|
@@ -259,6 +320,21 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
259
320
|
this._update(delta);
|
|
260
321
|
}
|
|
261
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Updates the job data with the provided delta.
|
|
325
|
+
* @internal
|
|
326
|
+
* @param delta
|
|
327
|
+
*/
|
|
328
|
+
_update(delta: Partial<JobData>) {
|
|
329
|
+
if (has(delta, 'eta')) {
|
|
330
|
+
// Keeping etaSeconds for backwards compatibility
|
|
331
|
+
if (delta.eta) {
|
|
332
|
+
delta.etaSeconds = Math.round((delta.eta.getTime() - Date.now()) / 1000);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
super._update(delta);
|
|
336
|
+
}
|
|
337
|
+
|
|
262
338
|
private handleUpdated(keys: string[]) {
|
|
263
339
|
if (keys.includes('step') || keys.includes('stepCount')) {
|
|
264
340
|
this.emit('progress', this.progress);
|
|
@@ -276,8 +352,8 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
276
352
|
}
|
|
277
353
|
|
|
278
354
|
async getResultData() {
|
|
279
|
-
if (!this.
|
|
280
|
-
throw new Error('No result
|
|
355
|
+
if (!this.hasResultMedia) {
|
|
356
|
+
throw new Error('No result media available');
|
|
281
357
|
}
|
|
282
358
|
const url = await this.getResultUrl();
|
|
283
359
|
const response = await fetch(url);
|
|
@@ -297,6 +373,10 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
297
373
|
strength: EnhancementStrength,
|
|
298
374
|
overrides: { positivePrompt?: string; stylePrompt?: string; tokenType?: TokenType } = {}
|
|
299
375
|
) {
|
|
376
|
+
const parentProjectParams = this._project.params;
|
|
377
|
+
if (parentProjectParams.type !== 'image') {
|
|
378
|
+
throw new Error('Enhancement is only available for images');
|
|
379
|
+
}
|
|
300
380
|
if (this.status !== 'completed') {
|
|
301
381
|
throw new Error('Job is not completed yet');
|
|
302
382
|
}
|
|
@@ -309,6 +389,7 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
309
389
|
}
|
|
310
390
|
const imageData = await this.getResultData();
|
|
311
391
|
const project = await this._api.create({
|
|
392
|
+
type: 'image',
|
|
312
393
|
...enhancementDefaults,
|
|
313
394
|
positivePrompt: overrides.positivePrompt || this._project.params.positivePrompt,
|
|
314
395
|
stylePrompt: overrides.stylePrompt || this._project.params.stylePrompt,
|
|
@@ -316,7 +397,7 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
316
397
|
seed: this.seed || this._project.params.seed,
|
|
317
398
|
startingImage: imageData,
|
|
318
399
|
startingImageStrength: 1 - getEnhacementStrength(strength),
|
|
319
|
-
sizePreset:
|
|
400
|
+
sizePreset: parentProjectParams.sizePreset
|
|
320
401
|
});
|
|
321
402
|
this._enhancementProject = project;
|
|
322
403
|
this._enhancementProject.on('updated', this.handleEnhancementUpdate);
|