@sogni-ai/sogni-client 0.3.3 → 0.4.0-aplha.10
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/README.md +3 -2
- package/dist/Account/CurrentAccount.d.ts +12 -2
- package/dist/Account/CurrentAccount.js.map +1 -1
- package/dist/Account/index.d.ts +130 -7
- package/dist/Account/index.js +135 -7
- package/dist/Account/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/events.d.ts +7 -1
- package/dist/ApiClient/WebSocketClient/index.d.ts +1 -1
- package/dist/ApiClient/WebSocketClient/index.js +14 -5
- package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/messages.d.ts +2 -0
- package/dist/Projects/Job.d.ts +25 -1
- package/dist/Projects/Job.js +78 -1
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/Project.d.ts +21 -3
- package/dist/Projects/Project.js +110 -2
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.d.ts +1 -61
- package/dist/Projects/createJobRequestMessage.js +5 -1
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +22 -3
- package/dist/Projects/index.js +82 -14
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/RawProject.d.ts +87 -0
- package/dist/Projects/types/RawProject.js +3 -0
- package/dist/Projects/types/RawProject.js.map +1 -0
- package/dist/Projects/types/events.d.ts +2 -0
- package/dist/Projects/types/index.d.ts +4 -0
- package/dist/lib/DataEntity.d.ts +1 -0
- package/dist/lib/DataEntity.js +2 -0
- package/dist/lib/DataEntity.js.map +1 -1
- package/dist/lib/base64.js +8 -6
- package/dist/lib/base64.js.map +1 -1
- package/dist/types/ErrorData.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/Account/CurrentAccount.ts +11 -1
- package/src/Account/index.ts +137 -9
- package/src/ApiClient/WebSocketClient/events.ts +5 -1
- package/src/ApiClient/WebSocketClient/index.ts +15 -6
- package/src/ApiClient/WebSocketClient/messages.ts +2 -0
- package/src/Projects/Job.ts +90 -1
- package/src/Projects/Project.ts +134 -5
- package/src/Projects/createJobRequestMessage.ts +5 -1
- package/src/Projects/index.ts +87 -16
- package/src/Projects/types/RawProject.ts +121 -0
- package/src/Projects/types/events.ts +2 -0
- package/src/Projects/types/index.ts +4 -0
- package/src/lib/DataEntity.ts +3 -0
- package/src/lib/base64.ts +8 -4
- package/src/types/ErrorData.ts +1 -0
- package/src/version.ts +1 -1
package/src/Account/index.ts
CHANGED
|
@@ -17,6 +17,14 @@ import { SupernetType } from '../ApiClient/WebSocketClient/types';
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Account API methods that let you interact with the user's account.
|
|
20
|
+
* Can be accessed via `client.account`. Look for more samples below.
|
|
21
|
+
*
|
|
22
|
+
* @example Retrieve the current account balance
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const balance = await client.account.refreshBalance();
|
|
25
|
+
* console.log(balance);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
20
28
|
*/
|
|
21
29
|
class AccountApi extends ApiGroup {
|
|
22
30
|
readonly currentAccount = new CurrentAccount();
|
|
@@ -43,13 +51,27 @@ class AccountApi extends ApiGroup {
|
|
|
43
51
|
this.currentAccount._clear();
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
async getNonce(walletAddress: string): Promise<string> {
|
|
54
|
+
private async getNonce(walletAddress: string): Promise<string> {
|
|
47
55
|
const res = await this.client.rest.post<ApiReponse<Nonce>>('/v1/account/nonce', {
|
|
48
56
|
walletAddress
|
|
49
57
|
});
|
|
50
58
|
return res.data.nonce;
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Create Ethers.js Wallet instance from username and password.
|
|
63
|
+
* This method is used internally to create a wallet for the user.
|
|
64
|
+
* You can use this method to create a wallet if you need to sign transactions.
|
|
65
|
+
*
|
|
66
|
+
* @example Create a wallet from username and password
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const wallet = client.account.getWallet('username', 'password');
|
|
69
|
+
* console.log(wallet.address);
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @param username - Sogni account username
|
|
73
|
+
* @param password - Sogni account password
|
|
74
|
+
*/
|
|
53
75
|
getWallet(username: string, password: string): Wallet {
|
|
54
76
|
const pwd = toUtf8Bytes(username.toLowerCase() + password);
|
|
55
77
|
const salt = toUtf8Bytes('sogni-salt-value');
|
|
@@ -57,6 +79,16 @@ class AccountApi extends ApiGroup {
|
|
|
57
79
|
return new Wallet(pkey, this.provider);
|
|
58
80
|
}
|
|
59
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Create a new account with the given username, email, and password.
|
|
84
|
+
* @internal
|
|
85
|
+
*
|
|
86
|
+
* @param username
|
|
87
|
+
* @param email
|
|
88
|
+
* @param password
|
|
89
|
+
* @param subscribe
|
|
90
|
+
* @param referralCode
|
|
91
|
+
*/
|
|
60
92
|
async create(
|
|
61
93
|
username: string,
|
|
62
94
|
email: string,
|
|
@@ -83,6 +115,31 @@ class AccountApi extends ApiGroup {
|
|
|
83
115
|
return res.data;
|
|
84
116
|
}
|
|
85
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Restore session with username and access token.
|
|
120
|
+
*
|
|
121
|
+
* You can save access token that you get from the login method and restore the session with this method.
|
|
122
|
+
*
|
|
123
|
+
* @example Store access token to local storage
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const { username, token } = await client.account.login('username', 'password');
|
|
126
|
+
* localStorage.setItem('sogni-username', username);
|
|
127
|
+
* localStorage.setItem('sogni-token', token);
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example Restore session from local storage
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const username = localStorage.getItem('sogni-username');
|
|
133
|
+
* const token = localStorage.getItem('sogni-token');
|
|
134
|
+
* if (username && token) {
|
|
135
|
+
* client.account.setToken(username, token);
|
|
136
|
+
* console.log('Session restored');
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* @param username
|
|
141
|
+
* @param token
|
|
142
|
+
*/
|
|
86
143
|
setToken(username: string, token: string): void {
|
|
87
144
|
this.client.authenticate(token);
|
|
88
145
|
this.currentAccount._update({
|
|
@@ -93,6 +150,13 @@ class AccountApi extends ApiGroup {
|
|
|
93
150
|
|
|
94
151
|
/**
|
|
95
152
|
* Login with username and password. WebSocket connection is established after successful login.
|
|
153
|
+
*
|
|
154
|
+
* @example Login with username and password
|
|
155
|
+
* ```typescript
|
|
156
|
+
* await client.account.login('username', 'password');
|
|
157
|
+
* console.log('Logged in');
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
96
160
|
* @param username
|
|
97
161
|
* @param password
|
|
98
162
|
*/
|
|
@@ -113,6 +177,12 @@ class AccountApi extends ApiGroup {
|
|
|
113
177
|
|
|
114
178
|
/**
|
|
115
179
|
* Logout the user and close the WebSocket connection.
|
|
180
|
+
*
|
|
181
|
+
* @example Logout the user
|
|
182
|
+
* ```typescript
|
|
183
|
+
* await client.account.logout();
|
|
184
|
+
* console.log('Logged out');
|
|
185
|
+
* ```
|
|
116
186
|
*/
|
|
117
187
|
async logout(): Promise<void> {
|
|
118
188
|
this.client.rest.post('/v1/account/logout').catch((e) => {
|
|
@@ -124,6 +194,16 @@ class AccountApi extends ApiGroup {
|
|
|
124
194
|
|
|
125
195
|
/**
|
|
126
196
|
* Refresh the balance of the current account.
|
|
197
|
+
*
|
|
198
|
+
* Usually, you don't need to call this method manually. Balance is updated automatically
|
|
199
|
+
* through WebSocket events. But you can call this method to force a balance refresh.
|
|
200
|
+
*
|
|
201
|
+
* @example Refresh user account balance
|
|
202
|
+
* ```typescript
|
|
203
|
+
* const balance = await client.account.refreshBalance();
|
|
204
|
+
* console.log(balance);
|
|
205
|
+
* // { net: '100.000000', settled: '100.000000', credit: '0.000000', debit: '0.000000' }
|
|
206
|
+
* ```
|
|
127
207
|
*/
|
|
128
208
|
async refreshBalance(): Promise<BalanceData> {
|
|
129
209
|
const res = await this.client.rest.get<ApiReponse<BalanceData>>('/v1/account/balance');
|
|
@@ -132,7 +212,18 @@ class AccountApi extends ApiGroup {
|
|
|
132
212
|
}
|
|
133
213
|
|
|
134
214
|
/**
|
|
135
|
-
* Get the balance of
|
|
215
|
+
* Get the balance of the wallet address.
|
|
216
|
+
*
|
|
217
|
+
* This method is used to get the balance of the wallet address. It returns $SOGNI and ETH balance.
|
|
218
|
+
*
|
|
219
|
+
* @example Get the balance of the wallet address
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const address = client.account.currentAccount.walletAddress;
|
|
222
|
+
* const balance = await client.account.walletBalance(address);
|
|
223
|
+
* console.log(balance);
|
|
224
|
+
* // { token: '100.000000', ether: '0.000000' }
|
|
225
|
+
* ```
|
|
226
|
+
*
|
|
136
227
|
* @param walletAddress
|
|
137
228
|
*/
|
|
138
229
|
async walletBalance(walletAddress: string) {
|
|
@@ -145,6 +236,11 @@ class AccountApi extends ApiGroup {
|
|
|
145
236
|
return res.data;
|
|
146
237
|
}
|
|
147
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Validate the username before signup
|
|
241
|
+
* @internal
|
|
242
|
+
* @param username
|
|
243
|
+
*/
|
|
148
244
|
async validateUsername(username: string) {
|
|
149
245
|
try {
|
|
150
246
|
return await this.client.rest.post<ApiReponse<undefined>>('/v1/account/username/validate', {
|
|
@@ -163,21 +259,44 @@ class AccountApi extends ApiGroup {
|
|
|
163
259
|
|
|
164
260
|
/**
|
|
165
261
|
* Switch between fast and relaxed networks.
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
262
|
+
* This will change default network used to process projects. After switching, you will updated
|
|
263
|
+
* list of AI models available for on selected network.
|
|
264
|
+
*
|
|
265
|
+
* @example Switch to the fast network
|
|
266
|
+
* ```typescript
|
|
267
|
+
* await client.account.switchNetwork('fast');
|
|
268
|
+
* console.log('Switched to the fast network, now lets wait until we get list of models');
|
|
269
|
+
* await client.projects.waitForModels();
|
|
270
|
+
* ```
|
|
271
|
+
* @param network - Network type to switch to
|
|
169
272
|
*/
|
|
170
|
-
async switchNetwork(network: SupernetType) {
|
|
273
|
+
async switchNetwork(network: SupernetType): Promise<SupernetType> {
|
|
171
274
|
this.currentAccount._update({
|
|
172
|
-
networkStatus: '
|
|
275
|
+
networkStatus: 'switching',
|
|
173
276
|
network: null
|
|
174
277
|
});
|
|
175
|
-
this.client.socket.switchNetwork(network);
|
|
278
|
+
const newNetwork = await this.client.socket.switchNetwork(network);
|
|
279
|
+
this.currentAccount._update({
|
|
280
|
+
networkStatus: 'connected',
|
|
281
|
+
network: newNetwork
|
|
282
|
+
});
|
|
283
|
+
return newNetwork;
|
|
176
284
|
}
|
|
177
285
|
|
|
178
286
|
/**
|
|
179
287
|
* Get the transaction history of the current account.
|
|
180
|
-
*
|
|
288
|
+
*
|
|
289
|
+
* @example Get the transaction history
|
|
290
|
+
* ```typescript
|
|
291
|
+
* const { entries, next } = await client.account.transactionHistory({
|
|
292
|
+
* status: 'completed',
|
|
293
|
+
* limit: 10,
|
|
294
|
+
* address: client.account.currentAccount.walletAddress
|
|
295
|
+
* });
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @param params - Transaction history query parameters
|
|
299
|
+
* @returns Transaction history entries and next query parameters
|
|
181
300
|
*/
|
|
182
301
|
async transactionHistory(
|
|
183
302
|
params: TxHistoryParams
|
|
@@ -211,6 +330,10 @@ class AccountApi extends ApiGroup {
|
|
|
211
330
|
};
|
|
212
331
|
}
|
|
213
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Get the rewards of the current account.
|
|
335
|
+
* @internal
|
|
336
|
+
*/
|
|
214
337
|
async rewards(): Promise<Reward[]> {
|
|
215
338
|
const r =
|
|
216
339
|
await this.client.rest.get<ApiReponse<{ rewards: RewardRaw[] }>>('/v2/account/rewards');
|
|
@@ -233,6 +356,11 @@ class AccountApi extends ApiGroup {
|
|
|
233
356
|
);
|
|
234
357
|
}
|
|
235
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Claim rewards by reward IDs.
|
|
361
|
+
* @internal
|
|
362
|
+
* @param rewardIds
|
|
363
|
+
*/
|
|
236
364
|
async claimRewards(rewardIds: string[]): Promise<void> {
|
|
237
365
|
await this.client.rest.post('/v2/account/reward/claim', {
|
|
238
366
|
claims: rewardIds
|
|
@@ -12,7 +12,7 @@ export type JobErrorData = {
|
|
|
12
12
|
imgID?: string;
|
|
13
13
|
isFromWorker: boolean;
|
|
14
14
|
error_message: string;
|
|
15
|
-
error: number;
|
|
15
|
+
error: number | string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export type JobProgressData = {
|
|
@@ -63,6 +63,10 @@ export type SocketEventMap = {
|
|
|
63
63
|
* @event WebSocketClient#balanceUpdate - Received balance update
|
|
64
64
|
*/
|
|
65
65
|
balanceUpdate: BalanceData;
|
|
66
|
+
/**
|
|
67
|
+
* @event WebSocketClient#changeNetwork - Default network changed
|
|
68
|
+
*/
|
|
69
|
+
changeNetwork: { network: SupernetType };
|
|
66
70
|
/**
|
|
67
71
|
* @event WebSocketClient#jobError - Job error occurred
|
|
68
72
|
*/
|
|
@@ -19,9 +19,13 @@ class WebSocketClient extends RestClient<SocketEventMap> {
|
|
|
19
19
|
private _pingInterval: NodeJS.Timeout | null = null;
|
|
20
20
|
|
|
21
21
|
constructor(baseUrl: string, appId: string, supernetType: SupernetType, logger: Logger) {
|
|
22
|
-
|
|
22
|
+
const _baseUrl = new URL(baseUrl);
|
|
23
|
+
if (_baseUrl.protocol === 'wss:') {
|
|
24
|
+
_baseUrl.protocol = 'https:';
|
|
25
|
+
}
|
|
26
|
+
super(_baseUrl.toString(), logger);
|
|
23
27
|
this.appId = appId;
|
|
24
|
-
this.baseUrl =
|
|
28
|
+
this.baseUrl = _baseUrl.toString();
|
|
25
29
|
this._supernetType = supernetType;
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -56,6 +60,7 @@ class WebSocketClient extends RestClient<SocketEventMap> {
|
|
|
56
60
|
}
|
|
57
61
|
const userAgent = `Sogni/${LIB_VERSION} (sogni-client)`;
|
|
58
62
|
const url = new URL(this.baseUrl);
|
|
63
|
+
url.protocol = 'wss:';
|
|
59
64
|
url.searchParams.set('appId', this.appId);
|
|
60
65
|
url.searchParams.set('clientName', userAgent);
|
|
61
66
|
url.searchParams.set('clientType', 'artist');
|
|
@@ -108,10 +113,14 @@ class WebSocketClient extends RestClient<SocketEventMap> {
|
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
switchNetwork(supernetType: SupernetType) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
switchNetwork(supernetType: SupernetType): Promise<SupernetType> {
|
|
117
|
+
return new Promise<SupernetType>(async (resolve, reject) => {
|
|
118
|
+
this.once('changeNetwork', ({ network }) => {
|
|
119
|
+
this._supernetType = network;
|
|
120
|
+
resolve(network);
|
|
121
|
+
});
|
|
122
|
+
await this.send('changeNetwork', supernetType);
|
|
123
|
+
});
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
/**
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { JobRequestRaw } from '../../Projects/createJobRequestMessage';
|
|
2
|
+
import { SupernetType } from './types';
|
|
2
3
|
|
|
3
4
|
export interface SocketMessageMap {
|
|
4
5
|
jobRequest: JobRequestRaw;
|
|
6
|
+
changeNetwork: SupernetType;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
export type MessageType = keyof SocketMessageMap;
|
package/src/Projects/Job.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import DataEntity, { EntityEvents } from '../lib/DataEntity';
|
|
2
2
|
import ErrorData from '../types/ErrorData';
|
|
3
|
+
import { RawJob, RawProject } from './types/RawProject';
|
|
4
|
+
import ProjectsApi from './index';
|
|
5
|
+
import { Logger } from '../lib/DefaultLogger';
|
|
6
|
+
import getUUID from '../lib/getUUID';
|
|
3
7
|
|
|
4
8
|
export type JobStatus =
|
|
5
9
|
| 'pending'
|
|
@@ -9,14 +13,27 @@ export type JobStatus =
|
|
|
9
13
|
| 'failed'
|
|
10
14
|
| 'canceled';
|
|
11
15
|
|
|
16
|
+
const JOB_STATUS_MAP: Record<RawJob['status'], JobStatus> = {
|
|
17
|
+
created: 'pending',
|
|
18
|
+
queued: 'pending',
|
|
19
|
+
assigned: 'initiating',
|
|
20
|
+
initiatingModel: 'initiating',
|
|
21
|
+
jobStarted: 'processing',
|
|
22
|
+
jobProgress: 'processing',
|
|
23
|
+
jobCompleted: 'completed',
|
|
24
|
+
jobError: 'failed'
|
|
25
|
+
};
|
|
26
|
+
|
|
12
27
|
/**
|
|
13
28
|
* @inline
|
|
14
29
|
*/
|
|
15
30
|
export interface JobData {
|
|
16
31
|
id: string;
|
|
32
|
+
projectId: string;
|
|
17
33
|
status: JobStatus;
|
|
18
34
|
step: number;
|
|
19
35
|
stepCount: number;
|
|
36
|
+
workerName?: string;
|
|
20
37
|
seed?: number;
|
|
21
38
|
isNSFW?: boolean;
|
|
22
39
|
userCanceled?: boolean;
|
|
@@ -31,9 +48,37 @@ export interface JobEventMap extends EntityEvents {
|
|
|
31
48
|
failed: ErrorData;
|
|
32
49
|
}
|
|
33
50
|
|
|
51
|
+
export interface JobOptions {
|
|
52
|
+
api: ProjectsApi;
|
|
53
|
+
logger: Logger;
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
class Job extends DataEntity<JobData, JobEventMap> {
|
|
35
|
-
|
|
57
|
+
static fromRaw(rawProject: RawProject, rawJob: RawJob, options: JobOptions) {
|
|
58
|
+
return new Job(
|
|
59
|
+
{
|
|
60
|
+
id: rawJob.imgID || getUUID(),
|
|
61
|
+
projectId: rawProject.id,
|
|
62
|
+
status: JOB_STATUS_MAP[rawJob.status],
|
|
63
|
+
step: rawJob.performedSteps,
|
|
64
|
+
stepCount: rawProject.stepCount,
|
|
65
|
+
workerName: rawJob.worker.name,
|
|
66
|
+
seed: rawJob.seedUsed,
|
|
67
|
+
isNSFW: rawJob.triggeredNSFWFilter
|
|
68
|
+
},
|
|
69
|
+
options
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private readonly _api: ProjectsApi;
|
|
74
|
+
private readonly _logger: Logger;
|
|
75
|
+
|
|
76
|
+
constructor(data: JobData, options: JobOptions) {
|
|
36
77
|
super(data);
|
|
78
|
+
|
|
79
|
+
this._api = options.api;
|
|
80
|
+
this._logger = options.logger;
|
|
81
|
+
|
|
37
82
|
this.on('updated', this.handleUpdated.bind(this));
|
|
38
83
|
}
|
|
39
84
|
|
|
@@ -41,6 +86,10 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
41
86
|
return this.data.id;
|
|
42
87
|
}
|
|
43
88
|
|
|
89
|
+
get projectId() {
|
|
90
|
+
return this.data.projectId;
|
|
91
|
+
}
|
|
92
|
+
|
|
44
93
|
/**
|
|
45
94
|
* Current status of the job.
|
|
46
95
|
*/
|
|
@@ -48,6 +97,10 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
48
97
|
return this.data.status;
|
|
49
98
|
}
|
|
50
99
|
|
|
100
|
+
get finished() {
|
|
101
|
+
return ['completed', 'failed', 'canceled'].includes(this.status);
|
|
102
|
+
}
|
|
103
|
+
|
|
51
104
|
/**
|
|
52
105
|
* Progress of the job in percentage (0-100).
|
|
53
106
|
*/
|
|
@@ -108,6 +161,42 @@ class Job extends DataEntity<JobData, JobEventMap> {
|
|
|
108
161
|
return !!this.data.isNSFW;
|
|
109
162
|
}
|
|
110
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Name of the worker that is processing this job.
|
|
166
|
+
*/
|
|
167
|
+
get workerName() {
|
|
168
|
+
return this.data.workerName;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Syncs the job data with the data received from the REST API.
|
|
173
|
+
* @internal
|
|
174
|
+
* @param data
|
|
175
|
+
*/
|
|
176
|
+
async _syncWithRestData(data: RawJob) {
|
|
177
|
+
const delta: Partial<JobData> = {
|
|
178
|
+
step: data.performedSteps,
|
|
179
|
+
workerName: data.worker.name,
|
|
180
|
+
seed: data.seedUsed,
|
|
181
|
+
isNSFW: data.triggeredNSFWFilter
|
|
182
|
+
};
|
|
183
|
+
if (JOB_STATUS_MAP[data.status]) {
|
|
184
|
+
delta.status = JOB_STATUS_MAP[data.status];
|
|
185
|
+
}
|
|
186
|
+
if (!this.data.resultUrl && delta.status === 'completed' && !data.triggeredNSFWFilter) {
|
|
187
|
+
try {
|
|
188
|
+
delta.resultUrl = await this._api.downloadUrl({
|
|
189
|
+
jobId: this.projectId,
|
|
190
|
+
imageId: this.id,
|
|
191
|
+
type: 'complete'
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
this._logger.error(error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this._update(delta);
|
|
198
|
+
}
|
|
199
|
+
|
|
111
200
|
private handleUpdated(keys: string[]) {
|
|
112
201
|
if (keys.includes('step') || keys.includes('stepCount')) {
|
|
113
202
|
this.emit('progress', this.progress);
|
package/src/Projects/Project.ts
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
import Job, { JobData } from './Job';
|
|
1
|
+
import Job, { JobData, JobStatus } from './Job';
|
|
2
2
|
import DataEntity, { EntityEvents } from '../lib/DataEntity';
|
|
3
3
|
import { ProjectParams } from './types';
|
|
4
4
|
import cloneDeep from 'lodash/cloneDeep';
|
|
5
5
|
import ErrorData from '../types/ErrorData';
|
|
6
6
|
import getUUID from '../lib/getUUID';
|
|
7
|
+
import { RawJob, RawProject } from './types/RawProject';
|
|
8
|
+
import ProjectsApi from './index';
|
|
9
|
+
import { Logger } from '../lib/DefaultLogger';
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
// If project is not finished and had no updates for 1 minute, force refresh
|
|
12
|
+
const PROJECT_TIMEOUT = 60 * 1000;
|
|
13
|
+
const MAX_FAILED_SYNC_ATTEMPTS = 3;
|
|
14
|
+
|
|
15
|
+
export type ProjectStatus =
|
|
16
|
+
| 'pending'
|
|
17
|
+
| 'queued'
|
|
18
|
+
| 'processing'
|
|
19
|
+
| 'completed'
|
|
20
|
+
| 'failed'
|
|
21
|
+
| 'canceled';
|
|
22
|
+
|
|
23
|
+
const PROJECT_STATUS_MAP: Record<RawProject['status'], ProjectStatus> = {
|
|
24
|
+
pending: 'pending',
|
|
25
|
+
active: 'queued',
|
|
26
|
+
assigned: 'processing',
|
|
27
|
+
progress: 'processing',
|
|
28
|
+
completed: 'completed',
|
|
29
|
+
errored: 'failed',
|
|
30
|
+
cancelled: 'canceled'
|
|
31
|
+
};
|
|
9
32
|
|
|
10
33
|
/**
|
|
11
34
|
* @inline
|
|
@@ -31,11 +54,20 @@ export interface ProjectEventMap extends EntityEvents {
|
|
|
31
54
|
jobFailed: Job;
|
|
32
55
|
}
|
|
33
56
|
|
|
57
|
+
export interface ProjectOptions {
|
|
58
|
+
api: ProjectsApi;
|
|
59
|
+
logger: Logger;
|
|
60
|
+
}
|
|
61
|
+
|
|
34
62
|
class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
35
63
|
private _jobs: Job[] = [];
|
|
36
64
|
private _lastEmitedProgress = -1;
|
|
65
|
+
private readonly _api: ProjectsApi;
|
|
66
|
+
private readonly _logger: Logger;
|
|
67
|
+
private _timeout: NodeJS.Timeout | null = null;
|
|
68
|
+
private _failedSyncAttempts = 0;
|
|
37
69
|
|
|
38
|
-
constructor(data: ProjectParams) {
|
|
70
|
+
constructor(data: ProjectParams, options: ProjectOptions) {
|
|
39
71
|
super({
|
|
40
72
|
id: getUUID(),
|
|
41
73
|
startedAt: new Date(),
|
|
@@ -44,6 +76,11 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
44
76
|
status: 'pending'
|
|
45
77
|
});
|
|
46
78
|
|
|
79
|
+
this._api = options.api;
|
|
80
|
+
this._logger = options.logger;
|
|
81
|
+
|
|
82
|
+
this._timeout = setInterval(this._checkForTimeout.bind(this), PROJECT_TIMEOUT);
|
|
83
|
+
|
|
47
84
|
this.on('updated', this.handleUpdated.bind(this));
|
|
48
85
|
}
|
|
49
86
|
|
|
@@ -59,6 +96,10 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
59
96
|
return this.data.status;
|
|
60
97
|
}
|
|
61
98
|
|
|
99
|
+
get finished() {
|
|
100
|
+
return ['completed', 'failed', 'canceled'].includes(this.status);
|
|
101
|
+
}
|
|
102
|
+
|
|
62
103
|
get error() {
|
|
63
104
|
return this.data.error;
|
|
64
105
|
}
|
|
@@ -137,6 +178,11 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
137
178
|
this.emit('progress', progress);
|
|
138
179
|
this._lastEmitedProgress = progress;
|
|
139
180
|
}
|
|
181
|
+
// If project is finished stop watching for timeout
|
|
182
|
+
if (this._timeout && this.finished) {
|
|
183
|
+
clearInterval(this._timeout!);
|
|
184
|
+
this._timeout = null;
|
|
185
|
+
}
|
|
140
186
|
if (keys.includes('status') || keys.includes('jobs')) {
|
|
141
187
|
const allJobsDone = this.jobs.every((job) =>
|
|
142
188
|
['completed', 'failed', 'canceled'].includes(job.status)
|
|
@@ -155,21 +201,104 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
155
201
|
* @internal
|
|
156
202
|
* @param data
|
|
157
203
|
*/
|
|
158
|
-
_addJob(data: JobData) {
|
|
159
|
-
const job =
|
|
204
|
+
_addJob(data: JobData | Job) {
|
|
205
|
+
const job =
|
|
206
|
+
data instanceof Job ? data : new Job(data, { api: this._api, logger: this._logger });
|
|
160
207
|
this._jobs.push(job);
|
|
161
208
|
job.on('updated', () => {
|
|
209
|
+
this.lastUpdated = new Date();
|
|
162
210
|
this.emit('updated', ['jobs']);
|
|
163
211
|
});
|
|
164
212
|
job.on('completed', () => {
|
|
165
213
|
this.emit('jobCompleted', job);
|
|
214
|
+
this._handleJobFinished(job);
|
|
166
215
|
});
|
|
167
216
|
job.on('failed', () => {
|
|
168
217
|
this.emit('jobFailed', job);
|
|
218
|
+
this._handleJobFinished(job);
|
|
169
219
|
});
|
|
170
220
|
return job;
|
|
171
221
|
}
|
|
172
222
|
|
|
223
|
+
private _handleJobFinished(job: Job) {
|
|
224
|
+
const finalStatus: JobStatus[] = ['completed', 'failed', 'canceled'];
|
|
225
|
+
const allJobsDone = this.jobs.every((job) => finalStatus.includes(job.status));
|
|
226
|
+
// If all jobs are done and project is not already failed or completed, update the project status
|
|
227
|
+
if (allJobsDone && this.status !== 'failed' && this.status !== 'completed') {
|
|
228
|
+
const allJobsFailed = this.jobs.every((job) => job.status === 'failed');
|
|
229
|
+
if (allJobsFailed) {
|
|
230
|
+
this._update({ status: 'failed' });
|
|
231
|
+
} else {
|
|
232
|
+
this._update({ status: 'completed' });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private _checkForTimeout() {
|
|
238
|
+
if (this.lastUpdated.getTime() + PROJECT_TIMEOUT < Date.now()) {
|
|
239
|
+
this._syncToServer().catch((error) => {
|
|
240
|
+
this._logger.error(error);
|
|
241
|
+
this._failedSyncAttempts++;
|
|
242
|
+
if (this._failedSyncAttempts > MAX_FAILED_SYNC_ATTEMPTS) {
|
|
243
|
+
this._logger.error(
|
|
244
|
+
`Failed to sync project data after ${MAX_FAILED_SYNC_ATTEMPTS} attempts. Stopping further attempts.`
|
|
245
|
+
);
|
|
246
|
+
clearInterval(this._timeout!);
|
|
247
|
+
this._timeout = null;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Sync project data with the data received from the REST API.
|
|
255
|
+
* @internal
|
|
256
|
+
*/
|
|
257
|
+
async _syncToServer() {
|
|
258
|
+
const data = await this._api.get(this.id);
|
|
259
|
+
const jobData = data.completedWorkerJobs.reduce((acc: Record<string, RawJob>, job) => {
|
|
260
|
+
const jobId = job.imgID || getUUID();
|
|
261
|
+
acc[jobId] = job;
|
|
262
|
+
return acc;
|
|
263
|
+
}, {});
|
|
264
|
+
for (const job of this._jobs) {
|
|
265
|
+
const restJob = jobData[job.id];
|
|
266
|
+
// This should never happen, but just in case we log a warning
|
|
267
|
+
if (!restJob) {
|
|
268
|
+
this._logger.warn(`Job with id ${job.id} not found in the REST project data`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
await job._syncWithRestData(restJob);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this._logger.error(error);
|
|
275
|
+
this._logger.error(`Failed to sync job ${job.id}`);
|
|
276
|
+
}
|
|
277
|
+
delete jobData[job.id];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// If there are any jobs left in jobData, it means they are new jobs that are not in the project yet
|
|
281
|
+
if (Object.keys(jobData).length) {
|
|
282
|
+
for (const job of Object.values(jobData)) {
|
|
283
|
+
const jobInstance = Job.fromRaw(data, job, { api: this._api, logger: this._logger });
|
|
284
|
+
this._addJob(jobInstance);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const delta: Partial<ProjectData> = {
|
|
289
|
+
params: {
|
|
290
|
+
...this.data.params,
|
|
291
|
+
numberOfImages: data.imageCount,
|
|
292
|
+
steps: data.stepCount,
|
|
293
|
+
numberOfPreviews: data.previewCount
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
if (PROJECT_STATUS_MAP[data.status]) {
|
|
297
|
+
delta.status = PROJECT_STATUS_MAP[data.status];
|
|
298
|
+
}
|
|
299
|
+
this._update(delta);
|
|
300
|
+
}
|
|
301
|
+
|
|
173
302
|
/**
|
|
174
303
|
* Get full project data snapshot. Can be used to serialize the project and store it in a database.
|
|
175
304
|
*/
|
|
@@ -67,7 +67,7 @@ function getTemplate() {
|
|
|
67
67
|
|
|
68
68
|
function createJobRequestMessage(id: string, params: ProjectParams) {
|
|
69
69
|
const template = getTemplate();
|
|
70
|
-
|
|
70
|
+
const jobRequest: Record<string, any> = {
|
|
71
71
|
...template,
|
|
72
72
|
keyFrames: [
|
|
73
73
|
{
|
|
@@ -92,6 +92,10 @@ function createJobRequestMessage(id: string, params: ProjectParams) {
|
|
|
92
92
|
jobID: id,
|
|
93
93
|
disableSafety: !!params.disableNSFWFilter
|
|
94
94
|
};
|
|
95
|
+
if (params.network) {
|
|
96
|
+
jobRequest.network = params.network;
|
|
97
|
+
}
|
|
98
|
+
return jobRequest;
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
export type JobRequestRaw = ReturnType<typeof createJobRequestMessage>;
|