@instruments/materialgraph 0.33.0 → 0.36.0
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/association-types.d.ts +11 -11
- package/associations-client.d.ts +7 -7
- package/associations-client.js +5 -5
- package/brand-types.d.ts +37 -41
- package/brand-types.js +3 -5
- package/brands-client.d.ts +22 -9
- package/brands-client.js +20 -6
- package/by-spec-types.d.ts +1 -1
- package/catalog-discovery-types.d.ts +3 -3
- package/catalogue-client.d.ts +2 -50
- package/catalogue-client.js +2 -10
- package/catalogue-types.d.ts +1 -1
- package/categories-client.d.ts +3 -5
- package/categories-client.js +1 -1
- package/category-types.d.ts +17 -30
- package/citation-types.d.ts +2 -2
- package/client-vocabulary.d.ts +2 -2
- package/client-vocabulary.js +4 -1
- package/client.d.ts +62 -105
- package/client.js +68 -106
- package/color-profiles-types.d.ts +70 -0
- package/colors-client.d.ts +76 -0
- package/colors-client.js +52 -0
- package/find-sampled-together-types.d.ts +70 -0
- package/{find-companions-types.js → find-sampled-together-types.js} +4 -4
- package/{find-variants-by-colour-types.d.ts → find-variants-by-color-types.d.ts} +70 -64
- package/find-variants-by-vector-types.d.ts +4 -1
- package/get-variant-types.d.ts +14 -2
- package/http-transport.d.ts +1 -1
- package/http-transport.js +1 -2
- package/identifiers.d.ts +2 -2
- package/identifiers.js +1 -1
- package/index.d.ts +5 -14
- package/index.js +1 -3
- package/list-variant-facets-types.d.ts +4 -4
- package/list-variants-types.d.ts +71 -21
- package/material-asset-types.d.ts +3 -3
- package/material-block-types.d.ts +9 -9
- package/material-types.d.ts +10 -8
- package/operations.d.ts +2 -2
- package/operations.js +19 -25
- package/package.json +2 -2
- package/{resolve-colour-types.d.ts → resolve-color-types.d.ts} +18 -18
- package/response-schemas.generated.d.ts +2 -2
- package/response-schemas.generated.js +41 -71
- package/response-schemas.js +1 -1
- package/samplize-catalogue-types.d.ts +4 -4
- package/samplize-colorvec.js +6 -6
- package/{corpus-scope-types.d.ts → search-catalogue-subset-types.d.ts} +3 -3
- package/search-variants-types.d.ts +33 -2
- package/types.d.ts +32 -284
- package/webmcp.generated.js +4 -9
- package/attribution-types.d.ts +0 -51
- package/attribution-types.js +0 -13
- package/by-colour-types.d.ts +0 -320
- package/colour-region-types.d.ts +0 -18
- package/colours-client.d.ts +0 -70
- package/colours-client.js +0 -38
- package/find-companions-types.d.ts +0 -70
- package/material-query-plan-types.d.ts +0 -70
- package/material-query-plan-types.js +0 -1
- package/ndjson.d.ts +0 -46
- package/ndjson.js +0 -111
- package/palette-search-types.d.ts +0 -171
- package/palette-search-types.js +0 -1
- package/query-understanding-types.d.ts +0 -254
- package/query-understanding-types.js +0 -1
- package/resolve-colour-types.js +0 -1
- package/search-card-types.d.ts +0 -86
- package/search-card-types.js +0 -1
- package/search-index-plan-types.d.ts +0 -74
- package/search-index-plan-types.js +0 -1
- package/search-refinement-types.d.ts +0 -103
- package/search-refinement-types.js +0 -2
- package/search-request-types.d.ts +0 -170
- package/search-request-types.js +0 -1
- package/search-response-receipt-types.d.ts +0 -24
- package/search-response-receipt-types.js +0 -1
- package/search-response-types.d.ts +0 -11
- package/search-response-types.js +0 -1
- package/search-result-validation.d.ts +0 -9
- package/search-result-validation.js +0 -83
- package/spec-types.d.ts +0 -109
- package/spec-types.js +0 -54
- package/specifications.d.ts +0 -34
- package/specifications.js +0 -56
- package/validation.d.ts +0 -43
- package/validation.js +0 -117
- /package/{by-colour-types.js → color-profiles-types.js} +0 -0
- /package/{colour-region-types.js → find-variants-by-color-types.js} +0 -0
- /package/{corpus-scope-types.js → resolve-color-types.js} +0 -0
- /package/{find-variants-by-colour-types.js → search-catalogue-subset-types.js} +0 -0
package/client.js
CHANGED
|
@@ -3,8 +3,8 @@ import { listBrands } from "./brands-client.js";
|
|
|
3
3
|
import { CatalogueClient } from "./catalogue-client.js";
|
|
4
4
|
import { CategoriesClient } from "./categories-client.js";
|
|
5
5
|
import { DEFAULT_BASE_URL } from "./client-config.js";
|
|
6
|
-
import { listAttributes as listAttributesCall, listEnumValues as listEnumValuesCall,
|
|
7
|
-
import {
|
|
6
|
+
import { listAttributes as listAttributesCall, listEnumValues as listEnumValuesCall, resolveColor as resolveColorCall, } from "./client-vocabulary.js";
|
|
7
|
+
import { ColorsClient } from "./colors-client.js";
|
|
8
8
|
import { MaterialGraphError } from "./errors.js";
|
|
9
9
|
import { HttpTransport } from "./http-transport.js";
|
|
10
10
|
import { listVariantAssets as listVariantAssetsCall, materialBlocks as materialBlocksCall, materialDocuments as materialDocumentsCall, } from "./material-resources-client.js";
|
|
@@ -23,25 +23,27 @@ export * from "./client-config.js";
|
|
|
23
23
|
*
|
|
24
24
|
* | The question | The lens |
|
|
25
25
|
* | --- | --- |
|
|
26
|
-
* | "find me something
|
|
26
|
+
* | "find me something matching these words" | {@link MaterialGraphClient.searchVariants} |
|
|
27
|
+
* | "find me something that looks or feels like this" | {@link MaterialGraphClient.findVariantsByVector} |
|
|
27
28
|
* | "tell me about THIS variant" | {@link MaterialGraphClient.getVariant} |
|
|
28
29
|
* | "which materials, from anyone, meet this hard spec?" | {@link MaterialGraphClient.listVariants} |
|
|
29
|
-
* | "what's near
|
|
30
|
-
* | "what
|
|
31
|
-
* | "what
|
|
30
|
+
* | "what's near a color, or a palette of colors?" | {@link MaterialGraphClient.findVariantsByColor} (`mode: "palette"`) |
|
|
31
|
+
* | "what colors does this category come in?" | `colors.profiles()` |
|
|
32
|
+
* | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.listVariants} (same product, other colorways) |
|
|
33
|
+
* | "what does the brand say is AVAILABLE on this?" | {@link MaterialGraphClient.associatedFinishes}, {@link MaterialGraphClient.associatedProducts} |
|
|
32
34
|
* | "what can I even ask for?" | {@link MaterialGraphClient.listVariantFacets}, `categories.list()` |
|
|
33
35
|
* | "what is this word called here?" | {@link MaterialGraphClient.listAttributes} |
|
|
34
36
|
* | "do the whole job for me, async" | `workflows.start()` + `mg:compose` |
|
|
35
37
|
*
|
|
36
38
|
* Three structural rules that fall out of that table:
|
|
37
|
-
* - **
|
|
38
|
-
* WORDS. A
|
|
39
|
-
* materials that look it. Route visual intent to the
|
|
39
|
+
* - **Color is a separate lens.** {@link MaterialGraphClient.searchVariants}
|
|
40
|
+
* matches WORDS. A color word in `query` finds materials whose text mentions
|
|
41
|
+
* it, not materials that look it. Route visual intent to the color lenses.
|
|
40
42
|
* - **Relationships hang off a material, not off a query.** You cannot ask "what
|
|
41
43
|
* pairs with beige oak"; you resolve a material first, then ask what pairs
|
|
42
44
|
* with THAT `variantId`.
|
|
43
45
|
* - **Declared is not inferred.** `associatedFinishes()` /
|
|
44
|
-
* `associatedProducts()` return compatibility the
|
|
46
|
+
* `associatedProducts()` return compatibility the BRAND stated;
|
|
45
47
|
* `pairings()` returns companionship the graph INFERRED. The response shapes
|
|
46
48
|
* share no field, on purpose — render them as separate sections and never
|
|
47
49
|
* concatenate them.
|
|
@@ -53,10 +55,10 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
53
55
|
/**
|
|
54
56
|
* THE QUESTION: "what kinds of thing exist, and what are their keys?"
|
|
55
57
|
* `categories.list()` pages the tree (roots, one node's children, or a
|
|
56
|
-
* name
|
|
58
|
+
* name search), and `categories.get(id)` reads one node in full:
|
|
57
59
|
* its place, the names that find it, and the canonical taxonomy concepts it
|
|
58
60
|
* relates to. The source of every category id the other lenses accept
|
|
59
|
-
* (`
|
|
61
|
+
* (`listVariants({ filters })`, `findVariantsByColor({ categoryIds })`).
|
|
60
62
|
*
|
|
61
63
|
* A category id is a TREE KEY, never a canonical `product_category` value:
|
|
62
64
|
* the two look alike and are not interchangeable. Discover ids here, never
|
|
@@ -65,17 +67,19 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
65
67
|
*/
|
|
66
68
|
categories;
|
|
67
69
|
/**
|
|
68
|
-
* THE QUESTION: "what
|
|
69
|
-
* of
|
|
70
|
+
* THE QUESTION: "what colors does this category come in?" (`profiles`).
|
|
71
|
+
* `profiles` is an aggregate over a category, not a list of materials, and
|
|
72
|
+
* carries a `basis` naming the snapshot it read.
|
|
70
73
|
*
|
|
71
|
-
* Instead use {@link MaterialGraphClient.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
74
|
+
* Instead use {@link MaterialGraphClient.findVariantsByColor} (`mode:
|
|
75
|
+
* "palette"`) for the materials near one or several colors and
|
|
76
|
+
* {@link MaterialGraphClient.listVariants} to page through a color's family.
|
|
77
|
+
* Color is usually derived from product imagery, which is visual likeness,
|
|
78
|
+
* not a color specification. A brand's declared color, such as a paint's
|
|
75
79
|
* published hex, wins where MaterialGraph holds one, and each value's
|
|
76
|
-
*
|
|
80
|
+
* value origin says which.
|
|
77
81
|
*/
|
|
78
|
-
|
|
82
|
+
colors;
|
|
79
83
|
/**
|
|
80
84
|
* THE QUESTION: "do a whole job for me" — the ASYNC lens. `start()` begins a
|
|
81
85
|
* Run of one registered workflow, `run()` polls it, `streamRunEvents()` and
|
|
@@ -100,9 +104,8 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
100
104
|
this.categories = new CategoriesClient({
|
|
101
105
|
get: async (operationId, query, signal, pathParams) => await this.#get(operationId, query, signal, pathParams),
|
|
102
106
|
});
|
|
103
|
-
this.
|
|
107
|
+
this.colors = new ColorsClient({
|
|
104
108
|
get: async (operationId, query, signal) => await this.#get(operationId, query, signal),
|
|
105
|
-
post: async (operationId, body, signal) => await this.#post(operationId, body, signal),
|
|
106
109
|
});
|
|
107
110
|
this.workflows = new WorkflowsClient({ transport });
|
|
108
111
|
}
|
|
@@ -123,46 +126,6 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
123
126
|
signal,
|
|
124
127
|
});
|
|
125
128
|
}
|
|
126
|
-
async search(input, signal) {
|
|
127
|
-
return await this.#post("search_materials_post", input, signal);
|
|
128
|
-
}
|
|
129
|
-
/** Whole materials ask → inspectable meanings and complete safe search requests. */
|
|
130
|
-
async planQuery(query, signal) {
|
|
131
|
-
return await this.#post("plan_material_query", { query }, signal);
|
|
132
|
-
}
|
|
133
|
-
async searchGet(params, signal) {
|
|
134
|
-
return await this.#get("search_materials_get", { ...params }, signal);
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* THE QUESTION: "what materials are near THIS colour?" — one colour (or a small
|
|
138
|
-
* palette), ranked by OKLab closeness to colour extracted from product imagery.
|
|
139
|
-
*
|
|
140
|
-
* Instead use `colours.palette()` for N colours at stated proportions (a
|
|
141
|
-
* different ranking, not a wrapper),
|
|
142
|
-
* {@link MaterialGraphClient.resolveColour} first if you hold a NAME,
|
|
143
|
-
* {@link MaterialGraphClient.listVariants} to page through a category or a brand, and
|
|
144
|
-
* {@link MaterialGraphClient.search} when colour is one signal among many.
|
|
145
|
-
* Report matches as visual likeness, never a stated colour specification.
|
|
146
|
-
* `family` gates to ONE colorscope family (a gated page can be short); `brand` gates to named brands — one brand's nearest colour to another's.
|
|
147
|
-
*/
|
|
148
|
-
async byColour(params, signal) {
|
|
149
|
-
return await this.#get("search_by_colour", {
|
|
150
|
-
a: params.a,
|
|
151
|
-
b: params.b,
|
|
152
|
-
brand: params.brand,
|
|
153
|
-
category: params.category,
|
|
154
|
-
colour: params.colour,
|
|
155
|
-
corpusScope: params.corpusScope,
|
|
156
|
-
family: params.family,
|
|
157
|
-
hex: params.hex,
|
|
158
|
-
l: params.l,
|
|
159
|
-
limit: params.limit,
|
|
160
|
-
maxColourDistance: params.maxColourDistance,
|
|
161
|
-
order: params.order,
|
|
162
|
-
q: params.q,
|
|
163
|
-
variantId: params.variantId,
|
|
164
|
-
}, signal);
|
|
165
|
-
}
|
|
166
129
|
// ── Materials ──────────────────────────────────────────────────────────────
|
|
167
130
|
/**
|
|
168
131
|
* THE QUESTION: "tell me about THIS variant" (GET
|
|
@@ -174,10 +137,10 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
174
137
|
* product, brand, category, image and page. Name `attributes` and `blocks`
|
|
175
138
|
* to add their values under `specifications`; a null value is unknown, never
|
|
176
139
|
* false or zero. Instead use {@link MaterialGraphClient.listVariants} for many
|
|
177
|
-
* ids, or for the other
|
|
140
|
+
* ids, or for the other colorways of the same product (ask `listVariants`
|
|
178
141
|
* with a `product in` filter),
|
|
179
142
|
* {@link MaterialGraphClient.materialBlocks} to tell "no value" from "not
|
|
180
|
-
* checked", {@link MaterialGraphClient.
|
|
143
|
+
* checked", {@link MaterialGraphClient.searchVariants} when you have no id yet.
|
|
181
144
|
*
|
|
182
145
|
* @param variantId Opaque — pass back exactly what you were given.
|
|
183
146
|
*/
|
|
@@ -212,11 +175,11 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
212
175
|
* off one resolved material (GET `/api/v1/materials/{variantId}/pairings`).
|
|
213
176
|
*
|
|
214
177
|
* Instead use {@link MaterialGraphClient.listVariants} for the same product in
|
|
215
|
-
* another
|
|
178
|
+
* another color, and {@link MaterialGraphClient.findVariantsByVector} for a LIKE-THIS substitute —
|
|
216
179
|
* similar and complementary are opposite questions.
|
|
217
180
|
*
|
|
218
181
|
* READ `item.source` BEFORE PHRASING: the rail mixes three tiers of evidence —
|
|
219
|
-
* `cospec` (real orders), `considered` (board activity), `
|
|
182
|
+
* `cospec` (real orders), `considered` (board activity), `color` (palette
|
|
220
183
|
* look-alike). Only `cospec` supports "sampled together". `cospecCount` tells
|
|
221
184
|
* you how much of the rail is real order evidence.
|
|
222
185
|
*/
|
|
@@ -225,11 +188,11 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
225
188
|
variantId,
|
|
226
189
|
});
|
|
227
190
|
}
|
|
228
|
-
/** "What finishes does the
|
|
191
|
+
/** "What finishes does the brand declare for THIS product?" - {@link declaredFinishes}. */
|
|
229
192
|
async associatedFinishes(variantId, options, signal) {
|
|
230
193
|
return await assoc.declaredFinishes(this.#transport, variantId, options, signal);
|
|
231
194
|
}
|
|
232
|
-
/** "What products does the
|
|
195
|
+
/** "What products does the brand declare THIS finish for?" - {@link declaredProducts}. */
|
|
233
196
|
async associatedProducts(variantId, options, signal) {
|
|
234
197
|
return await assoc.declaredProducts(this.#transport, variantId, options, signal);
|
|
235
198
|
}
|
|
@@ -240,17 +203,19 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
240
203
|
* THE QUESTION: "eligible variants matching these hard filters, ranked and
|
|
241
204
|
* cursor-paged" (GET `/api/v1/variants`, MG-2222) — the one read behind
|
|
242
205
|
* `list_variants`, hydrating ids you already hold (`{terms: [{field:
|
|
243
|
-
* "variant", op: "in", values: [...]}]}`, up to 100), the other
|
|
206
|
+
* "variant", op: "in", values: [...]}]}`, up to 100), the other colorways
|
|
244
207
|
* of a product (`{terms: [{field: "product", op: "in", values:
|
|
245
208
|
* [productId]}, {field: "variant", op: "not_in", values: [variantId]}]}` —
|
|
246
|
-
* `filters` is kind-grouped, `{terms, numbers, presence}`, every
|
|
247
|
-
* across all
|
|
209
|
+
* `filters` is kind-grouped, `{terms, numbers, presence, anyField}`, every
|
|
210
|
+
* condition across all four ANDed, 20 at most), or a category/brand listing. An explicit
|
|
248
211
|
* `variant in` id that does not resolve to an eligible variant is
|
|
249
|
-
* reported in `unresolvedVariantIds
|
|
250
|
-
* it names a
|
|
251
|
-
*
|
|
212
|
+
* reported in `unresolvedVariantIds` when it names no variant, in
|
|
213
|
+
* `unavailableVariantIds` when it names a catalogue variant this list
|
|
214
|
+
* cannot show in the requested scope (a retired one, for example), or in
|
|
215
|
+
* `excludedBySearchCatalogueSubset` when it names a real variant outside the
|
|
216
|
+
* materials catalogue, never silently dropped from the page.
|
|
252
217
|
*
|
|
253
|
-
* `filters`/`optionalFilters`/`orderBy`/`
|
|
218
|
+
* `filters`/`optionalFilters`/`orderBy`/`colors` are sent as URL-encoded
|
|
254
219
|
* JSON; `attributes`/`include`/`words` as comma-separated lists — the server
|
|
255
220
|
* validates, this method does not duplicate that check.
|
|
256
221
|
*/
|
|
@@ -260,10 +225,8 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
260
225
|
? input.attributes.join(",")
|
|
261
226
|
: undefined,
|
|
262
227
|
collapse: input.collapse,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
: undefined,
|
|
266
|
-
corpusScope: input.corpusScope,
|
|
228
|
+
colors: input.colors?.length ? JSON.stringify(input.colors) : undefined,
|
|
229
|
+
searchCatalogueSubset: input.searchCatalogueSubset,
|
|
267
230
|
cursor: input.cursor,
|
|
268
231
|
filters: input.filters ? JSON.stringify(input.filters) : undefined,
|
|
269
232
|
include: input.include?.length ? input.include.join(",") : undefined,
|
|
@@ -283,7 +246,8 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
283
246
|
* and how many of each" (POST `/api/v1/variants/facets`, MG-2294) — the
|
|
284
247
|
* options and counts a filter UI shows beside a {@link
|
|
285
248
|
* MaterialGraphClient.listVariants} page. `filters` is the same tree
|
|
286
|
-
* `listVariants()` takes
|
|
249
|
+
* `listVariants()` takes, less its `orText` and `unstated` additions; the
|
|
250
|
+
* server validates both.
|
|
287
251
|
*/
|
|
288
252
|
async listVariantFacets(input, signal) {
|
|
289
253
|
return await this.#post("list_variant_facets", input, signal);
|
|
@@ -307,45 +271,44 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
307
271
|
}
|
|
308
272
|
/**
|
|
309
273
|
* THE QUESTION: "which variants hold, resemble, or can be ranked by, this
|
|
310
|
-
*
|
|
311
|
-
* One required `mode`: `contains` (a swatch for every required `
|
|
312
|
-
* condition), `palette` (whole-palette closeness to `
|
|
274
|
+
* color?" (POST `/api/v1/variants/by-color`, MG-2217/MG-2230/MG-2247).
|
|
275
|
+
* One required `mode`: `contains` (a swatch for every required `colors[]`
|
|
276
|
+
* condition), `palette` (whole-palette closeness to `colors[]`, an
|
|
313
277
|
* image's extracted palette, or `targetVariantId`'s own palette) or
|
|
314
|
-
* `rerank` (reorder a held `variantIds` set by
|
|
315
|
-
* {@link MaterialGraphClient.byColour} instead for ONE colour against the
|
|
316
|
-
* search index rather than the canonical variant graph.
|
|
278
|
+
* `rerank` (reorder a held `variantIds` set by color closeness).
|
|
317
279
|
*/
|
|
318
|
-
async
|
|
319
|
-
return await this.#post("
|
|
280
|
+
async findVariantsByColor(input, signal) {
|
|
281
|
+
return await this.#post("find_variants_by_color", input, signal);
|
|
320
282
|
}
|
|
321
283
|
/**
|
|
322
284
|
* THE QUESTION: "what did designers sample together with these?" (POST
|
|
323
|
-
* `/api/v1/variants/
|
|
285
|
+
* `/api/v1/variants/sampled-together`, MG-2332). Give `variantIds` and/or
|
|
324
286
|
* `materials` (free-text names, one variant each, at most twelve together);
|
|
325
|
-
*
|
|
326
|
-
* `seedMatchCount`, `relation` and a one-line `reason`. `projectType` reads one
|
|
327
|
-
* sector; `include` adds `image` and `
|
|
287
|
+
* the materials come back other brands first, then the most projects, each
|
|
288
|
+
* with its `lift`, `seedMatchCount`, `relation` and a one-line `reason`. `projectType` reads one
|
|
289
|
+
* sector; `include` adds `image` and `color` fields a lean page leaves out.
|
|
328
290
|
* A sample order shows sampling, never specification. Read `unresolved`
|
|
329
291
|
* before presenting: a scheme of four that resolved two is a different claim.
|
|
330
292
|
*/
|
|
331
|
-
async
|
|
332
|
-
return await this.#post("
|
|
293
|
+
async findSampledTogether(input, signal) {
|
|
294
|
+
return await this.#post("find_sampled_together", input, signal);
|
|
333
295
|
}
|
|
334
296
|
/**
|
|
335
297
|
* THE QUESTION: "what looks or feels like this?" (POST
|
|
336
298
|
* `/api/v1/variants/by-vector`, MG-2231). Eligible variants ranked by
|
|
337
299
|
* semantic or visual similarity to a text description (mood words count),
|
|
338
300
|
* an image reference, or both fused into one embedding. `filters` is the
|
|
339
|
-
* same tree {@link MaterialGraphClient.listVariants} takes
|
|
301
|
+
* same tree {@link MaterialGraphClient.listVariants} takes, less its `orText`
|
|
302
|
+
* and `unstated` additions. `query.mode`
|
|
340
303
|
* values other than `"text"` embed through Voyage and spend the shared
|
|
341
304
|
* per-key `embed` rate budget.
|
|
342
305
|
*/
|
|
343
306
|
async findVariantsByVector(input, signal) {
|
|
344
307
|
return await this.#post("find_variants_by_vector", input, signal);
|
|
345
308
|
}
|
|
346
|
-
/** Resolve a
|
|
347
|
-
async
|
|
348
|
-
return await
|
|
309
|
+
/** Resolve a color name to its hex, and the named paint's catalogue variant. */
|
|
310
|
+
async resolveColor(name, signal, options) {
|
|
311
|
+
return await resolveColorCall(this.#transport, name, signal, options);
|
|
349
312
|
}
|
|
350
313
|
// ── Catalog ──────────────────────────────────────────────────────────────────
|
|
351
314
|
/**
|
|
@@ -373,8 +336,8 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
373
336
|
async listEnumValues(options, signal) {
|
|
374
337
|
return await listEnumValuesCall(this.#transport, options, signal);
|
|
375
338
|
}
|
|
376
|
-
// Hard-specification search (formerly `bySpec()
|
|
377
|
-
//
|
|
339
|
+
// Hard-specification search (formerly `bySpec()`) moved to
|
|
340
|
+
// {@link MaterialGraphClient.listVariants}
|
|
378
341
|
// (MG-2222): each `attribute:value` criterion is one `filters` condition —
|
|
379
342
|
// `{field: attributeId, op: "in", values: [...]}` in `filters.terms` for a
|
|
380
343
|
// term, `{field: attributeId, op: "gte"/"lte"/etc, value}` in
|
|
@@ -390,13 +353,12 @@ export class MaterialGraphClient extends CatalogueClient {
|
|
|
390
353
|
async whoami(signal) {
|
|
391
354
|
return await this.#get("whoami", undefined, signal);
|
|
392
355
|
}
|
|
393
|
-
// ──
|
|
356
|
+
// ── Color helpers (extract) ─────────────────────────────────────────────────
|
|
394
357
|
/**
|
|
395
|
-
* THE QUESTION: "what
|
|
396
|
-
* materials. The bridge from an inspiration image into the
|
|
397
|
-
* here, then feed the swatches to
|
|
398
|
-
* {@link MaterialGraphClient.
|
|
399
|
-
|
|
358
|
+
* THE QUESTION: "what colors are in this image?" — the palette only, no
|
|
359
|
+
* materials. The bridge from an inspiration image into the color lens: extract
|
|
360
|
+
* here, then feed the swatches to
|
|
361
|
+
* {@link MaterialGraphClient.findVariantsByColor} (`mode: "palette"`).
|
|
400
362
|
*/
|
|
401
363
|
async extractPalette(input, signal) {
|
|
402
364
|
return await this.#post("extract_image_palette", input, signal);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for the category color profile read - GET
|
|
3
|
+
* `/api/v1/colors/profiles`.
|
|
4
|
+
*
|
|
5
|
+
* Kept out of `types.ts` because the shared mirror is at its source-size
|
|
6
|
+
* budget. Same tolerant-parsing contract as
|
|
7
|
+
* the rest of the mirror - response enums are widened so a new server value
|
|
8
|
+
* still type-checks.
|
|
9
|
+
*/
|
|
10
|
+
import type { OpenEnum } from "./types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Which taxonomy tier a profile's `categoryKey` names, or `"merged"` where the
|
|
13
|
+
* key is a composite over a caller-defined key set rather than a taxonomy node.
|
|
14
|
+
*/
|
|
15
|
+
export type CategoryTier = OpenEnum<"group" | "type" | "merged">;
|
|
16
|
+
/** One representative color bucket of a category. */
|
|
17
|
+
export type CategoryColor = {
|
|
18
|
+
hex: string;
|
|
19
|
+
oklab: {
|
|
20
|
+
l: number;
|
|
21
|
+
a: number;
|
|
22
|
+
b: number;
|
|
23
|
+
};
|
|
24
|
+
variantCount: number;
|
|
25
|
+
/** This bucket's share of the selection's colored variants, 0 to 1. */
|
|
26
|
+
proportion: number;
|
|
27
|
+
};
|
|
28
|
+
export type CategoryColorProfile = {
|
|
29
|
+
categoryKey: string;
|
|
30
|
+
level: CategoryTier;
|
|
31
|
+
name: string;
|
|
32
|
+
path: string;
|
|
33
|
+
/**
|
|
34
|
+
* Under `groupBy: "merged"`, the requested keys in composite-key order, so
|
|
35
|
+
* `categoryKey` never has to be split. Absent on a single-category profile.
|
|
36
|
+
*/
|
|
37
|
+
members?: string[];
|
|
38
|
+
variantCount: number;
|
|
39
|
+
avgLightness: number;
|
|
40
|
+
avgChroma: number;
|
|
41
|
+
colors: CategoryColor[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The snapshot a profile response was computed from. `variantCount` is the
|
|
45
|
+
* size of the whole materialized view across every category, not of the
|
|
46
|
+
* selection; `refreshedAt` is its age.
|
|
47
|
+
*/
|
|
48
|
+
export type CategoryColorProfilesBasis = {
|
|
49
|
+
variantCount: number;
|
|
50
|
+
refreshedAt: string | null;
|
|
51
|
+
};
|
|
52
|
+
export type CategoryColorProfilesResponse = {
|
|
53
|
+
profiles: CategoryColorProfile[];
|
|
54
|
+
basis: CategoryColorProfilesBasis;
|
|
55
|
+
};
|
|
56
|
+
/** Request parameters for `colors.profiles()`. */
|
|
57
|
+
export type ColorProfilesParams = {
|
|
58
|
+
/** 1 to 8 category keys, at the tier named by `groupBy`. */
|
|
59
|
+
productTypeIds: readonly string[];
|
|
60
|
+
/**
|
|
61
|
+
* `"type"` (the default) names product types. `"group"` merges each group's
|
|
62
|
+
* leaf product types into one palette; `"merged"` merges EVERY key's leaves
|
|
63
|
+
* into a single combined palette.
|
|
64
|
+
*/
|
|
65
|
+
groupBy?: "group" | "type" | "merged";
|
|
66
|
+
/** Representative colors per category, 1 to 64. The server defaults to 8. */
|
|
67
|
+
topN?: number;
|
|
68
|
+
/** `"diverse"` maximises visual distinctness instead of frequency. */
|
|
69
|
+
select?: "frequency" | "diverse";
|
|
70
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { CategoryColorProfilesResponse, ColorProfilesParams } from "./color-profiles-types.js";
|
|
2
|
+
import type { QueryParams } from "./http-transport.js";
|
|
3
|
+
import type { SdkOperationId } from "./operations.js";
|
|
4
|
+
/**
|
|
5
|
+
* The color surface, namespaced as `mg.colors` - `profiles`, the colors a
|
|
6
|
+
* category comes in.
|
|
7
|
+
*
|
|
8
|
+
* A namespace rather than a flat method on an already-large client facade.
|
|
9
|
+
*
|
|
10
|
+
* The namespace holds no transport of its own: the client hands it a bound
|
|
11
|
+
* `get`, so auth, rate-limit accounting and error shaping stay in one place.
|
|
12
|
+
*
|
|
13
|
+
* `profiles` is an aggregate and carries a `basis`: it reads a materialized
|
|
14
|
+
* view refreshed on a schedule, not on write, so check how much of the corpus
|
|
15
|
+
* the answer covers before treating it as complete.
|
|
16
|
+
*
|
|
17
|
+
* ROUTING OUT OF THIS NAMESPACE. Color is a lens, and this is not the only
|
|
18
|
+
* door into it:
|
|
19
|
+
* - One or several colors → `mg.findVariantsByColor({ mode: "palette", colors })`.
|
|
20
|
+
* - A color NAME rather than a hex → `mg.resolveColor(name)` first.
|
|
21
|
+
* - Color WORDS (families, moods, undertones) → `mg.listEnumValues()` for the
|
|
22
|
+
* terms, `mg.listVariantFacets()` for how many variants carry each.
|
|
23
|
+
* - Walking the corpus by lightness/vividness → `mg.listVariants({ orderBy })`.
|
|
24
|
+
* - Color as one signal among words → `mg.searchVariants(...)`.
|
|
25
|
+
*
|
|
26
|
+
* And the honesty rule the whole lens rests on: every color here is DERIVED
|
|
27
|
+
* FROM PRODUCT IMAGERY, not declared by the brand. Report visual
|
|
28
|
+
* likeness, never a stated color specification.
|
|
29
|
+
*/
|
|
30
|
+
/** The client's own request primitives, handed down so this file owns no transport. */
|
|
31
|
+
export type ColorsTransport = {
|
|
32
|
+
get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
|
|
33
|
+
};
|
|
34
|
+
export declare class ColorsClient {
|
|
35
|
+
#private;
|
|
36
|
+
constructor(transport: ColorsTransport);
|
|
37
|
+
/**
|
|
38
|
+
* THE QUESTION: "what colors does this CATEGORY come in?" - the
|
|
39
|
+
* representative palette of 1 to 8 categories in ONE call (GET
|
|
40
|
+
* `/api/v1/colors/profiles`).
|
|
41
|
+
*
|
|
42
|
+
* An aggregate over a category, not a list of materials: use
|
|
43
|
+
* `mg.findVariantsByColor()` or `mg.searchVariants()` when you want the
|
|
44
|
+
* materials themselves. Check `basis` -
|
|
45
|
+
* this reads a scheduled materialized view, so it can lag the catalogue.
|
|
46
|
+
*
|
|
47
|
+
* `groupBy: "group"` takes product-type GROUP keys and merges every leaf type
|
|
48
|
+
* under a group into one histogram BEFORE ranking - a group's palette, not
|
|
49
|
+
* the union of its leaves' heads. `select: "diverse"` returns visually spread
|
|
50
|
+
* swatches (greedy max-min over OKLab, 0.02 minimum separation, backfilled by
|
|
51
|
+
* frequency if fewer survive) instead of the most frequent ones; a client
|
|
52
|
+
* cannot recover that spread from a truncated frequency list, which is why it
|
|
53
|
+
* is a server mode rather than a local sort.
|
|
54
|
+
*
|
|
55
|
+
* `groupBy: "merged"` merges across the KEYS as well and returns ONE profile
|
|
56
|
+
* over the union of their leaves - the palette of a caller-defined facet such
|
|
57
|
+
* as "Wood" (`wood_surface` + `wood_flooring`) that the taxonomy holds no
|
|
58
|
+
* single node for, and that no combination of per-key responses reconstructs.
|
|
59
|
+
* Its keys may sit at either tier, `categoryKey` is those keys sorted and
|
|
60
|
+
* joined with `+`, and `members` hands them back so nothing has to split it.
|
|
61
|
+
*
|
|
62
|
+
* A selected category with no color data comes back as a zero-count profile,
|
|
63
|
+
* not a missing key. Category keys come from `mg.categories.list()`.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const { profiles, basis } = await mg.colors.profiles({
|
|
67
|
+
* productTypeIds: ["wood_surface", "wood_flooring"],
|
|
68
|
+
* groupBy: "merged",
|
|
69
|
+
* select: "diverse",
|
|
70
|
+
* topN: 12,
|
|
71
|
+
* });
|
|
72
|
+
* // profiles[0].members is ["wood_flooring", "wood_surface"]; check
|
|
73
|
+
* // basis.refreshedAt before presenting the palette as current.
|
|
74
|
+
*/
|
|
75
|
+
profiles(params: ColorProfilesParams, signal?: AbortSignal): Promise<CategoryColorProfilesResponse>;
|
|
76
|
+
}
|
package/colors-client.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class ColorsClient {
|
|
2
|
+
#transport;
|
|
3
|
+
constructor(transport) {
|
|
4
|
+
this.#transport = transport;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* THE QUESTION: "what colors does this CATEGORY come in?" - the
|
|
8
|
+
* representative palette of 1 to 8 categories in ONE call (GET
|
|
9
|
+
* `/api/v1/colors/profiles`).
|
|
10
|
+
*
|
|
11
|
+
* An aggregate over a category, not a list of materials: use
|
|
12
|
+
* `mg.findVariantsByColor()` or `mg.searchVariants()` when you want the
|
|
13
|
+
* materials themselves. Check `basis` -
|
|
14
|
+
* this reads a scheduled materialized view, so it can lag the catalogue.
|
|
15
|
+
*
|
|
16
|
+
* `groupBy: "group"` takes product-type GROUP keys and merges every leaf type
|
|
17
|
+
* under a group into one histogram BEFORE ranking - a group's palette, not
|
|
18
|
+
* the union of its leaves' heads. `select: "diverse"` returns visually spread
|
|
19
|
+
* swatches (greedy max-min over OKLab, 0.02 minimum separation, backfilled by
|
|
20
|
+
* frequency if fewer survive) instead of the most frequent ones; a client
|
|
21
|
+
* cannot recover that spread from a truncated frequency list, which is why it
|
|
22
|
+
* is a server mode rather than a local sort.
|
|
23
|
+
*
|
|
24
|
+
* `groupBy: "merged"` merges across the KEYS as well and returns ONE profile
|
|
25
|
+
* over the union of their leaves - the palette of a caller-defined facet such
|
|
26
|
+
* as "Wood" (`wood_surface` + `wood_flooring`) that the taxonomy holds no
|
|
27
|
+
* single node for, and that no combination of per-key responses reconstructs.
|
|
28
|
+
* Its keys may sit at either tier, `categoryKey` is those keys sorted and
|
|
29
|
+
* joined with `+`, and `members` hands them back so nothing has to split it.
|
|
30
|
+
*
|
|
31
|
+
* A selected category with no color data comes back as a zero-count profile,
|
|
32
|
+
* not a missing key. Category keys come from `mg.categories.list()`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const { profiles, basis } = await mg.colors.profiles({
|
|
36
|
+
* productTypeIds: ["wood_surface", "wood_flooring"],
|
|
37
|
+
* groupBy: "merged",
|
|
38
|
+
* select: "diverse",
|
|
39
|
+
* topN: 12,
|
|
40
|
+
* });
|
|
41
|
+
* // profiles[0].members is ["wood_flooring", "wood_surface"]; check
|
|
42
|
+
* // basis.refreshedAt before presenting the palette as current.
|
|
43
|
+
*/
|
|
44
|
+
async profiles(params, signal) {
|
|
45
|
+
return await this.#transport.get("get_category_color_profiles", {
|
|
46
|
+
groupBy: params.groupBy,
|
|
47
|
+
productTypeIds: params.productTypeIds.join(","),
|
|
48
|
+
select: params.select,
|
|
49
|
+
topN: params.topN,
|
|
50
|
+
}, signal);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module find-sampled-together-types
|
|
3
|
+
* @description Hand-maintained mirror of `find_sampled_together`'s (MG-2332) input and
|
|
4
|
+
* output wire shapes. The source of truth is
|
|
5
|
+
* `packages/products/src/cospec/schema.ts` (`findSampledTogetherInputSchema`,
|
|
6
|
+
* `findSampledTogetherResultSchema`). This SDK has no RUNTIME dependency on
|
|
7
|
+
* `@materialgraph/products` (it's a devDependency only, for the parity tests in
|
|
8
|
+
* `__tests__/`), so the shapes are copied here rather than imported.
|
|
9
|
+
* `contract-parity.test.ts` keeps this mirror honest against the server types,
|
|
10
|
+
* the project-type list included.
|
|
11
|
+
*/
|
|
12
|
+
/** The closed sector vocabulary co-specification evidence is tagged with. */
|
|
13
|
+
export type SampledTogetherProjectType = "Workplace" | "Residential" | "Institution Education" | "Hospitality: Hotel" | "Mixed-Use & Multi-Unit Residential" | "Healthcare" | "Hospitality: Restaurant" | "Government" | "Senior Living" | "Retail" | "Other" | "Sports Entertainment" | "Wellness" | "Medical Dental" | "Aviation" | "Industrial" | "Tech" | "Transportation" | "Single Family" | "Condominium Cooperative" | "Marine" | "Power & Energy" | "University Design Program: Interior Design" | "Non-Billable" | "University Design Program: Interior Architecture" | "University Design Program: Architecture" | "Historic Preservation";
|
|
14
|
+
/** At least one of `variantIds` and `materials`, at most twelve together. */
|
|
15
|
+
export type FindSampledTogetherInput = {
|
|
16
|
+
/** Optional row fields: image adds imageUrl, color adds swatches. */
|
|
17
|
+
include?: ("image" | "color")[];
|
|
18
|
+
/** Ops and audit switch: leave sample cards in. Default false. */
|
|
19
|
+
includeSamplingArtifacts?: boolean;
|
|
20
|
+
/** Materials to return, default 20, at most 100. */
|
|
21
|
+
limit?: number;
|
|
22
|
+
/** Names to resolve when you hold no ids. Misses come back unresolved. */
|
|
23
|
+
materials?: string[];
|
|
24
|
+
/** Only materials sampled together in projects of this sector. */
|
|
25
|
+
projectType?: SampledTogetherProjectType;
|
|
26
|
+
/** Variants to find sampled-together materials for. */
|
|
27
|
+
variantIds?: string[];
|
|
28
|
+
};
|
|
29
|
+
export type FindSampledTogetherRow = {
|
|
30
|
+
brand: string;
|
|
31
|
+
/** Palette likeness to the seeds. Present with `include: ["color"]`; never reorders. */
|
|
32
|
+
colorFit?: number | null;
|
|
33
|
+
/** Present with `include: ["image"]`. */
|
|
34
|
+
imageUrl?: string | null;
|
|
35
|
+
/** Mean lift over the seeds it pairs with; above 1 is above average. */
|
|
36
|
+
lift: number;
|
|
37
|
+
product: string;
|
|
38
|
+
/** Why it was suggested, in one line. */
|
|
39
|
+
reason: string;
|
|
40
|
+
/** `other_brand` is another brand; `brand_sibling` and `colorway` share the seed's. */
|
|
41
|
+
relation: "other_brand" | "brand_sibling" | "colorway";
|
|
42
|
+
/** How many seeds it was sampled with. */
|
|
43
|
+
seedMatchCount: number;
|
|
44
|
+
/** Present with `include: ["color"]`. */
|
|
45
|
+
swatches?: {
|
|
46
|
+
hex: string;
|
|
47
|
+
proportion: number;
|
|
48
|
+
}[];
|
|
49
|
+
variantId: string;
|
|
50
|
+
variantName: string | null;
|
|
51
|
+
};
|
|
52
|
+
export type FindSampledTogetherOutput = {
|
|
53
|
+
/** Other brands first, then seeds matched, then lift weighted by projects; one row per product. */
|
|
54
|
+
sampledTogether: FindSampledTogetherRow[];
|
|
55
|
+
/** The seeds as resolved, so a caller sees what a name became. */
|
|
56
|
+
seeds: {
|
|
57
|
+
brand: string;
|
|
58
|
+
/** Present with `include: ["image"]`. */
|
|
59
|
+
imageUrl?: string | null;
|
|
60
|
+
product: string;
|
|
61
|
+
/** The id or name this seed came from. */
|
|
62
|
+
resolvedFrom: string;
|
|
63
|
+
variantId: string;
|
|
64
|
+
variantName: string | null;
|
|
65
|
+
}[];
|
|
66
|
+
/** Seed ids outside the materials catalogue; present only when at least one was. */
|
|
67
|
+
excludedBySearchCatalogueSubset?: string[];
|
|
68
|
+
/** References that matched no variant. */
|
|
69
|
+
unresolved: string[];
|
|
70
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @module find-
|
|
3
|
-
* @description Hand-maintained mirror of `
|
|
2
|
+
* @module find-sampled-together-types
|
|
3
|
+
* @description Hand-maintained mirror of `find_sampled_together`'s (MG-2332) input and
|
|
4
4
|
* output wire shapes. The source of truth is
|
|
5
|
-
* `packages/products/src/cospec/schema.ts` (`
|
|
6
|
-
* `
|
|
5
|
+
* `packages/products/src/cospec/schema.ts` (`findSampledTogetherInputSchema`,
|
|
6
|
+
* `findSampledTogetherResultSchema`). This SDK has no RUNTIME dependency on
|
|
7
7
|
* `@materialgraph/products` (it's a devDependency only, for the parity tests in
|
|
8
8
|
* `__tests__/`), so the shapes are copied here rather than imported.
|
|
9
9
|
* `contract-parity.test.ts` keeps this mirror honest against the server types,
|