@mediaviz/sdk 1.3.0 → 1.5.0-dev.81

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/sdk.cjs CHANGED
@@ -91,8 +91,15 @@ function requireErrors () {
91
91
  * @returns {OAuthError}
92
92
  */
93
93
  static fromResponse(status, body) {
94
- if (body && typeof body === 'object' && typeof body.error === 'string') {
95
- return new OAuthError(body.error, body.error_description ?? '', status, body);
94
+ // FastAPI wraps HTTPException payloads in a `detail` envelope, so an OAuth
95
+ // error arrives as { detail: { error, error_description } }. Unwrap it
96
+ // before falling back, or every server-side OAuth error reads as server_error.
97
+ const payload =
98
+ body && typeof body === 'object' && typeof body.error !== 'string' && body.detail
99
+ ? body.detail
100
+ : body;
101
+ if (payload && typeof payload === 'object' && typeof payload.error === 'string') {
102
+ return new OAuthError(payload.error, payload.error_description ?? '', status, body);
96
103
  }
97
104
  return new OAuthError('server_error', 'Unexpected server response', status, body);
98
105
  }
@@ -173,6 +180,9 @@ function requireClient () {
173
180
  const { postForm, postJson } = requireHttp();
174
181
  const { OAuthError } = requireErrors();
175
182
 
183
+ // In-flight dedup key for client_credentials re-mints (no refresh_token to key on).
184
+ const CLIENT_CREDENTIALS_KEY = Symbol('client_credentials');
185
+
176
186
  class OAuthClient {
177
187
  /**
178
188
  * @param {import('./types').OAuthClientConfig} config
@@ -188,12 +198,22 @@ function requireClient () {
188
198
  */
189
199
  static async registerClient(params) {
190
200
  const baseUrl = params.baseUrl.replace(/\/+$/, '');
191
- return postJson(`${baseUrl}/oauth/clients`, {
201
+ const body = {
192
202
  client_name: params.clientName,
193
203
  client_type: params.clientType,
194
204
  redirect_uris: params.redirectUris,
195
205
  is_first_party: params.isFirstParty,
196
- });
206
+ };
207
+ // grant_types/company_id are required to provision a confidential
208
+ // client_credentials (machine-to-machine) client; omitted bodies keep the
209
+ // legacy public/authorization_code shape. The endpoint is admin-gated, so a
210
+ // bearer token must be supplied for the request to authenticate.
211
+ if (params.grantTypes !== undefined) body.grant_types = params.grantTypes;
212
+ if (params.companyId !== undefined) body.company_id = params.companyId;
213
+ const headers = params.accessToken
214
+ ? { Authorization: `Bearer ${params.accessToken}` }
215
+ : {};
216
+ return postJson(`${baseUrl}/oauth/clients`, body, headers);
197
217
  }
198
218
 
199
219
  /**
@@ -311,15 +331,27 @@ function requireClient () {
311
331
  return { data: json, updatedTokens: null };
312
332
  }
313
333
 
314
- // 401: attempt refresh — propagate OAuthError if refresh fails.
315
- // Concurrent request() callers with the same refresh_token share one in-flight
316
- // call so the server only sees one rotation (the second would get invalid_grant).
317
- let pending = this._inFlightRefreshes.get(refreshToken);
334
+ // 401: re-authenticate, then retry once.
335
+ // - User sessions (refresh_token present) use the refresh_token grant. The server
336
+ // rotates the token one-time, so concurrent callers share one in-flight call
337
+ // (a second rotation would get invalid_grant).
338
+ // - Machine-to-machine sessions (no refresh_token, client_secret configured) re-mint
339
+ // via client_credentials, which issues no refresh token to rotate (RFC 6749 §4.4).
340
+ // - With neither a refresh_token nor a client_secret there is nothing to retry with.
341
+ if (!refreshToken && !this._config.clientSecret) {
342
+ throw OAuthError.fromResponse(401, await firstResponse.json());
343
+ }
344
+
345
+ const reauthKey = refreshToken || CLIENT_CREDENTIALS_KEY;
346
+ let pending = this._inFlightRefreshes.get(reauthKey);
318
347
  if (!pending) {
319
- pending = this.refreshAccessToken(refreshToken).finally(() => {
320
- this._inFlightRefreshes.delete(refreshToken);
348
+ const reauth = refreshToken
349
+ ? this.refreshAccessToken(refreshToken)
350
+ : this.getClientCredentialsToken();
351
+ pending = reauth.finally(() => {
352
+ this._inFlightRefreshes.delete(reauthKey);
321
353
  });
322
- this._inFlightRefreshes.set(refreshToken, pending);
354
+ this._inFlightRefreshes.set(reauthKey, pending);
323
355
  }
324
356
  const newTokens = await pending;
325
357
 
@@ -401,6 +433,9 @@ function requireTypes () {
401
433
  * @property {string} clientType - "public" or "confidential"
402
434
  * @property {string[]} redirectUris - HTTPS redirect URIs
403
435
  * @property {boolean} isFirstParty - Whether this is a first-party client
436
+ * @property {string[]} [grantTypes] - Allowed grants, e.g. ["client_credentials"]; required for machine-to-machine clients
437
+ * @property {number} [companyId] - Company to bind the client to; required for the client_credentials grant
438
+ * @property {string} [accessToken] - Admin bearer token; the registration endpoint is authorization-gated
404
439
  */
405
440
 
406
441
  /**
@@ -912,6 +947,17 @@ class Keywords {
912
947
  return data;
913
948
  }
914
949
 
950
+ async addCompanyToKeywordFilteringList(keywordListId, { companyId } = {}) {
951
+ this._ctx.requireTokens();
952
+ let path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}/company`;
953
+ const query = new URLSearchParams();
954
+ if (companyId !== undefined) (Array.isArray(companyId) ? companyId : [companyId]).forEach(v => query.append('company_id', v));
955
+ const qs = query.toString();
956
+ if (qs) path += '?' + qs;
957
+ const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken);
958
+ return data;
959
+ }
960
+
915
961
  async requestKeywordListExport(keywordListId) {
916
962
  this._ctx.requireTokens();
917
963
  const path = `/api/v1/keyword/export/${encodeURIComponent(keywordListId)}`;
@@ -944,6 +990,17 @@ class Keywords {
944
990
  return data;
945
991
  }
946
992
 
993
+ async removeCompanyFromKeywordFilteringList(keywordListId, { companyId } = {}) {
994
+ this._ctx.requireTokens();
995
+ let path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}/company`;
996
+ const query = new URLSearchParams();
997
+ if (companyId !== undefined) (Array.isArray(companyId) ? companyId : [companyId]).forEach(v => query.append('company_id', v));
998
+ const qs = query.toString();
999
+ if (qs) path += '?' + qs;
1000
+ const { data } = await this._ctx.client.request(path, 'DELETE', this._ctx.accessToken, this._ctx.refreshToken);
1001
+ return data;
1002
+ }
1003
+
947
1004
  async deleteKeywordFilteringListById(keywordListId) {
948
1005
  this._ctx.requireTokens();
949
1006
  const path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}`;
@@ -1103,6 +1160,17 @@ class Person {
1103
1160
  return data;
1104
1161
  }
1105
1162
 
1163
+ async getPhotoIdsByPersons(projectTableName, { personId } = {}) {
1164
+ this._ctx.requireTokens();
1165
+ let path = `/api/v1/person/${encodeURIComponent(projectTableName)}/photos`;
1166
+ const query = new URLSearchParams();
1167
+ if (personId !== undefined) (Array.isArray(personId) ? personId : [personId]).forEach(v => query.append('person_id', v));
1168
+ const qs = query.toString();
1169
+ if (qs) path += '?' + qs;
1170
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1171
+ return data;
1172
+ }
1173
+
1106
1174
  async getAllPersonsFromPhoto(projectTableName, photoId) {
1107
1175
  this._ctx.requireTokens();
1108
1176
  const path = `/api/v1/person/${encodeURIComponent(projectTableName)}/photo/${encodeURIComponent(photoId)}`;
@@ -1336,7 +1404,7 @@ class Photoupload {
1336
1404
  this._caches['_get_template'].set(_cacheKey_get_template, template);
1337
1405
  }
1338
1406
 
1339
- const upload_result = await this.uploadPhotoToMediaviz(companyId, userId, projectTableName, photo.title, { fileContent: photo.fileContent, mimetype: photo.mimetype, filePath: photo.filePath }, { clientSideId: photo.clientSideId, blur: photo.blur, colors: photo.colors, faceRecognition: photo.faceRecognition, imageDescribe: photo.imageDescribe, imageClassification: photo.imageClassification, imageComparison: photo.imageComparison, size: photo.size, sourceResolutionX: photo.sourceResolutionX, sourceResolutionY: photo.sourceResolutionY, dateTaken: photo.dateTaken, latitude: photo.latitude, longitude: photo.longitude });
1407
+ const upload_result = await this.uploadPhotoToMediaviz(companyId, userId, projectTableName, photo.title, { fileContent: photo.fileContent, mimetype: photo.mimetype, filePath: photo.filePath }, { clientSideId: photo.clientSideId, blur: template?.blur, colors: template?.colors, faceRecognition: template?.face_recognition, imageDescribe: template?.image_describe, imageClassification: template?.image_classification, imageComparison: template?.image_comparison, size: photo.size, sourceResolutionX: photo.sourceResolutionX, sourceResolutionY: photo.sourceResolutionY, dateTaken: photo.dateTaken, latitude: photo.latitude, longitude: photo.longitude, ocr: template?.ocr });
1340
1408
 
1341
1409
  return upload_result;
1342
1410
  }
@@ -1403,13 +1471,6 @@ class Photos {
1403
1471
  return data;
1404
1472
  }
1405
1473
 
1406
- async getProjectThumbnail(tableName) {
1407
- this._ctx.requireTokens();
1408
- const path = `/api/v1/photos_project/${encodeURIComponent(tableName)}`;
1409
- const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1410
- return data;
1411
- }
1412
-
1413
1474
  async updatePhotoInProject({ tableName, photoId, photoData } = {}) {
1414
1475
  this._ctx.requireTokens();
1415
1476
  let path = `/api/v1/photos_update`;
@@ -1472,50 +1533,56 @@ class Search {
1472
1533
  return data;
1473
1534
  }
1474
1535
 
1475
- async searchProjectPhotosText(projectTableName, searchText, size = undefined) {
1536
+ async searchProjectPhotosParametrized(projectTableName, { andSearchText, orSearchText, notSearchText, city, country, state, albums, dateMin, dateMax, dateNullAnd, dateNullOr, ascOrDesc, bestOfSimilarSetsOnly, splitByTier, lastId, limit } = {}) {
1476
1537
  this._ctx.requireTokens();
1477
- const path = `/api/v1/search/text/${encodeURIComponent(projectTableName)}/`;
1478
- const body = stripUndef$1({
1479
- search_text: searchText,
1480
- size: size,
1481
- });
1482
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1483
- return data;
1484
- }
1485
-
1486
- async searchProjectPhotosNaturalLanguage(projectTableName, searchText, size = undefined) {
1487
- this._ctx.requireTokens();
1488
- const path = `/api/v1/search/nl/${encodeURIComponent(projectTableName)}/`;
1489
- const body = stripUndef$1({
1490
- search_text: searchText,
1491
- size: size,
1492
- });
1493
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1538
+ let path = `/api/v1/search/parametrized/${encodeURIComponent(projectTableName)}/`;
1539
+ const query = new URLSearchParams();
1540
+ if (andSearchText !== undefined) (Array.isArray(andSearchText) ? andSearchText : [andSearchText]).forEach(v => query.append('and_search_text', v));
1541
+ if (orSearchText !== undefined) (Array.isArray(orSearchText) ? orSearchText : [orSearchText]).forEach(v => query.append('or_search_text', v));
1542
+ if (notSearchText !== undefined) (Array.isArray(notSearchText) ? notSearchText : [notSearchText]).forEach(v => query.append('not_search_text', v));
1543
+ if (city !== undefined) (Array.isArray(city) ? city : [city]).forEach(v => query.append('city', v));
1544
+ if (country !== undefined) (Array.isArray(country) ? country : [country]).forEach(v => query.append('country', v));
1545
+ if (state !== undefined) (Array.isArray(state) ? state : [state]).forEach(v => query.append('state', v));
1546
+ if (albums !== undefined) (Array.isArray(albums) ? albums : [albums]).forEach(v => query.append('albums', v));
1547
+ if (dateMin !== undefined) (Array.isArray(dateMin) ? dateMin : [dateMin]).forEach(v => query.append('date_min', v));
1548
+ if (dateMax !== undefined) (Array.isArray(dateMax) ? dateMax : [dateMax]).forEach(v => query.append('date_max', v));
1549
+ if (dateNullAnd !== undefined) (Array.isArray(dateNullAnd) ? dateNullAnd : [dateNullAnd]).forEach(v => query.append('date_null_and', v));
1550
+ if (dateNullOr !== undefined) (Array.isArray(dateNullOr) ? dateNullOr : [dateNullOr]).forEach(v => query.append('date_null_or', v));
1551
+ if (ascOrDesc !== undefined) (Array.isArray(ascOrDesc) ? ascOrDesc : [ascOrDesc]).forEach(v => query.append('asc_or_desc', v));
1552
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1553
+ if (splitByTier !== undefined) (Array.isArray(splitByTier) ? splitByTier : [splitByTier]).forEach(v => query.append('split_by_tier', v));
1554
+ if (lastId !== undefined) (Array.isArray(lastId) ? lastId : [lastId]).forEach(v => query.append('last_id', v));
1555
+ if (limit !== undefined) (Array.isArray(limit) ? limit : [limit]).forEach(v => query.append('limit', v));
1556
+ const qs = query.toString();
1557
+ if (qs) path += '?' + qs;
1558
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1494
1559
  return data;
1495
1560
  }
1496
1561
 
1497
- async searchProjectPhotosNaturalLanguageHybrid(projectTableName, searchText, size = undefined, { blend, minCosine } = {}) {
1562
+ async recallProjectPhotos(projectTableName, { ascOrDesc, bestOfSimilarSetsOnly, lastId, limit } = {}) {
1498
1563
  this._ctx.requireTokens();
1499
- let path = `/api/v1/search/nl_hybrid/${encodeURIComponent(projectTableName)}/`;
1564
+ let path = `/api/v1/search/recall/${encodeURIComponent(projectTableName)}/`;
1500
1565
  const query = new URLSearchParams();
1501
- if (blend !== undefined) (Array.isArray(blend) ? blend : [blend]).forEach(v => query.append('blend', v));
1502
- if (minCosine !== undefined) (Array.isArray(minCosine) ? minCosine : [minCosine]).forEach(v => query.append('min_cosine', v));
1566
+ if (ascOrDesc !== undefined) (Array.isArray(ascOrDesc) ? ascOrDesc : [ascOrDesc]).forEach(v => query.append('asc_or_desc', v));
1567
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1568
+ if (lastId !== undefined) (Array.isArray(lastId) ? lastId : [lastId]).forEach(v => query.append('last_id', v));
1569
+ if (limit !== undefined) (Array.isArray(limit) ? limit : [limit]).forEach(v => query.append('limit', v));
1503
1570
  const qs = query.toString();
1504
1571
  if (qs) path += '?' + qs;
1505
- const body = stripUndef$1({
1506
- search_text: searchText,
1507
- size: size,
1508
- });
1509
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1572
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1510
1573
  return data;
1511
1574
  }
1512
1575
 
1513
- async searchProjectPhotosNaturalLanguageAuto(projectTableName, searchText, size = undefined, { blend, minCosine } = {}) {
1576
+ async searchProjectPhotosNaturalLanguage(projectTableName, searchText, size = undefined, { minCosine, labelMinCosine, labelTopK, labelDelta, bestOfSimilarSetsOnly, splitByTier } = {}) {
1514
1577
  this._ctx.requireTokens();
1515
- let path = `/api/v1/search/auto/${encodeURIComponent(projectTableName)}/`;
1578
+ let path = `/api/v1/search/natural_language/${encodeURIComponent(projectTableName)}/`;
1516
1579
  const query = new URLSearchParams();
1517
- if (blend !== undefined) (Array.isArray(blend) ? blend : [blend]).forEach(v => query.append('blend', v));
1518
1580
  if (minCosine !== undefined) (Array.isArray(minCosine) ? minCosine : [minCosine]).forEach(v => query.append('min_cosine', v));
1581
+ if (labelMinCosine !== undefined) (Array.isArray(labelMinCosine) ? labelMinCosine : [labelMinCosine]).forEach(v => query.append('label_min_cosine', v));
1582
+ if (labelTopK !== undefined) (Array.isArray(labelTopK) ? labelTopK : [labelTopK]).forEach(v => query.append('label_top_k', v));
1583
+ if (labelDelta !== undefined) (Array.isArray(labelDelta) ? labelDelta : [labelDelta]).forEach(v => query.append('label_delta', v));
1584
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1585
+ if (splitByTier !== undefined) (Array.isArray(splitByTier) ? splitByTier : [splitByTier]).forEach(v => query.append('split_by_tier', v));
1519
1586
  const qs = query.toString();
1520
1587
  if (qs) path += '?' + qs;
1521
1588
  const body = stripUndef$1({
package/dist/sdk.d.cts CHANGED
@@ -85,10 +85,11 @@ export interface CompanyDisplay {
85
85
  }
86
86
 
87
87
  export interface CompanyKeywordListOverview {
88
+ id: number;
88
89
  name: string;
89
90
  active_keywords: number;
90
91
  excluded_keywords: number;
91
- is_active: boolean;
92
+ project_ids?: string[];
92
93
  }
93
94
 
94
95
  export interface CreditBalanceOutput {
@@ -376,10 +377,12 @@ export class Keywords {
376
377
  updateKeywordFilteringListLabels(keywordListId: number, listKeywordsToInclude: number[], listKeywordsToExclude: number[]): Promise<KeywordListDisplayWithList>;
377
378
  updateKeywordFilteringListDetails(keywordListId: number, body?: { name?: string, projectList?: string[] }): Promise<KeywordListDisplayWithProjects>;
378
379
  addProjectsToKeywordFilteringList(keywordListId: number, options?: { projectIds?: any }): Promise<Record<string, any>>;
380
+ addCompanyToKeywordFilteringList(keywordListId: number, options?: { companyId?: number | number[] }): Promise<Record<string, any>>;
379
381
  requestKeywordListExport(keywordListId: number): Promise<Record<string, any>>;
380
382
  requestKeywordListExportStatus(keywordListId: number): Promise<AnalysisStatusResponse>;
381
383
  getKeywordsAndIds(): Promise<any>;
382
384
  removeProjectsFromKeywordFilteringList(keywordListId: number, options?: { projectIds?: any }): Promise<Record<string, any>>;
385
+ removeCompanyFromKeywordFilteringList(keywordListId: number, options?: { companyId?: number | number[] }): Promise<Record<string, any>>;
383
386
  deleteKeywordFilteringListById(keywordListId: number): Promise<Record<string, any>>;
384
387
  }
385
388
 
@@ -408,6 +411,7 @@ export class Person {
408
411
  splitPersons(projectTableName: string, id: number, options?: { newName?: string | string[], destinationPersonId?: string | string[] }): Promise<PhotoPersonLight>;
409
412
  getAllPersonsFromProject(projectTableName: string): Promise<PhotoPersonWithS3Link[]>;
410
413
  getAllPersonNamesFromProject(projectTableName: string): Promise<any>;
414
+ getPhotoIdsByPersons(projectTableName: string, options?: { personId?: string[] | string[][] }): Promise<any>;
411
415
  getAllPersonsFromPhoto(projectTableName: string, photoId: number): Promise<PhotoPersonLight[]>;
412
416
  }
413
417
 
@@ -422,7 +426,6 @@ export class Photos {
422
426
  getProjectPhotoIdsByTableName(tableName: string, options?: { ascOrDesc?: string | string[], lastId?: number | number[], limit?: number | number[], includeAll?: boolean | boolean[], startDate?: string | string[], endDate?: string | string[], noDateTaken?: boolean | boolean[] }): Promise<any>;
423
427
  getRankedProjectPhotoIdsByTableName(tableName: string, options?: { ascOrDesc?: string | string[], lastId?: number | number[], limit?: number | number[], startDate?: string | string[], endDate?: string | string[], noDateTaken?: boolean | boolean[] }): Promise<Record<string, any>>;
424
428
  getProjectMonthYearsWithPhotos(tableName: string): Promise<any>;
425
- getProjectThumbnail(tableName: string): Promise<any>;
426
429
  updatePhotoInProject(options?: { tableName?: string | string[], photoId?: number | number[], photoData?: Record<string, any> | Record<string, any>[] }): Promise<PhotoDisplay>;
427
430
  updatePhotoRanking(tableName: string, photoId: number, newCategory: string): Promise<Record<string, any>>;
428
431
  deletePhotoFromProject(tableName: string, options?: { photoIds?: any }): Promise<Record<string, any>>;
@@ -452,10 +455,9 @@ export class Projects {
452
455
 
453
456
  export class Search {
454
457
  searchProjectPhotos(projectTableName: string, options?: { andParams?: any, andStringParams?: any, orParams?: any, orStringParams?: any, notParams?: any, notStringParams?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], dateOrder?: string | string[], customAlbumId?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], curatedAlbumId?: number | number[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
455
- searchProjectPhotosText(projectTableName: string, searchText: string, size?: number): Promise<any>;
456
- searchProjectPhotosNaturalLanguage(projectTableName: string, searchText: string, size?: number): Promise<any>;
457
- searchProjectPhotosNaturalLanguageHybrid(projectTableName: string, searchText: string, size?: number, options?: { blend?: string | string[], minCosine?: number | number[] }): Promise<any>;
458
- searchProjectPhotosNaturalLanguageAuto(projectTableName: string, searchText: string, size?: number, options?: { blend?: string | string[], minCosine?: number | number[] }): Promise<any>;
458
+ searchProjectPhotosParametrized(projectTableName: string, options?: { andSearchText?: any, orSearchText?: any, notSearchText?: any, city?: any, country?: any, state?: any, albums?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], ascOrDesc?: string | string[], bestOfSimilarSetsOnly?: boolean | boolean[], splitByTier?: boolean | boolean[], lastId?: number | number[], limit?: number | number[] }): Promise<Record<string, any>>;
459
+ recallProjectPhotos(projectTableName: string, options?: { ascOrDesc?: string | string[], bestOfSimilarSetsOnly?: boolean | boolean[], lastId?: number | number[], limit?: number | number[] }): Promise<any>;
460
+ searchProjectPhotosNaturalLanguage(projectTableName: string, searchText: string, size?: number, options?: { minCosine?: number | number[], labelMinCosine?: number | number[], labelTopK?: number | number[], labelDelta?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
459
461
  getProjectSavedSearches(projectTableName: string): Promise<any>;
460
462
  getSavedSearchById(searchId: number): Promise<SearchDisplay>;
461
463
  saveProjectPhotosSearch(projectTableName: string, options?: { searchName?: string | string[], andParams?: any, andStringParams?: any, orParams?: any, orStringParams?: any, notParams?: any, notStringParams?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], dateOrder?: string | string[], customAlbumId?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], curatedAlbumId?: number | number[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
package/dist/sdk.d.ts CHANGED
@@ -85,10 +85,11 @@ export interface CompanyDisplay {
85
85
  }
86
86
 
87
87
  export interface CompanyKeywordListOverview {
88
+ id: number;
88
89
  name: string;
89
90
  active_keywords: number;
90
91
  excluded_keywords: number;
91
- is_active: boolean;
92
+ project_ids?: string[];
92
93
  }
93
94
 
94
95
  export interface CreditBalanceOutput {
@@ -376,10 +377,12 @@ export class Keywords {
376
377
  updateKeywordFilteringListLabels(keywordListId: number, listKeywordsToInclude: number[], listKeywordsToExclude: number[]): Promise<KeywordListDisplayWithList>;
377
378
  updateKeywordFilteringListDetails(keywordListId: number, body?: { name?: string, projectList?: string[] }): Promise<KeywordListDisplayWithProjects>;
378
379
  addProjectsToKeywordFilteringList(keywordListId: number, options?: { projectIds?: any }): Promise<Record<string, any>>;
380
+ addCompanyToKeywordFilteringList(keywordListId: number, options?: { companyId?: number | number[] }): Promise<Record<string, any>>;
379
381
  requestKeywordListExport(keywordListId: number): Promise<Record<string, any>>;
380
382
  requestKeywordListExportStatus(keywordListId: number): Promise<AnalysisStatusResponse>;
381
383
  getKeywordsAndIds(): Promise<any>;
382
384
  removeProjectsFromKeywordFilteringList(keywordListId: number, options?: { projectIds?: any }): Promise<Record<string, any>>;
385
+ removeCompanyFromKeywordFilteringList(keywordListId: number, options?: { companyId?: number | number[] }): Promise<Record<string, any>>;
383
386
  deleteKeywordFilteringListById(keywordListId: number): Promise<Record<string, any>>;
384
387
  }
385
388
 
@@ -408,6 +411,7 @@ export class Person {
408
411
  splitPersons(projectTableName: string, id: number, options?: { newName?: string | string[], destinationPersonId?: string | string[] }): Promise<PhotoPersonLight>;
409
412
  getAllPersonsFromProject(projectTableName: string): Promise<PhotoPersonWithS3Link[]>;
410
413
  getAllPersonNamesFromProject(projectTableName: string): Promise<any>;
414
+ getPhotoIdsByPersons(projectTableName: string, options?: { personId?: string[] | string[][] }): Promise<any>;
411
415
  getAllPersonsFromPhoto(projectTableName: string, photoId: number): Promise<PhotoPersonLight[]>;
412
416
  }
413
417
 
@@ -422,7 +426,6 @@ export class Photos {
422
426
  getProjectPhotoIdsByTableName(tableName: string, options?: { ascOrDesc?: string | string[], lastId?: number | number[], limit?: number | number[], includeAll?: boolean | boolean[], startDate?: string | string[], endDate?: string | string[], noDateTaken?: boolean | boolean[] }): Promise<any>;
423
427
  getRankedProjectPhotoIdsByTableName(tableName: string, options?: { ascOrDesc?: string | string[], lastId?: number | number[], limit?: number | number[], startDate?: string | string[], endDate?: string | string[], noDateTaken?: boolean | boolean[] }): Promise<Record<string, any>>;
424
428
  getProjectMonthYearsWithPhotos(tableName: string): Promise<any>;
425
- getProjectThumbnail(tableName: string): Promise<any>;
426
429
  updatePhotoInProject(options?: { tableName?: string | string[], photoId?: number | number[], photoData?: Record<string, any> | Record<string, any>[] }): Promise<PhotoDisplay>;
427
430
  updatePhotoRanking(tableName: string, photoId: number, newCategory: string): Promise<Record<string, any>>;
428
431
  deletePhotoFromProject(tableName: string, options?: { photoIds?: any }): Promise<Record<string, any>>;
@@ -452,10 +455,9 @@ export class Projects {
452
455
 
453
456
  export class Search {
454
457
  searchProjectPhotos(projectTableName: string, options?: { andParams?: any, andStringParams?: any, orParams?: any, orStringParams?: any, notParams?: any, notStringParams?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], dateOrder?: string | string[], customAlbumId?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], curatedAlbumId?: number | number[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
455
- searchProjectPhotosText(projectTableName: string, searchText: string, size?: number): Promise<any>;
456
- searchProjectPhotosNaturalLanguage(projectTableName: string, searchText: string, size?: number): Promise<any>;
457
- searchProjectPhotosNaturalLanguageHybrid(projectTableName: string, searchText: string, size?: number, options?: { blend?: string | string[], minCosine?: number | number[] }): Promise<any>;
458
- searchProjectPhotosNaturalLanguageAuto(projectTableName: string, searchText: string, size?: number, options?: { blend?: string | string[], minCosine?: number | number[] }): Promise<any>;
458
+ searchProjectPhotosParametrized(projectTableName: string, options?: { andSearchText?: any, orSearchText?: any, notSearchText?: any, city?: any, country?: any, state?: any, albums?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], ascOrDesc?: string | string[], bestOfSimilarSetsOnly?: boolean | boolean[], splitByTier?: boolean | boolean[], lastId?: number | number[], limit?: number | number[] }): Promise<Record<string, any>>;
459
+ recallProjectPhotos(projectTableName: string, options?: { ascOrDesc?: string | string[], bestOfSimilarSetsOnly?: boolean | boolean[], lastId?: number | number[], limit?: number | number[] }): Promise<any>;
460
+ searchProjectPhotosNaturalLanguage(projectTableName: string, searchText: string, size?: number, options?: { minCosine?: number | number[], labelMinCosine?: number | number[], labelTopK?: number | number[], labelDelta?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
459
461
  getProjectSavedSearches(projectTableName: string): Promise<any>;
460
462
  getSavedSearchById(searchId: number): Promise<SearchDisplay>;
461
463
  saveProjectPhotosSearch(projectTableName: string, options?: { searchName?: string | string[], andParams?: any, andStringParams?: any, orParams?: any, orStringParams?: any, notParams?: any, notStringParams?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], dateOrder?: string | string[], customAlbumId?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], curatedAlbumId?: number | number[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
package/dist/sdk.esm.d.ts CHANGED
@@ -85,10 +85,11 @@ export interface CompanyDisplay {
85
85
  }
86
86
 
87
87
  export interface CompanyKeywordListOverview {
88
+ id: number;
88
89
  name: string;
89
90
  active_keywords: number;
90
91
  excluded_keywords: number;
91
- is_active: boolean;
92
+ project_ids?: string[];
92
93
  }
93
94
 
94
95
  export interface CreditBalanceOutput {
@@ -376,10 +377,12 @@ export class Keywords {
376
377
  updateKeywordFilteringListLabels(keywordListId: number, listKeywordsToInclude: number[], listKeywordsToExclude: number[]): Promise<KeywordListDisplayWithList>;
377
378
  updateKeywordFilteringListDetails(keywordListId: number, body?: { name?: string, projectList?: string[] }): Promise<KeywordListDisplayWithProjects>;
378
379
  addProjectsToKeywordFilteringList(keywordListId: number, options?: { projectIds?: any }): Promise<Record<string, any>>;
380
+ addCompanyToKeywordFilteringList(keywordListId: number, options?: { companyId?: number | number[] }): Promise<Record<string, any>>;
379
381
  requestKeywordListExport(keywordListId: number): Promise<Record<string, any>>;
380
382
  requestKeywordListExportStatus(keywordListId: number): Promise<AnalysisStatusResponse>;
381
383
  getKeywordsAndIds(): Promise<any>;
382
384
  removeProjectsFromKeywordFilteringList(keywordListId: number, options?: { projectIds?: any }): Promise<Record<string, any>>;
385
+ removeCompanyFromKeywordFilteringList(keywordListId: number, options?: { companyId?: number | number[] }): Promise<Record<string, any>>;
383
386
  deleteKeywordFilteringListById(keywordListId: number): Promise<Record<string, any>>;
384
387
  }
385
388
 
@@ -408,6 +411,7 @@ export class Person {
408
411
  splitPersons(projectTableName: string, id: number, options?: { newName?: string | string[], destinationPersonId?: string | string[] }): Promise<PhotoPersonLight>;
409
412
  getAllPersonsFromProject(projectTableName: string): Promise<PhotoPersonWithS3Link[]>;
410
413
  getAllPersonNamesFromProject(projectTableName: string): Promise<any>;
414
+ getPhotoIdsByPersons(projectTableName: string, options?: { personId?: string[] | string[][] }): Promise<any>;
411
415
  getAllPersonsFromPhoto(projectTableName: string, photoId: number): Promise<PhotoPersonLight[]>;
412
416
  }
413
417
 
@@ -422,7 +426,6 @@ export class Photos {
422
426
  getProjectPhotoIdsByTableName(tableName: string, options?: { ascOrDesc?: string | string[], lastId?: number | number[], limit?: number | number[], includeAll?: boolean | boolean[], startDate?: string | string[], endDate?: string | string[], noDateTaken?: boolean | boolean[] }): Promise<any>;
423
427
  getRankedProjectPhotoIdsByTableName(tableName: string, options?: { ascOrDesc?: string | string[], lastId?: number | number[], limit?: number | number[], startDate?: string | string[], endDate?: string | string[], noDateTaken?: boolean | boolean[] }): Promise<Record<string, any>>;
424
428
  getProjectMonthYearsWithPhotos(tableName: string): Promise<any>;
425
- getProjectThumbnail(tableName: string): Promise<any>;
426
429
  updatePhotoInProject(options?: { tableName?: string | string[], photoId?: number | number[], photoData?: Record<string, any> | Record<string, any>[] }): Promise<PhotoDisplay>;
427
430
  updatePhotoRanking(tableName: string, photoId: number, newCategory: string): Promise<Record<string, any>>;
428
431
  deletePhotoFromProject(tableName: string, options?: { photoIds?: any }): Promise<Record<string, any>>;
@@ -452,10 +455,9 @@ export class Projects {
452
455
 
453
456
  export class Search {
454
457
  searchProjectPhotos(projectTableName: string, options?: { andParams?: any, andStringParams?: any, orParams?: any, orStringParams?: any, notParams?: any, notStringParams?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], dateOrder?: string | string[], customAlbumId?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], curatedAlbumId?: number | number[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
455
- searchProjectPhotosText(projectTableName: string, searchText: string, size?: number): Promise<any>;
456
- searchProjectPhotosNaturalLanguage(projectTableName: string, searchText: string, size?: number): Promise<any>;
457
- searchProjectPhotosNaturalLanguageHybrid(projectTableName: string, searchText: string, size?: number, options?: { blend?: string | string[], minCosine?: number | number[] }): Promise<any>;
458
- searchProjectPhotosNaturalLanguageAuto(projectTableName: string, searchText: string, size?: number, options?: { blend?: string | string[], minCosine?: number | number[] }): Promise<any>;
458
+ searchProjectPhotosParametrized(projectTableName: string, options?: { andSearchText?: any, orSearchText?: any, notSearchText?: any, city?: any, country?: any, state?: any, albums?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], ascOrDesc?: string | string[], bestOfSimilarSetsOnly?: boolean | boolean[], splitByTier?: boolean | boolean[], lastId?: number | number[], limit?: number | number[] }): Promise<Record<string, any>>;
459
+ recallProjectPhotos(projectTableName: string, options?: { ascOrDesc?: string | string[], bestOfSimilarSetsOnly?: boolean | boolean[], lastId?: number | number[], limit?: number | number[] }): Promise<any>;
460
+ searchProjectPhotosNaturalLanguage(projectTableName: string, searchText: string, size?: number, options?: { minCosine?: number | number[], labelMinCosine?: number | number[], labelTopK?: number | number[], labelDelta?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
459
461
  getProjectSavedSearches(projectTableName: string): Promise<any>;
460
462
  getSavedSearchById(searchId: number): Promise<SearchDisplay>;
461
463
  saveProjectPhotosSearch(projectTableName: string, options?: { searchName?: string | string[], andParams?: any, andStringParams?: any, orParams?: any, orStringParams?: any, notParams?: any, notStringParams?: any, dateMin?: string | string[], dateMax?: string | string[], dateNullAnd?: boolean | boolean[], dateNullOr?: boolean | boolean[], dateOrder?: string | string[], customAlbumId?: number | number[], bestOfSimilarSetsOnly?: boolean | boolean[], curatedAlbumId?: number | number[], splitByTier?: boolean | boolean[] }): Promise<Record<string, any>>;
package/dist/sdk.esm.js CHANGED
@@ -89,8 +89,15 @@ function requireErrors () {
89
89
  * @returns {OAuthError}
90
90
  */
91
91
  static fromResponse(status, body) {
92
- if (body && typeof body === 'object' && typeof body.error === 'string') {
93
- return new OAuthError(body.error, body.error_description ?? '', status, body);
92
+ // FastAPI wraps HTTPException payloads in a `detail` envelope, so an OAuth
93
+ // error arrives as { detail: { error, error_description } }. Unwrap it
94
+ // before falling back, or every server-side OAuth error reads as server_error.
95
+ const payload =
96
+ body && typeof body === 'object' && typeof body.error !== 'string' && body.detail
97
+ ? body.detail
98
+ : body;
99
+ if (payload && typeof payload === 'object' && typeof payload.error === 'string') {
100
+ return new OAuthError(payload.error, payload.error_description ?? '', status, body);
94
101
  }
95
102
  return new OAuthError('server_error', 'Unexpected server response', status, body);
96
103
  }
@@ -171,6 +178,9 @@ function requireClient () {
171
178
  const { postForm, postJson } = requireHttp();
172
179
  const { OAuthError } = requireErrors();
173
180
 
181
+ // In-flight dedup key for client_credentials re-mints (no refresh_token to key on).
182
+ const CLIENT_CREDENTIALS_KEY = Symbol('client_credentials');
183
+
174
184
  class OAuthClient {
175
185
  /**
176
186
  * @param {import('./types').OAuthClientConfig} config
@@ -186,12 +196,22 @@ function requireClient () {
186
196
  */
187
197
  static async registerClient(params) {
188
198
  const baseUrl = params.baseUrl.replace(/\/+$/, '');
189
- return postJson(`${baseUrl}/oauth/clients`, {
199
+ const body = {
190
200
  client_name: params.clientName,
191
201
  client_type: params.clientType,
192
202
  redirect_uris: params.redirectUris,
193
203
  is_first_party: params.isFirstParty,
194
- });
204
+ };
205
+ // grant_types/company_id are required to provision a confidential
206
+ // client_credentials (machine-to-machine) client; omitted bodies keep the
207
+ // legacy public/authorization_code shape. The endpoint is admin-gated, so a
208
+ // bearer token must be supplied for the request to authenticate.
209
+ if (params.grantTypes !== undefined) body.grant_types = params.grantTypes;
210
+ if (params.companyId !== undefined) body.company_id = params.companyId;
211
+ const headers = params.accessToken
212
+ ? { Authorization: `Bearer ${params.accessToken}` }
213
+ : {};
214
+ return postJson(`${baseUrl}/oauth/clients`, body, headers);
195
215
  }
196
216
 
197
217
  /**
@@ -309,15 +329,27 @@ function requireClient () {
309
329
  return { data: json, updatedTokens: null };
310
330
  }
311
331
 
312
- // 401: attempt refresh — propagate OAuthError if refresh fails.
313
- // Concurrent request() callers with the same refresh_token share one in-flight
314
- // call so the server only sees one rotation (the second would get invalid_grant).
315
- let pending = this._inFlightRefreshes.get(refreshToken);
332
+ // 401: re-authenticate, then retry once.
333
+ // - User sessions (refresh_token present) use the refresh_token grant. The server
334
+ // rotates the token one-time, so concurrent callers share one in-flight call
335
+ // (a second rotation would get invalid_grant).
336
+ // - Machine-to-machine sessions (no refresh_token, client_secret configured) re-mint
337
+ // via client_credentials, which issues no refresh token to rotate (RFC 6749 §4.4).
338
+ // - With neither a refresh_token nor a client_secret there is nothing to retry with.
339
+ if (!refreshToken && !this._config.clientSecret) {
340
+ throw OAuthError.fromResponse(401, await firstResponse.json());
341
+ }
342
+
343
+ const reauthKey = refreshToken || CLIENT_CREDENTIALS_KEY;
344
+ let pending = this._inFlightRefreshes.get(reauthKey);
316
345
  if (!pending) {
317
- pending = this.refreshAccessToken(refreshToken).finally(() => {
318
- this._inFlightRefreshes.delete(refreshToken);
346
+ const reauth = refreshToken
347
+ ? this.refreshAccessToken(refreshToken)
348
+ : this.getClientCredentialsToken();
349
+ pending = reauth.finally(() => {
350
+ this._inFlightRefreshes.delete(reauthKey);
319
351
  });
320
- this._inFlightRefreshes.set(refreshToken, pending);
352
+ this._inFlightRefreshes.set(reauthKey, pending);
321
353
  }
322
354
  const newTokens = await pending;
323
355
 
@@ -399,6 +431,9 @@ function requireTypes () {
399
431
  * @property {string} clientType - "public" or "confidential"
400
432
  * @property {string[]} redirectUris - HTTPS redirect URIs
401
433
  * @property {boolean} isFirstParty - Whether this is a first-party client
434
+ * @property {string[]} [grantTypes] - Allowed grants, e.g. ["client_credentials"]; required for machine-to-machine clients
435
+ * @property {number} [companyId] - Company to bind the client to; required for the client_credentials grant
436
+ * @property {string} [accessToken] - Admin bearer token; the registration endpoint is authorization-gated
402
437
  */
403
438
 
404
439
  /**
@@ -910,6 +945,17 @@ class Keywords {
910
945
  return data;
911
946
  }
912
947
 
948
+ async addCompanyToKeywordFilteringList(keywordListId, { companyId } = {}) {
949
+ this._ctx.requireTokens();
950
+ let path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}/company`;
951
+ const query = new URLSearchParams();
952
+ if (companyId !== undefined) (Array.isArray(companyId) ? companyId : [companyId]).forEach(v => query.append('company_id', v));
953
+ const qs = query.toString();
954
+ if (qs) path += '?' + qs;
955
+ const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken);
956
+ return data;
957
+ }
958
+
913
959
  async requestKeywordListExport(keywordListId) {
914
960
  this._ctx.requireTokens();
915
961
  const path = `/api/v1/keyword/export/${encodeURIComponent(keywordListId)}`;
@@ -942,6 +988,17 @@ class Keywords {
942
988
  return data;
943
989
  }
944
990
 
991
+ async removeCompanyFromKeywordFilteringList(keywordListId, { companyId } = {}) {
992
+ this._ctx.requireTokens();
993
+ let path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}/company`;
994
+ const query = new URLSearchParams();
995
+ if (companyId !== undefined) (Array.isArray(companyId) ? companyId : [companyId]).forEach(v => query.append('company_id', v));
996
+ const qs = query.toString();
997
+ if (qs) path += '?' + qs;
998
+ const { data } = await this._ctx.client.request(path, 'DELETE', this._ctx.accessToken, this._ctx.refreshToken);
999
+ return data;
1000
+ }
1001
+
945
1002
  async deleteKeywordFilteringListById(keywordListId) {
946
1003
  this._ctx.requireTokens();
947
1004
  const path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}`;
@@ -1101,6 +1158,17 @@ class Person {
1101
1158
  return data;
1102
1159
  }
1103
1160
 
1161
+ async getPhotoIdsByPersons(projectTableName, { personId } = {}) {
1162
+ this._ctx.requireTokens();
1163
+ let path = `/api/v1/person/${encodeURIComponent(projectTableName)}/photos`;
1164
+ const query = new URLSearchParams();
1165
+ if (personId !== undefined) (Array.isArray(personId) ? personId : [personId]).forEach(v => query.append('person_id', v));
1166
+ const qs = query.toString();
1167
+ if (qs) path += '?' + qs;
1168
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1169
+ return data;
1170
+ }
1171
+
1104
1172
  async getAllPersonsFromPhoto(projectTableName, photoId) {
1105
1173
  this._ctx.requireTokens();
1106
1174
  const path = `/api/v1/person/${encodeURIComponent(projectTableName)}/photo/${encodeURIComponent(photoId)}`;
@@ -1334,7 +1402,7 @@ class Photoupload {
1334
1402
  this._caches['_get_template'].set(_cacheKey_get_template, template);
1335
1403
  }
1336
1404
 
1337
- const upload_result = await this.uploadPhotoToMediaviz(companyId, userId, projectTableName, photo.title, { fileContent: photo.fileContent, mimetype: photo.mimetype, filePath: photo.filePath }, { clientSideId: photo.clientSideId, blur: photo.blur, colors: photo.colors, faceRecognition: photo.faceRecognition, imageDescribe: photo.imageDescribe, imageClassification: photo.imageClassification, imageComparison: photo.imageComparison, size: photo.size, sourceResolutionX: photo.sourceResolutionX, sourceResolutionY: photo.sourceResolutionY, dateTaken: photo.dateTaken, latitude: photo.latitude, longitude: photo.longitude });
1405
+ const upload_result = await this.uploadPhotoToMediaviz(companyId, userId, projectTableName, photo.title, { fileContent: photo.fileContent, mimetype: photo.mimetype, filePath: photo.filePath }, { clientSideId: photo.clientSideId, blur: template?.blur, colors: template?.colors, faceRecognition: template?.face_recognition, imageDescribe: template?.image_describe, imageClassification: template?.image_classification, imageComparison: template?.image_comparison, size: photo.size, sourceResolutionX: photo.sourceResolutionX, sourceResolutionY: photo.sourceResolutionY, dateTaken: photo.dateTaken, latitude: photo.latitude, longitude: photo.longitude, ocr: template?.ocr });
1338
1406
 
1339
1407
  return upload_result;
1340
1408
  }
@@ -1401,13 +1469,6 @@ class Photos {
1401
1469
  return data;
1402
1470
  }
1403
1471
 
1404
- async getProjectThumbnail(tableName) {
1405
- this._ctx.requireTokens();
1406
- const path = `/api/v1/photos_project/${encodeURIComponent(tableName)}`;
1407
- const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1408
- return data;
1409
- }
1410
-
1411
1472
  async updatePhotoInProject({ tableName, photoId, photoData } = {}) {
1412
1473
  this._ctx.requireTokens();
1413
1474
  let path = `/api/v1/photos_update`;
@@ -1470,50 +1531,56 @@ class Search {
1470
1531
  return data;
1471
1532
  }
1472
1533
 
1473
- async searchProjectPhotosText(projectTableName, searchText, size = undefined) {
1534
+ async searchProjectPhotosParametrized(projectTableName, { andSearchText, orSearchText, notSearchText, city, country, state, albums, dateMin, dateMax, dateNullAnd, dateNullOr, ascOrDesc, bestOfSimilarSetsOnly, splitByTier, lastId, limit } = {}) {
1474
1535
  this._ctx.requireTokens();
1475
- const path = `/api/v1/search/text/${encodeURIComponent(projectTableName)}/`;
1476
- const body = stripUndef$1({
1477
- search_text: searchText,
1478
- size: size,
1479
- });
1480
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1481
- return data;
1482
- }
1483
-
1484
- async searchProjectPhotosNaturalLanguage(projectTableName, searchText, size = undefined) {
1485
- this._ctx.requireTokens();
1486
- const path = `/api/v1/search/nl/${encodeURIComponent(projectTableName)}/`;
1487
- const body = stripUndef$1({
1488
- search_text: searchText,
1489
- size: size,
1490
- });
1491
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1536
+ let path = `/api/v1/search/parametrized/${encodeURIComponent(projectTableName)}/`;
1537
+ const query = new URLSearchParams();
1538
+ if (andSearchText !== undefined) (Array.isArray(andSearchText) ? andSearchText : [andSearchText]).forEach(v => query.append('and_search_text', v));
1539
+ if (orSearchText !== undefined) (Array.isArray(orSearchText) ? orSearchText : [orSearchText]).forEach(v => query.append('or_search_text', v));
1540
+ if (notSearchText !== undefined) (Array.isArray(notSearchText) ? notSearchText : [notSearchText]).forEach(v => query.append('not_search_text', v));
1541
+ if (city !== undefined) (Array.isArray(city) ? city : [city]).forEach(v => query.append('city', v));
1542
+ if (country !== undefined) (Array.isArray(country) ? country : [country]).forEach(v => query.append('country', v));
1543
+ if (state !== undefined) (Array.isArray(state) ? state : [state]).forEach(v => query.append('state', v));
1544
+ if (albums !== undefined) (Array.isArray(albums) ? albums : [albums]).forEach(v => query.append('albums', v));
1545
+ if (dateMin !== undefined) (Array.isArray(dateMin) ? dateMin : [dateMin]).forEach(v => query.append('date_min', v));
1546
+ if (dateMax !== undefined) (Array.isArray(dateMax) ? dateMax : [dateMax]).forEach(v => query.append('date_max', v));
1547
+ if (dateNullAnd !== undefined) (Array.isArray(dateNullAnd) ? dateNullAnd : [dateNullAnd]).forEach(v => query.append('date_null_and', v));
1548
+ if (dateNullOr !== undefined) (Array.isArray(dateNullOr) ? dateNullOr : [dateNullOr]).forEach(v => query.append('date_null_or', v));
1549
+ if (ascOrDesc !== undefined) (Array.isArray(ascOrDesc) ? ascOrDesc : [ascOrDesc]).forEach(v => query.append('asc_or_desc', v));
1550
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1551
+ if (splitByTier !== undefined) (Array.isArray(splitByTier) ? splitByTier : [splitByTier]).forEach(v => query.append('split_by_tier', v));
1552
+ if (lastId !== undefined) (Array.isArray(lastId) ? lastId : [lastId]).forEach(v => query.append('last_id', v));
1553
+ if (limit !== undefined) (Array.isArray(limit) ? limit : [limit]).forEach(v => query.append('limit', v));
1554
+ const qs = query.toString();
1555
+ if (qs) path += '?' + qs;
1556
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1492
1557
  return data;
1493
1558
  }
1494
1559
 
1495
- async searchProjectPhotosNaturalLanguageHybrid(projectTableName, searchText, size = undefined, { blend, minCosine } = {}) {
1560
+ async recallProjectPhotos(projectTableName, { ascOrDesc, bestOfSimilarSetsOnly, lastId, limit } = {}) {
1496
1561
  this._ctx.requireTokens();
1497
- let path = `/api/v1/search/nl_hybrid/${encodeURIComponent(projectTableName)}/`;
1562
+ let path = `/api/v1/search/recall/${encodeURIComponent(projectTableName)}/`;
1498
1563
  const query = new URLSearchParams();
1499
- if (blend !== undefined) (Array.isArray(blend) ? blend : [blend]).forEach(v => query.append('blend', v));
1500
- if (minCosine !== undefined) (Array.isArray(minCosine) ? minCosine : [minCosine]).forEach(v => query.append('min_cosine', v));
1564
+ if (ascOrDesc !== undefined) (Array.isArray(ascOrDesc) ? ascOrDesc : [ascOrDesc]).forEach(v => query.append('asc_or_desc', v));
1565
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1566
+ if (lastId !== undefined) (Array.isArray(lastId) ? lastId : [lastId]).forEach(v => query.append('last_id', v));
1567
+ if (limit !== undefined) (Array.isArray(limit) ? limit : [limit]).forEach(v => query.append('limit', v));
1501
1568
  const qs = query.toString();
1502
1569
  if (qs) path += '?' + qs;
1503
- const body = stripUndef$1({
1504
- search_text: searchText,
1505
- size: size,
1506
- });
1507
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1570
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1508
1571
  return data;
1509
1572
  }
1510
1573
 
1511
- async searchProjectPhotosNaturalLanguageAuto(projectTableName, searchText, size = undefined, { blend, minCosine } = {}) {
1574
+ async searchProjectPhotosNaturalLanguage(projectTableName, searchText, size = undefined, { minCosine, labelMinCosine, labelTopK, labelDelta, bestOfSimilarSetsOnly, splitByTier } = {}) {
1512
1575
  this._ctx.requireTokens();
1513
- let path = `/api/v1/search/auto/${encodeURIComponent(projectTableName)}/`;
1576
+ let path = `/api/v1/search/natural_language/${encodeURIComponent(projectTableName)}/`;
1514
1577
  const query = new URLSearchParams();
1515
- if (blend !== undefined) (Array.isArray(blend) ? blend : [blend]).forEach(v => query.append('blend', v));
1516
1578
  if (minCosine !== undefined) (Array.isArray(minCosine) ? minCosine : [minCosine]).forEach(v => query.append('min_cosine', v));
1579
+ if (labelMinCosine !== undefined) (Array.isArray(labelMinCosine) ? labelMinCosine : [labelMinCosine]).forEach(v => query.append('label_min_cosine', v));
1580
+ if (labelTopK !== undefined) (Array.isArray(labelTopK) ? labelTopK : [labelTopK]).forEach(v => query.append('label_top_k', v));
1581
+ if (labelDelta !== undefined) (Array.isArray(labelDelta) ? labelDelta : [labelDelta]).forEach(v => query.append('label_delta', v));
1582
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1583
+ if (splitByTier !== undefined) (Array.isArray(splitByTier) ? splitByTier : [splitByTier]).forEach(v => query.append('split_by_tier', v));
1517
1584
  const qs = query.toString();
1518
1585
  if (qs) path += '?' + qs;
1519
1586
  const body = stripUndef$1({
package/dist/sdk.umd.js CHANGED
@@ -95,8 +95,15 @@
95
95
  * @returns {OAuthError}
96
96
  */
97
97
  static fromResponse(status, body) {
98
- if (body && typeof body === 'object' && typeof body.error === 'string') {
99
- return new OAuthError(body.error, body.error_description ?? '', status, body);
98
+ // FastAPI wraps HTTPException payloads in a `detail` envelope, so an OAuth
99
+ // error arrives as { detail: { error, error_description } }. Unwrap it
100
+ // before falling back, or every server-side OAuth error reads as server_error.
101
+ const payload =
102
+ body && typeof body === 'object' && typeof body.error !== 'string' && body.detail
103
+ ? body.detail
104
+ : body;
105
+ if (payload && typeof payload === 'object' && typeof payload.error === 'string') {
106
+ return new OAuthError(payload.error, payload.error_description ?? '', status, body);
100
107
  }
101
108
  return new OAuthError('server_error', 'Unexpected server response', status, body);
102
109
  }
@@ -177,6 +184,9 @@
177
184
  const { postForm, postJson } = requireHttp();
178
185
  const { OAuthError } = requireErrors();
179
186
 
187
+ // In-flight dedup key for client_credentials re-mints (no refresh_token to key on).
188
+ const CLIENT_CREDENTIALS_KEY = Symbol('client_credentials');
189
+
180
190
  class OAuthClient {
181
191
  /**
182
192
  * @param {import('./types').OAuthClientConfig} config
@@ -192,12 +202,22 @@
192
202
  */
193
203
  static async registerClient(params) {
194
204
  const baseUrl = params.baseUrl.replace(/\/+$/, '');
195
- return postJson(`${baseUrl}/oauth/clients`, {
205
+ const body = {
196
206
  client_name: params.clientName,
197
207
  client_type: params.clientType,
198
208
  redirect_uris: params.redirectUris,
199
209
  is_first_party: params.isFirstParty,
200
- });
210
+ };
211
+ // grant_types/company_id are required to provision a confidential
212
+ // client_credentials (machine-to-machine) client; omitted bodies keep the
213
+ // legacy public/authorization_code shape. The endpoint is admin-gated, so a
214
+ // bearer token must be supplied for the request to authenticate.
215
+ if (params.grantTypes !== undefined) body.grant_types = params.grantTypes;
216
+ if (params.companyId !== undefined) body.company_id = params.companyId;
217
+ const headers = params.accessToken
218
+ ? { Authorization: `Bearer ${params.accessToken}` }
219
+ : {};
220
+ return postJson(`${baseUrl}/oauth/clients`, body, headers);
201
221
  }
202
222
 
203
223
  /**
@@ -315,15 +335,27 @@
315
335
  return { data: json, updatedTokens: null };
316
336
  }
317
337
 
318
- // 401: attempt refresh — propagate OAuthError if refresh fails.
319
- // Concurrent request() callers with the same refresh_token share one in-flight
320
- // call so the server only sees one rotation (the second would get invalid_grant).
321
- let pending = this._inFlightRefreshes.get(refreshToken);
338
+ // 401: re-authenticate, then retry once.
339
+ // - User sessions (refresh_token present) use the refresh_token grant. The server
340
+ // rotates the token one-time, so concurrent callers share one in-flight call
341
+ // (a second rotation would get invalid_grant).
342
+ // - Machine-to-machine sessions (no refresh_token, client_secret configured) re-mint
343
+ // via client_credentials, which issues no refresh token to rotate (RFC 6749 §4.4).
344
+ // - With neither a refresh_token nor a client_secret there is nothing to retry with.
345
+ if (!refreshToken && !this._config.clientSecret) {
346
+ throw OAuthError.fromResponse(401, await firstResponse.json());
347
+ }
348
+
349
+ const reauthKey = refreshToken || CLIENT_CREDENTIALS_KEY;
350
+ let pending = this._inFlightRefreshes.get(reauthKey);
322
351
  if (!pending) {
323
- pending = this.refreshAccessToken(refreshToken).finally(() => {
324
- this._inFlightRefreshes.delete(refreshToken);
352
+ const reauth = refreshToken
353
+ ? this.refreshAccessToken(refreshToken)
354
+ : this.getClientCredentialsToken();
355
+ pending = reauth.finally(() => {
356
+ this._inFlightRefreshes.delete(reauthKey);
325
357
  });
326
- this._inFlightRefreshes.set(refreshToken, pending);
358
+ this._inFlightRefreshes.set(reauthKey, pending);
327
359
  }
328
360
  const newTokens = await pending;
329
361
 
@@ -405,6 +437,9 @@
405
437
  * @property {string} clientType - "public" or "confidential"
406
438
  * @property {string[]} redirectUris - HTTPS redirect URIs
407
439
  * @property {boolean} isFirstParty - Whether this is a first-party client
440
+ * @property {string[]} [grantTypes] - Allowed grants, e.g. ["client_credentials"]; required for machine-to-machine clients
441
+ * @property {number} [companyId] - Company to bind the client to; required for the client_credentials grant
442
+ * @property {string} [accessToken] - Admin bearer token; the registration endpoint is authorization-gated
408
443
  */
409
444
 
410
445
  /**
@@ -916,6 +951,17 @@
916
951
  return data;
917
952
  }
918
953
 
954
+ async addCompanyToKeywordFilteringList(keywordListId, { companyId } = {}) {
955
+ this._ctx.requireTokens();
956
+ let path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}/company`;
957
+ const query = new URLSearchParams();
958
+ if (companyId !== undefined) (Array.isArray(companyId) ? companyId : [companyId]).forEach(v => query.append('company_id', v));
959
+ const qs = query.toString();
960
+ if (qs) path += '?' + qs;
961
+ const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken);
962
+ return data;
963
+ }
964
+
919
965
  async requestKeywordListExport(keywordListId) {
920
966
  this._ctx.requireTokens();
921
967
  const path = `/api/v1/keyword/export/${encodeURIComponent(keywordListId)}`;
@@ -948,6 +994,17 @@
948
994
  return data;
949
995
  }
950
996
 
997
+ async removeCompanyFromKeywordFilteringList(keywordListId, { companyId } = {}) {
998
+ this._ctx.requireTokens();
999
+ let path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}/company`;
1000
+ const query = new URLSearchParams();
1001
+ if (companyId !== undefined) (Array.isArray(companyId) ? companyId : [companyId]).forEach(v => query.append('company_id', v));
1002
+ const qs = query.toString();
1003
+ if (qs) path += '?' + qs;
1004
+ const { data } = await this._ctx.client.request(path, 'DELETE', this._ctx.accessToken, this._ctx.refreshToken);
1005
+ return data;
1006
+ }
1007
+
951
1008
  async deleteKeywordFilteringListById(keywordListId) {
952
1009
  this._ctx.requireTokens();
953
1010
  const path = `/api/v1/keyword/${encodeURIComponent(keywordListId)}`;
@@ -1107,6 +1164,17 @@
1107
1164
  return data;
1108
1165
  }
1109
1166
 
1167
+ async getPhotoIdsByPersons(projectTableName, { personId } = {}) {
1168
+ this._ctx.requireTokens();
1169
+ let path = `/api/v1/person/${encodeURIComponent(projectTableName)}/photos`;
1170
+ const query = new URLSearchParams();
1171
+ if (personId !== undefined) (Array.isArray(personId) ? personId : [personId]).forEach(v => query.append('person_id', v));
1172
+ const qs = query.toString();
1173
+ if (qs) path += '?' + qs;
1174
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1175
+ return data;
1176
+ }
1177
+
1110
1178
  async getAllPersonsFromPhoto(projectTableName, photoId) {
1111
1179
  this._ctx.requireTokens();
1112
1180
  const path = `/api/v1/person/${encodeURIComponent(projectTableName)}/photo/${encodeURIComponent(photoId)}`;
@@ -1340,7 +1408,7 @@
1340
1408
  this._caches['_get_template'].set(_cacheKey_get_template, template);
1341
1409
  }
1342
1410
 
1343
- const upload_result = await this.uploadPhotoToMediaviz(companyId, userId, projectTableName, photo.title, { fileContent: photo.fileContent, mimetype: photo.mimetype, filePath: photo.filePath }, { clientSideId: photo.clientSideId, blur: photo.blur, colors: photo.colors, faceRecognition: photo.faceRecognition, imageDescribe: photo.imageDescribe, imageClassification: photo.imageClassification, imageComparison: photo.imageComparison, size: photo.size, sourceResolutionX: photo.sourceResolutionX, sourceResolutionY: photo.sourceResolutionY, dateTaken: photo.dateTaken, latitude: photo.latitude, longitude: photo.longitude });
1411
+ const upload_result = await this.uploadPhotoToMediaviz(companyId, userId, projectTableName, photo.title, { fileContent: photo.fileContent, mimetype: photo.mimetype, filePath: photo.filePath }, { clientSideId: photo.clientSideId, blur: template?.blur, colors: template?.colors, faceRecognition: template?.face_recognition, imageDescribe: template?.image_describe, imageClassification: template?.image_classification, imageComparison: template?.image_comparison, size: photo.size, sourceResolutionX: photo.sourceResolutionX, sourceResolutionY: photo.sourceResolutionY, dateTaken: photo.dateTaken, latitude: photo.latitude, longitude: photo.longitude, ocr: template?.ocr });
1344
1412
 
1345
1413
  return upload_result;
1346
1414
  }
@@ -1407,13 +1475,6 @@
1407
1475
  return data;
1408
1476
  }
1409
1477
 
1410
- async getProjectThumbnail(tableName) {
1411
- this._ctx.requireTokens();
1412
- const path = `/api/v1/photos_project/${encodeURIComponent(tableName)}`;
1413
- const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1414
- return data;
1415
- }
1416
-
1417
1478
  async updatePhotoInProject({ tableName, photoId, photoData } = {}) {
1418
1479
  this._ctx.requireTokens();
1419
1480
  let path = `/api/v1/photos_update`;
@@ -1476,50 +1537,56 @@
1476
1537
  return data;
1477
1538
  }
1478
1539
 
1479
- async searchProjectPhotosText(projectTableName, searchText, size = undefined) {
1540
+ async searchProjectPhotosParametrized(projectTableName, { andSearchText, orSearchText, notSearchText, city, country, state, albums, dateMin, dateMax, dateNullAnd, dateNullOr, ascOrDesc, bestOfSimilarSetsOnly, splitByTier, lastId, limit } = {}) {
1480
1541
  this._ctx.requireTokens();
1481
- const path = `/api/v1/search/text/${encodeURIComponent(projectTableName)}/`;
1482
- const body = stripUndef$1({
1483
- search_text: searchText,
1484
- size: size,
1485
- });
1486
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1487
- return data;
1488
- }
1489
-
1490
- async searchProjectPhotosNaturalLanguage(projectTableName, searchText, size = undefined) {
1491
- this._ctx.requireTokens();
1492
- const path = `/api/v1/search/nl/${encodeURIComponent(projectTableName)}/`;
1493
- const body = stripUndef$1({
1494
- search_text: searchText,
1495
- size: size,
1496
- });
1497
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1542
+ let path = `/api/v1/search/parametrized/${encodeURIComponent(projectTableName)}/`;
1543
+ const query = new URLSearchParams();
1544
+ if (andSearchText !== undefined) (Array.isArray(andSearchText) ? andSearchText : [andSearchText]).forEach(v => query.append('and_search_text', v));
1545
+ if (orSearchText !== undefined) (Array.isArray(orSearchText) ? orSearchText : [orSearchText]).forEach(v => query.append('or_search_text', v));
1546
+ if (notSearchText !== undefined) (Array.isArray(notSearchText) ? notSearchText : [notSearchText]).forEach(v => query.append('not_search_text', v));
1547
+ if (city !== undefined) (Array.isArray(city) ? city : [city]).forEach(v => query.append('city', v));
1548
+ if (country !== undefined) (Array.isArray(country) ? country : [country]).forEach(v => query.append('country', v));
1549
+ if (state !== undefined) (Array.isArray(state) ? state : [state]).forEach(v => query.append('state', v));
1550
+ if (albums !== undefined) (Array.isArray(albums) ? albums : [albums]).forEach(v => query.append('albums', v));
1551
+ if (dateMin !== undefined) (Array.isArray(dateMin) ? dateMin : [dateMin]).forEach(v => query.append('date_min', v));
1552
+ if (dateMax !== undefined) (Array.isArray(dateMax) ? dateMax : [dateMax]).forEach(v => query.append('date_max', v));
1553
+ if (dateNullAnd !== undefined) (Array.isArray(dateNullAnd) ? dateNullAnd : [dateNullAnd]).forEach(v => query.append('date_null_and', v));
1554
+ if (dateNullOr !== undefined) (Array.isArray(dateNullOr) ? dateNullOr : [dateNullOr]).forEach(v => query.append('date_null_or', v));
1555
+ if (ascOrDesc !== undefined) (Array.isArray(ascOrDesc) ? ascOrDesc : [ascOrDesc]).forEach(v => query.append('asc_or_desc', v));
1556
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1557
+ if (splitByTier !== undefined) (Array.isArray(splitByTier) ? splitByTier : [splitByTier]).forEach(v => query.append('split_by_tier', v));
1558
+ if (lastId !== undefined) (Array.isArray(lastId) ? lastId : [lastId]).forEach(v => query.append('last_id', v));
1559
+ if (limit !== undefined) (Array.isArray(limit) ? limit : [limit]).forEach(v => query.append('limit', v));
1560
+ const qs = query.toString();
1561
+ if (qs) path += '?' + qs;
1562
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1498
1563
  return data;
1499
1564
  }
1500
1565
 
1501
- async searchProjectPhotosNaturalLanguageHybrid(projectTableName, searchText, size = undefined, { blend, minCosine } = {}) {
1566
+ async recallProjectPhotos(projectTableName, { ascOrDesc, bestOfSimilarSetsOnly, lastId, limit } = {}) {
1502
1567
  this._ctx.requireTokens();
1503
- let path = `/api/v1/search/nl_hybrid/${encodeURIComponent(projectTableName)}/`;
1568
+ let path = `/api/v1/search/recall/${encodeURIComponent(projectTableName)}/`;
1504
1569
  const query = new URLSearchParams();
1505
- if (blend !== undefined) (Array.isArray(blend) ? blend : [blend]).forEach(v => query.append('blend', v));
1506
- if (minCosine !== undefined) (Array.isArray(minCosine) ? minCosine : [minCosine]).forEach(v => query.append('min_cosine', v));
1570
+ if (ascOrDesc !== undefined) (Array.isArray(ascOrDesc) ? ascOrDesc : [ascOrDesc]).forEach(v => query.append('asc_or_desc', v));
1571
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1572
+ if (lastId !== undefined) (Array.isArray(lastId) ? lastId : [lastId]).forEach(v => query.append('last_id', v));
1573
+ if (limit !== undefined) (Array.isArray(limit) ? limit : [limit]).forEach(v => query.append('limit', v));
1507
1574
  const qs = query.toString();
1508
1575
  if (qs) path += '?' + qs;
1509
- const body = stripUndef$1({
1510
- search_text: searchText,
1511
- size: size,
1512
- });
1513
- const { data } = await this._ctx.client.request(path, 'POST', this._ctx.accessToken, this._ctx.refreshToken, body);
1576
+ const { data } = await this._ctx.client.request(path, 'GET', this._ctx.accessToken, this._ctx.refreshToken);
1514
1577
  return data;
1515
1578
  }
1516
1579
 
1517
- async searchProjectPhotosNaturalLanguageAuto(projectTableName, searchText, size = undefined, { blend, minCosine } = {}) {
1580
+ async searchProjectPhotosNaturalLanguage(projectTableName, searchText, size = undefined, { minCosine, labelMinCosine, labelTopK, labelDelta, bestOfSimilarSetsOnly, splitByTier } = {}) {
1518
1581
  this._ctx.requireTokens();
1519
- let path = `/api/v1/search/auto/${encodeURIComponent(projectTableName)}/`;
1582
+ let path = `/api/v1/search/natural_language/${encodeURIComponent(projectTableName)}/`;
1520
1583
  const query = new URLSearchParams();
1521
- if (blend !== undefined) (Array.isArray(blend) ? blend : [blend]).forEach(v => query.append('blend', v));
1522
1584
  if (minCosine !== undefined) (Array.isArray(minCosine) ? minCosine : [minCosine]).forEach(v => query.append('min_cosine', v));
1585
+ if (labelMinCosine !== undefined) (Array.isArray(labelMinCosine) ? labelMinCosine : [labelMinCosine]).forEach(v => query.append('label_min_cosine', v));
1586
+ if (labelTopK !== undefined) (Array.isArray(labelTopK) ? labelTopK : [labelTopK]).forEach(v => query.append('label_top_k', v));
1587
+ if (labelDelta !== undefined) (Array.isArray(labelDelta) ? labelDelta : [labelDelta]).forEach(v => query.append('label_delta', v));
1588
+ if (bestOfSimilarSetsOnly !== undefined) (Array.isArray(bestOfSimilarSetsOnly) ? bestOfSimilarSetsOnly : [bestOfSimilarSetsOnly]).forEach(v => query.append('best_of_similar_sets_only', v));
1589
+ if (splitByTier !== undefined) (Array.isArray(splitByTier) ? splitByTier : [splitByTier]).forEach(v => query.append('split_by_tier', v));
1523
1590
  const qs = query.toString();
1524
1591
  if (qs) path += '?' + qs;
1525
1592
  const body = stripUndef$1({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediaviz/sdk",
3
- "version": "1.3.0",
3
+ "version": "1.5.0-dev.81",
4
4
  "description": "MediaViz JavaScript SDK \u2014 auto-generated public endpoint client.",
5
5
  "license": "MIT",
6
6
  "repository": {