@sogni-ai/sogni-client 3.0.0-alpha.2 → 3.0.0-alpha.20

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +3 -1
  3. package/dist/Account/CurrentAccount.d.ts +3 -3
  4. package/dist/Account/CurrentAccount.js +14 -4
  5. package/dist/Account/CurrentAccount.js.map +1 -1
  6. package/dist/Account/index.d.ts +6 -4
  7. package/dist/Account/index.js +8 -5
  8. package/dist/Account/index.js.map +1 -1
  9. package/dist/Account/types.d.ts +5 -0
  10. package/dist/ApiClient/WebSocketClient/events.d.ts +21 -7
  11. package/dist/ApiClient/WebSocketClient/index.js +13 -3
  12. package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
  13. package/dist/Projects/Job.d.ts +41 -0
  14. package/dist/Projects/Job.js +77 -1
  15. package/dist/Projects/Job.js.map +1 -1
  16. package/dist/Projects/Project.d.ts +1 -1
  17. package/dist/Projects/Project.js +22 -19
  18. package/dist/Projects/Project.js.map +1 -1
  19. package/dist/Projects/createJobRequestMessage.js +1 -1
  20. package/dist/Projects/createJobRequestMessage.js.map +1 -1
  21. package/dist/Projects/index.d.ts +5 -1
  22. package/dist/Projects/index.js +44 -8
  23. package/dist/Projects/index.js.map +1 -1
  24. package/dist/Projects/types/ControlNetParams.d.ts +1 -1
  25. package/dist/Projects/types/events.d.ts +6 -0
  26. package/dist/Projects/types/index.d.ts +7 -0
  27. package/dist/Projects/utils.d.ts +2 -0
  28. package/dist/Projects/utils.js +14 -0
  29. package/dist/Projects/utils.js.map +1 -0
  30. package/dist/Stats/types.d.ts +1 -1
  31. package/dist/types/token.d.ts +1 -0
  32. package/dist/types/token.js +3 -0
  33. package/dist/types/token.js.map +1 -0
  34. package/dist/version.js +1 -1
  35. package/package.json +1 -1
  36. package/src/Account/CurrentAccount.ts +16 -6
  37. package/src/Account/index.ts +15 -13
  38. package/src/Account/types.ts +7 -0
  39. package/src/ApiClient/WebSocketClient/events.ts +25 -8
  40. package/src/ApiClient/WebSocketClient/index.ts +13 -3
  41. package/src/Projects/Job.ts +97 -1
  42. package/src/Projects/Project.ts +24 -20
  43. package/src/Projects/createJobRequestMessage.ts +2 -1
  44. package/src/Projects/index.ts +44 -8
  45. package/src/Projects/types/ControlNetParams.ts +2 -1
  46. package/src/Projects/types/events.ts +6 -0
  47. package/src/Projects/types/index.ts +8 -0
  48. package/src/Projects/utils.ts +12 -0
  49. package/src/Stats/types.ts +2 -0
  50. package/src/types/token.ts +1 -0
  51. package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import DataEntity from '../lib/DataEntity';
2
- import { BalanceData } from './types';
2
+ import { Balances } from './types';
3
3
  import { SupernetType } from '../ApiClient/WebSocketClient/types';
4
4
  /**
5
5
  * @inline
@@ -16,7 +16,7 @@ export interface AccountData {
16
16
  */
17
17
  networkStatus: 'connected' | 'disconnected' | 'connecting' | 'switching';
18
18
  network: SupernetType | null;
19
- balance: BalanceData;
19
+ balance: Balances;
20
20
  walletAddress?: string;
21
21
  username?: string;
22
22
  token?: string;
@@ -28,10 +28,20 @@ function getDefaults(): AccountData {
28
28
  networkStatus: 'disconnected',
29
29
  network: null,
30
30
  balance: {
31
- credit: '0',
32
- debit: '0',
33
- net: '0',
34
- settled: '0'
31
+ sogni: {
32
+ credit: '0',
33
+ debit: '0',
34
+ net: '0',
35
+ settled: '0',
36
+ unclaimed: '0'
37
+ },
38
+ spark: {
39
+ credit: '0',
40
+ debit: '0',
41
+ net: '0',
42
+ settled: '0',
43
+ unclaimed: '0'
44
+ }
35
45
  },
36
46
  walletAddress: undefined,
37
47
  username: undefined,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AccountCreateData,
3
3
  AccountCreateParams,
4
- BalanceData,
4
+ Balances,
5
5
  LoginData,
6
6
  Nonce,
7
7
  Reward,
@@ -39,7 +39,7 @@ class AccountApi extends ApiGroup {
39
39
  this.client.auth.on('updated', this.handleAuthUpdated.bind(this));
40
40
  }
41
41
 
42
- private handleBalanceUpdate(data: BalanceData) {
42
+ private handleBalanceUpdate(data: Balances) {
43
43
  this.currentAccount._update({ balance: data });
44
44
  }
45
45
 
@@ -214,8 +214,8 @@ class AccountApi extends ApiGroup {
214
214
  * // { net: '100.000000', settled: '100.000000', credit: '0.000000', debit: '0.000000' }
215
215
  * ```
216
216
  */
217
- async refreshBalance(): Promise<BalanceData> {
218
- const res = await this.client.rest.get<ApiReponse<BalanceData>>('/v1/account/balance');
217
+ async refreshBalance(): Promise<Balances> {
218
+ const res = await this.client.rest.get<ApiReponse<Balances>>('/v3/account/balance');
219
219
  this.currentAccount._update({ balance: res.data });
220
220
  return res.data;
221
221
  }
@@ -236,12 +236,11 @@ class AccountApi extends ApiGroup {
236
236
  * @param walletAddress
237
237
  */
238
238
  async walletBalance(walletAddress: string) {
239
- const res = await this.client.rest.get<ApiReponse<{ token: string; ether: string }>>(
240
- '/v1/wallet/balance',
241
- {
242
- walletAddress
243
- }
244
- );
239
+ const res = await this.client.rest.get<
240
+ ApiReponse<{ sogni: string; spark: string; ether: string }>
241
+ >('/v2/wallet/balance', {
242
+ walletAddress
243
+ });
245
244
  return res.data;
246
245
  }
247
246
 
@@ -345,7 +344,7 @@ class AccountApi extends ApiGroup {
345
344
  */
346
345
  async rewards(): Promise<Reward[]> {
347
346
  const r =
348
- await this.client.rest.get<ApiReponse<{ rewards: RewardRaw[] }>>('/v2/account/rewards');
347
+ await this.client.rest.get<ApiReponse<{ rewards: RewardRaw[] }>>('/v3/account/rewards');
349
348
 
350
349
  return r.data.rewards.map(
351
350
  (raw: RewardRaw): Reward => ({
@@ -354,6 +353,7 @@ class AccountApi extends ApiGroup {
354
353
  title: raw.title,
355
354
  description: raw.description,
356
355
  amount: raw.amount,
356
+ tokenType: raw.tokenType,
357
357
  claimed: !!raw.claimed,
358
358
  canClaim: !!raw.canClaim,
359
359
  lastClaim: new Date(raw.lastClaimTimestamp * 1000),
@@ -369,10 +369,12 @@ class AccountApi extends ApiGroup {
369
369
  * Claim rewards by reward IDs.
370
370
  * @internal
371
371
  * @param rewardIds
372
+ * @param turnstileToken - Turnstile token, required to claim rewards
372
373
  */
373
- async claimRewards(rewardIds: string[]): Promise<void> {
374
+ async claimRewards(rewardIds: string[], turnstileToken?: string): Promise<void> {
374
375
  await this.client.rest.post('/v2/account/reward/claim', {
375
- claims: rewardIds
376
+ claims: rewardIds,
377
+ turnstileToken
376
378
  });
377
379
  }
378
380
 
@@ -1,3 +1,5 @@
1
+ import { TokenType } from '../types/token';
2
+
1
3
  export interface Nonce {
2
4
  nonce: string;
3
5
  }
@@ -27,8 +29,11 @@ export interface BalanceData {
27
29
  credit: string;
28
30
  debit: string;
29
31
  net: string;
32
+ unclaimed: string;
30
33
  }
31
34
 
35
+ export type Balances = Record<TokenType, BalanceData>;
36
+
32
37
  export interface TxHistoryParams {
33
38
  status: 'completed';
34
39
  address: string;
@@ -82,6 +87,7 @@ export interface RewardRaw {
82
87
  title: string;
83
88
  description: string;
84
89
  amount: string;
90
+ tokenType: TokenType;
85
91
  claimed: number;
86
92
  canClaim: number;
87
93
  lastClaimTimestamp: number;
@@ -94,6 +100,7 @@ export interface Reward {
94
100
  title: string;
95
101
  description: string;
96
102
  amount: string;
103
+ tokenType: TokenType;
97
104
  claimed: boolean;
98
105
  canClaim: boolean;
99
106
  lastClaim: Date;
@@ -1,11 +1,5 @@
1
1
  import { SupernetType } from './types';
2
-
3
- export type BalanceData = {
4
- settled: string;
5
- credit: string;
6
- debit: string;
7
- net: string;
8
- };
2
+ import { Balances } from '../../Account/types';
9
3
 
10
4
  export type JobErrorData = {
11
5
  jobID: string;
@@ -38,6 +32,9 @@ export type JobStateData =
38
32
  jobID: string;
39
33
  imgID: string;
40
34
  workerName: string;
35
+ positivePrompt?: string;
36
+ negativePrompt?: string;
37
+ jobIndex?: number;
41
38
  }
42
39
  | {
43
40
  jobID: string;
@@ -58,11 +55,25 @@ export type ServerDisconnectData = {
58
55
  reason: string;
59
56
  };
60
57
 
58
+ export type ToastMessage = {
59
+ type: 'info' | 'success' | 'warning' | 'error';
60
+ message: string;
61
+ // Number of milliseconds to show the toast
62
+ autoClose: number;
63
+ stickyID: string;
64
+ };
65
+
66
+ export type ArtistCancelConfirmation = {
67
+ didCancel: boolean;
68
+ error_message: string;
69
+ jobID: string;
70
+ };
71
+
61
72
  export type SocketEventMap = {
62
73
  /**
63
74
  * @event WebSocketClient#balanceUpdate - Received balance update
64
75
  */
65
- balanceUpdate: BalanceData;
76
+ balanceUpdate: Balances;
66
77
  /**
67
78
  * @event WebSocketClient#changeNetwork - Default network changed
68
79
  */
@@ -95,4 +106,10 @@ export type SocketEventMap = {
95
106
  * @event WebSocketClient#disconnected - WebSocket connection was closed
96
107
  */
97
108
  disconnected: ServerDisconnectData;
109
+ /**
110
+ * @event WebSocketClient#toastMessage - Toast message received
111
+ */
112
+ toastMessage: ToastMessage;
113
+
114
+ artistCancelConfirmation: ArtistCancelConfirmation;
98
115
  };
@@ -28,8 +28,17 @@ class WebSocketClient extends RestClient<SocketEventMap> {
28
28
  logger: Logger
29
29
  ) {
30
30
  const _baseUrl = new URL(baseUrl);
31
- if (_baseUrl.protocol === 'wss:') {
32
- _baseUrl.protocol = 'https:';
31
+ switch (_baseUrl.protocol) {
32
+ case 'http:':
33
+ case 'ws:':
34
+ _baseUrl.protocol = 'http:';
35
+ break;
36
+ case 'https:':
37
+ case 'wss:':
38
+ _baseUrl.protocol = 'https:';
39
+ break;
40
+ default:
41
+ _baseUrl.protocol = 'https:';
33
42
  }
34
43
  super(_baseUrl.toString(), auth, logger);
35
44
  this.appId = appId;
@@ -51,7 +60,8 @@ class WebSocketClient extends RestClient<SocketEventMap> {
51
60
  }
52
61
  const userAgent = `Sogni/${PROTOCOL_VERSION} (sogni-client) ${LIB_VERSION}`;
53
62
  const url = new URL(this.baseUrl);
54
- url.protocol = 'wss:';
63
+ const isNotSecure = url.protocol === 'http:' || url.protocol === 'ws:';
64
+ url.protocol = isNotSecure ? 'ws:' : 'wss:';
55
65
  url.searchParams.set('appId', this.appId);
56
66
  url.searchParams.set('clientName', userAgent);
57
67
  url.searchParams.set('clientType', 'artist');
@@ -4,6 +4,23 @@ import { RawJob, RawProject } from './types/RawProject';
4
4
  import ProjectsApi from './index';
5
5
  import { Logger } from '../lib/DefaultLogger';
6
6
  import getUUID from '../lib/getUUID';
7
+ import { EnhancementStrength } from './types';
8
+ import Project from './Project';
9
+ import { SupernetType } from '../ApiClient/WebSocketClient/types';
10
+ import { getEnhacementStrength } from './utils';
11
+
12
+ export const enhancementDefaults = {
13
+ network: 'fast' as SupernetType,
14
+ modelId: 'flux1-schnell-fp8',
15
+ positivePrompt: '',
16
+ negativePrompt: '',
17
+ stylePrompt: '',
18
+ startingImageStrength: 0.5,
19
+ steps: 5,
20
+ guidance: 1,
21
+ numberOfImages: 1,
22
+ numberOfPreviews: 0
23
+ };
7
24
 
8
25
  export type JobStatus =
9
26
  | 'pending'
@@ -40,6 +57,9 @@ export interface JobData {
40
57
  previewUrl?: string;
41
58
  resultUrl?: string | null;
42
59
  error?: ErrorData;
60
+ positivePrompt?: string;
61
+ negativePrompt?: string;
62
+ jobIndex?: number;
43
63
  }
44
64
 
45
65
  export interface JobEventMap extends EntityEvents {
@@ -51,6 +71,7 @@ export interface JobEventMap extends EntityEvents {
51
71
  export interface JobOptions {
52
72
  api: ProjectsApi;
53
73
  logger: Logger;
74
+ project: Project;
54
75
  }
55
76
 
56
77
  class Job extends DataEntity<JobData, JobEventMap> {
@@ -72,14 +93,18 @@ class Job extends DataEntity<JobData, JobEventMap> {
72
93
 
73
94
  private readonly _api: ProjectsApi;
74
95
  private readonly _logger: Logger;
96
+ private readonly _project: Project;
97
+ private _enhancementProject: Project | null = null;
75
98
 
76
99
  constructor(data: JobData, options: JobOptions) {
77
100
  super(data);
78
101
 
79
102
  this._api = options.api;
80
103
  this._logger = options.logger;
104
+ this._project = options.project;
81
105
 
82
106
  this.on('updated', this.handleUpdated.bind(this));
107
+ this.handleEnhancementUpdate = this.handleEnhancementUpdate.bind(this);
83
108
  }
84
109
 
85
110
  get id() {
@@ -152,12 +177,31 @@ class Job extends DataEntity<JobData, JobEventMap> {
152
177
  return this.data.error;
153
178
  }
154
179
 
180
+ get hasResultImage() {
181
+ return this.status === 'completed' && !this.isNSFW;
182
+ }
183
+
184
+ get enhancedImage() {
185
+ if (!this._enhancementProject) {
186
+ return null;
187
+ }
188
+ const project = this._enhancementProject;
189
+ const job = project.jobs[0];
190
+ return {
191
+ status: project.status,
192
+ progress: project.progress,
193
+ result: job?.resultUrl || null,
194
+ error: project.error,
195
+ getResultUrl: () => job?.getResultUrl()
196
+ };
197
+ }
198
+
155
199
  /**
156
200
  * Get the result URL of the job. This method will make a request to the API to get signed URL.
157
201
  * IMPORTANT: URL expires after 30 minutes, so make sure to download the image as soon as possible.
158
202
  */
159
203
  async getResultUrl(): Promise<string> {
160
- if (this.data.status === 'completed') {
204
+ if (this.data.status !== 'completed') {
161
205
  throw new Error('Job is not completed yet');
162
206
  }
163
207
  const url = await this._api.downloadUrl({
@@ -225,6 +269,58 @@ class Job extends DataEntity<JobData, JobEventMap> {
225
269
  this.emit('failed', this.data.error!);
226
270
  }
227
271
  }
272
+
273
+ private handleEnhancementUpdate() {
274
+ this.emit('updated', ['enhancedImage']);
275
+ }
276
+
277
+ async getResultData() {
278
+ if (!this.hasResultImage) {
279
+ throw new Error('No result image available');
280
+ }
281
+ const url = await this.getResultUrl();
282
+ const response = await fetch(url);
283
+ if (!response.ok) {
284
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
285
+ }
286
+ return response.blob();
287
+ }
288
+
289
+ /**
290
+ * Enhance the image using the Flux model. This method will create a new project with the
291
+ * enhancement parameters and use the result image of the current job as the starting image.
292
+ * @param strength - how much freedom the model has to change the image.
293
+ * @param overrides - optional parameters to override original prompt or style.
294
+ */
295
+ async enhance(
296
+ strength: EnhancementStrength,
297
+ overrides: { positivePrompt?: string; stylePrompt?: string } = {}
298
+ ) {
299
+ if (this.status !== 'completed') {
300
+ throw new Error('Job is not completed yet');
301
+ }
302
+ if (this.isNSFW) {
303
+ throw new Error('Job did not pass NSFW filter');
304
+ }
305
+ if (this._enhancementProject) {
306
+ this._enhancementProject.off('updated', this.handleEnhancementUpdate);
307
+ this._enhancementProject = null;
308
+ }
309
+ const imageData = await this.getResultData();
310
+ const project = await this._api.create({
311
+ ...enhancementDefaults,
312
+ positivePrompt: overrides.positivePrompt || this._project.params.positivePrompt,
313
+ stylePrompt: overrides.stylePrompt || this._project.params.stylePrompt,
314
+ seed: this.seed || this._project.params.seed,
315
+ startingImage: imageData,
316
+ startingImageStrength: 1 - getEnhacementStrength(strength),
317
+ sizePreset: this._project.params.sizePreset
318
+ });
319
+ this._enhancementProject = project;
320
+ this._enhancementProject.on('updated', this.handleEnhancementUpdate);
321
+ const images = await project.waitForCompletion();
322
+ return images[0];
323
+ }
228
324
  }
229
325
 
230
326
  export default Job;
@@ -1,4 +1,4 @@
1
- import Job, { JobData, JobStatus } from './Job';
1
+ import Job, { JobData } from './Job';
2
2
  import DataEntity, { EntityEvents } from '../lib/DataEntity';
3
3
  import { ProjectParams } from './types';
4
4
  import cloneDeep from 'lodash/cloneDeep';
@@ -50,6 +50,7 @@ export interface ProjectEventMap extends EntityEvents {
50
50
  progress: number;
51
51
  completed: string[];
52
52
  failed: ErrorData;
53
+ jobStarted: Job;
53
54
  jobCompleted: Job;
54
55
  jobFailed: Job;
55
56
  }
@@ -209,48 +210,47 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
209
210
  */
210
211
  _addJob(data: JobData | Job) {
211
212
  const job =
212
- data instanceof Job ? data : new Job(data, { api: this._api, logger: this._logger });
213
+ data instanceof Job
214
+ ? data
215
+ : new Job(data, { api: this._api, logger: this._logger, project: this });
213
216
  this._jobs.push(job);
214
217
  job.on('updated', () => {
215
218
  this.lastUpdated = new Date();
216
219
  this.emit('updated', ['jobs']);
217
220
  });
221
+ this.emit('jobStarted', job);
218
222
  job.on('completed', () => {
219
223
  this.emit('jobCompleted', job);
220
- this._handleJobFinished(job);
221
224
  });
222
225
  job.on('failed', () => {
223
226
  this.emit('jobFailed', job);
224
- this._handleJobFinished(job);
225
227
  });
226
228
  return job;
227
229
  }
228
230
 
229
- private _handleJobFinished(job: Job) {
230
- const finalStatus: JobStatus[] = ['completed', 'failed', 'canceled'];
231
- const allJobsDone = this.jobs.every((job) => finalStatus.includes(job.status));
232
- // If all jobs are done and project is not already failed or completed, update the project status
233
- if (allJobsDone && this.status !== 'failed' && this.status !== 'completed') {
234
- const allJobsFailed = this.jobs.every((job) => job.status === 'failed');
235
- if (allJobsFailed) {
236
- this._update({ status: 'failed' });
237
- } else {
238
- this._update({ status: 'completed' });
239
- }
240
- }
241
- }
242
-
243
231
  private _checkForTimeout() {
244
232
  if (this.lastUpdated.getTime() + PROJECT_TIMEOUT < Date.now()) {
245
233
  this._syncToServer().catch((error) => {
246
234
  this._logger.error(error);
247
235
  this._failedSyncAttempts++;
248
- if (this._failedSyncAttempts > MAX_FAILED_SYNC_ATTEMPTS) {
236
+ if (this._failedSyncAttempts >= MAX_FAILED_SYNC_ATTEMPTS) {
249
237
  this._logger.error(
250
238
  `Failed to sync project data after ${MAX_FAILED_SYNC_ATTEMPTS} attempts. Stopping further attempts.`
251
239
  );
252
240
  clearInterval(this._timeout!);
253
241
  this._timeout = null;
242
+ this.jobs.forEach((job) => {
243
+ if (!job.finished) {
244
+ job._update({
245
+ status: 'failed',
246
+ error: { code: 0, message: 'Job timed out' }
247
+ });
248
+ }
249
+ });
250
+ this._update({
251
+ status: 'failed',
252
+ error: { code: 0, message: 'Project timed out. Please try again or contact support.' }
253
+ });
254
254
  }
255
255
  });
256
256
  }
@@ -286,7 +286,11 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
286
286
  // If there are any jobs left in jobData, it means they are new jobs that are not in the project yet
287
287
  if (Object.keys(jobData).length) {
288
288
  for (const job of Object.values(jobData)) {
289
- const jobInstance = Job.fromRaw(data, job, { api: this._api, logger: this._logger });
289
+ const jobInstance = Job.fromRaw(data, job, {
290
+ api: this._api,
291
+ logger: this._logger,
292
+ project: this
293
+ });
290
294
  this._addJob(jobInstance);
291
295
  }
292
296
  }
@@ -119,7 +119,8 @@ function createJobRequestMessage(id: string, params: ProjectParams) {
119
119
  keyFrames: [
120
120
  {
121
121
  ...template.keyFrames[0],
122
- scheduler: params.scheduler,
122
+ scheduler: params.scheduler || null,
123
+ timeStepSpacing: params.timeStepSpacing || null,
123
124
  steps: params.steps,
124
125
  guidanceScale: params.guidance,
125
126
  modelID: params.modelId,
@@ -1,6 +1,7 @@
1
1
  import ApiGroup, { ApiConfig } from '../ApiGroup';
2
2
  import {
3
3
  AvailableModel,
4
+ EnhancementStrength,
4
5
  EstimateRequest,
5
6
  ImageUrlParams,
6
7
  ProjectParams,
@@ -24,9 +25,11 @@ import { RawProject } from './types/RawProject';
24
25
  import ErrorData from '../types/ErrorData';
25
26
  import { SupernetType } from '../ApiClient/WebSocketClient/types';
26
27
  import Cache from '../lib/Cache';
28
+ import { enhancementDefaults } from './Job';
29
+ import { getEnhacementStrength } from './utils';
27
30
 
28
31
  const sizePresetCache = new Cache<SizePreset[]>(10 * 60 * 1000);
29
- const GARBAGE_COLLECT_TIMEOUT = 10000;
32
+ const GARBAGE_COLLECT_TIMEOUT = 30000;
30
33
  const MODELS_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 24 hours
31
34
 
32
35
  function mapErrorCodes(code: string): number {
@@ -67,7 +70,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
67
70
  this.client.socket.on('jobProgress', this.handleJobProgress.bind(this));
68
71
  this.client.socket.on('jobError', this.handleJobError.bind(this));
69
72
  this.client.socket.on('jobResult', this.handleJobResult.bind(this));
70
- // Listen to server disconnect event
73
+ // Listen to the server disconnect event
71
74
  this.client.on('disconnected', this.handleServerDisconnected.bind(this));
72
75
  // Listen to project and job events and update project and job instances
73
76
  this.on('project', this.handleProjectEvent.bind(this));
@@ -115,7 +118,10 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
115
118
  type: 'initiating',
116
119
  projectId: data.jobID,
117
120
  jobId: data.imgID,
118
- workerName: data.workerName
121
+ workerName: data.workerName,
122
+ positivePrompt: data.positivePrompt,
123
+ negativePrompt: data.negativePrompt,
124
+ jobIndex: data.jobIndex
119
125
  });
120
126
  return;
121
127
  case 'jobStarted': {
@@ -123,7 +129,10 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
123
129
  type: 'started',
124
130
  projectId: data.jobID,
125
131
  jobId: data.imgID,
126
- workerName: data.workerName
132
+ workerName: data.workerName,
133
+ positivePrompt: data.positivePrompt,
134
+ negativePrompt: data.negativePrompt,
135
+ jobIndex: data.jobIndex
127
136
  });
128
137
  return;
129
138
  }
@@ -241,7 +250,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
241
250
  this.client.logger.error(e);
242
251
  });
243
252
  setTimeout(() => {
244
- this.projects = this.projects.filter((p) => p.id !== event.projectId);
253
+ this.projects = this.projects.filter((p) => !p.finished);
245
254
  }, GARBAGE_COLLECT_TIMEOUT);
246
255
  }
247
256
  }
@@ -263,15 +272,30 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
263
272
  }
264
273
  switch (event.type) {
265
274
  case 'initiating':
266
- job._update({ status: 'initiating', workerName: event.workerName });
275
+ // positivePrompt and negativePrompt are only received if a Dynamic Prompt was used for the project creating a different prompt for each job
276
+ job._update({
277
+ status: 'initiating',
278
+ workerName: event.workerName,
279
+ positivePrompt: event.positivePrompt,
280
+ negativePrompt: event.negativePrompt,
281
+ jobIndex: event.jobIndex
282
+ });
267
283
  break;
268
284
  case 'started':
269
- job._update({ status: 'processing', workerName: event.workerName });
285
+ // positivePrompt and negativePrompt are only received if a Dynamic Prompt was used for the project creating a different prompt for each job
286
+ job._update({
287
+ status: 'processing',
288
+ workerName: event.workerName,
289
+ positivePrompt: event.positivePrompt,
290
+ negativePrompt: event.negativePrompt,
291
+ jobIndex: event.jobIndex
292
+ });
270
293
  break;
271
294
  case 'progress':
272
295
  job._update({
273
296
  status: 'processing',
274
- step: event.step,
297
+ // Jus in case event comes out of order
298
+ step: Math.max(event.step, job.step),
275
299
  stepCount: event.stepCount
276
300
  });
277
301
  if (project.status !== 'processing') {
@@ -479,6 +503,18 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
479
503
  };
480
504
  }
481
505
 
506
+ async estimateEnhancementCost(strength: EnhancementStrength) {
507
+ return this.estimateCost({
508
+ network: enhancementDefaults.network,
509
+ model: enhancementDefaults.modelId,
510
+ imageCount: 1,
511
+ stepCount: enhancementDefaults.steps,
512
+ previewCount: 0,
513
+ cnEnabled: false,
514
+ startingImageStrength: getEnhacementStrength(strength)
515
+ });
516
+ }
517
+
482
518
  /**
483
519
  * Get upload URL for image
484
520
  * @internal
@@ -16,7 +16,8 @@ export type ControlNetName =
16
16
  | 'segmentation'
17
17
  | 'shuffle'
18
18
  | 'softedge'
19
- | 'tile';
19
+ | 'tile'
20
+ | 'instantid';
20
21
 
21
22
  /**
22
23
  * Raw ControlNet parameters passed to the API
@@ -29,11 +29,17 @@ export interface JobEventBase {
29
29
  export interface JobInitiating extends JobEventBase {
30
30
  type: 'initiating';
31
31
  workerName: string;
32
+ positivePrompt?: string;
33
+ negativePrompt?: string;
34
+ jobIndex?: number;
32
35
  }
33
36
 
34
37
  export interface JobStarted extends JobEventBase {
35
38
  type: 'started';
36
39
  workerName: string;
40
+ positivePrompt?: string;
41
+ negativePrompt?: string;
42
+ jobIndex?: number;
37
43
  }
38
44
 
39
45
  export interface JobProgress extends JobEventBase {
@@ -1,5 +1,6 @@
1
1
  import { SupernetType } from '../../ApiClient/WebSocketClient/types';
2
2
  import { ControlNetParams } from './ControlNetParams';
3
+ import { TokenType } from '../../types/token';
3
4
 
4
5
  export interface SupportedModel {
5
6
  id: string;
@@ -141,6 +142,11 @@ export interface ProjectParams {
141
142
  * ControlNet model parameters
142
143
  */
143
144
  controlNet?: ControlNetParams;
145
+ /**
146
+ * Select which tokens to use for the project.
147
+ * If not specified, the Sogni token will be used.
148
+ */
149
+ tokenType?: TokenType;
144
150
  }
145
151
 
146
152
  export type ImageUrlParams = {
@@ -195,3 +201,5 @@ export interface EstimateRequest {
195
201
  */
196
202
  height?: number;
197
203
  }
204
+
205
+ export type EnhancementStrength = 'light' | 'medium' | 'heavy';
@@ -0,0 +1,12 @@
1
+ import { EnhancementStrength } from './types';
2
+
3
+ export function getEnhacementStrength(strength: EnhancementStrength): number {
4
+ switch (strength) {
5
+ case 'light':
6
+ return 0.15;
7
+ case 'heavy':
8
+ return 0.49;
9
+ default:
10
+ return 0.35;
11
+ }
12
+ }
@@ -4,7 +4,9 @@ export type LeaderboardType =
4
4
  | 'renderSecCompleteWorker'
5
5
  | 'renderSecCompleteArtist'
6
6
  | 'renderTokenCompleteWorker'
7
+ | 'renderTokenCompleteWorker2'
7
8
  | 'renderTokenCompleteArtist'
9
+ | 'renderTokenCompleteArtist2'
8
10
  | 'jobCompleteWorker'
9
11
  | 'jobCompleteArtist'
10
12
  | 'projectCompleteArtist'