@civitai/blocks-react 0.67.0 → 0.68.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/README.md +386 -13
- package/dist/hooks/useBatchEstimate.d.ts +190 -0
- package/dist/hooks/useBatchEstimate.js +195 -0
- package/dist/hooks/useBlockAnalytics.d.ts +46 -4
- package/dist/hooks/useBlockAnalytics.js +41 -4
- package/dist/hooks/useBuzzWorkflow.d.ts +4 -1
- package/dist/hooks/useBuzzWorkflow.js +4 -1
- package/dist/hooks/useResourcePicker.d.ts +98 -37
- package/dist/hooks/useResourcePicker.js +77 -7
- package/dist/hooks/useSharedStorage.d.ts +24 -0
- package/dist/hooks/useSharedStorage.js +55 -0
- package/dist/hooks/useUploadImageBytes.d.ts +2 -6
- package/dist/hooks/useUploadImageBytes.js +2 -6
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/internal/liveHost.d.ts +27 -2
- package/dist/internal/liveHost.js +133 -15
- package/dist/internal/mockHost.d.ts +81 -2
- package/dist/internal/mockHost.js +308 -78
- package/dist/internal/pickerOverlay.d.ts +32 -0
- package/dist/internal/pickerOverlay.js +99 -2
- package/dist/internal/trackEventCheck.d.ts +34 -0
- package/dist/internal/trackEventCheck.js +194 -0
- package/dist/transport/requestTimeouts.js +3 -0
- package/dist/transport/validate.d.ts +49 -6
- package/dist/transport/validate.js +132 -28
- package/package.json +4 -4
|
@@ -115,7 +115,12 @@ export function openPickerOverlay(opts) {
|
|
|
115
115
|
let grid = null;
|
|
116
116
|
let statusEl = null;
|
|
117
117
|
let searchEl = null;
|
|
118
|
+
let commitBtn = null;
|
|
118
119
|
let sentinel = null;
|
|
120
|
+
// --- Multi-select state. `staged` is in STAGING order — that order is the
|
|
121
|
+
// reply's order, so it is an array and never a Set keyed for lookup. ---
|
|
122
|
+
const multiMax = opts.multiple && opts.resultChannel === 'RESOURCE_PICKER_RESULT' ? opts.multiple.max : null;
|
|
123
|
+
let staged = [];
|
|
119
124
|
let observer = null;
|
|
120
125
|
// Separate observer that gates each thumbnail's `<img src>` (see observeThumb).
|
|
121
126
|
let thumbObserver = null;
|
|
@@ -197,6 +202,7 @@ export function openPickerOverlay(opts) {
|
|
|
197
202
|
grid = null;
|
|
198
203
|
statusEl = null;
|
|
199
204
|
searchEl = null;
|
|
205
|
+
commitBtn = null;
|
|
200
206
|
};
|
|
201
207
|
const resolve = (selection) => {
|
|
202
208
|
if (resolved)
|
|
@@ -205,7 +211,52 @@ export function openPickerOverlay(opts) {
|
|
|
205
211
|
teardown();
|
|
206
212
|
opts.onResolve(selection);
|
|
207
213
|
};
|
|
214
|
+
/** Repaint the staged outlines + the commit button from `staged`. */
|
|
215
|
+
const paintStaged = () => {
|
|
216
|
+
if (grid) {
|
|
217
|
+
for (const cell of Array.from(grid.querySelectorAll('[data-picker-card]'))) {
|
|
218
|
+
const id = Number(cell.getAttribute('data-picker-card'));
|
|
219
|
+
const isStaged = staged.some((c) => c.versionId === id);
|
|
220
|
+
cell.setAttribute('aria-pressed', String(isStaged));
|
|
221
|
+
cell.style.outline = isStaged ? '2px solid #5ec8a0' : '';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (commitBtn) {
|
|
225
|
+
commitBtn.disabled = staged.length === 0;
|
|
226
|
+
commitBtn.textContent =
|
|
227
|
+
staged.length === 0 ? `Pick up to ${multiMax}` : `Add ${staged.length} of ${multiMax}`;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
/** Multi-select: stage `card`, or un-stage it. Never exceeds `multiMax`. */
|
|
231
|
+
const toggleStaged = (card) => {
|
|
232
|
+
if (multiMax === null || resolved)
|
|
233
|
+
return;
|
|
234
|
+
if (staged.some((c) => c.versionId === card.versionId)) {
|
|
235
|
+
staged = staged.filter((c) => c.versionId !== card.versionId);
|
|
236
|
+
}
|
|
237
|
+
else if (staged.length < multiMax) {
|
|
238
|
+
staged = [...staged, card];
|
|
239
|
+
}
|
|
240
|
+
paintStaged();
|
|
241
|
+
};
|
|
242
|
+
const commitStaged = () => {
|
|
243
|
+
if (multiMax === null || staged.length === 0)
|
|
244
|
+
return;
|
|
245
|
+
resolve({
|
|
246
|
+
channel: 'RESOURCE_PICKER_RESULT',
|
|
247
|
+
selectedResources: staged.map((card) => cardToResource(card, opts.type)),
|
|
248
|
+
});
|
|
249
|
+
};
|
|
208
250
|
const selectCard = (card) => {
|
|
251
|
+
// Multi-select: a programmatic "select" is the one-resource batch — still a
|
|
252
|
+
// LIST reply, because the reply shape follows the REQUEST, not the count.
|
|
253
|
+
if (multiMax !== null) {
|
|
254
|
+
resolve({
|
|
255
|
+
channel: 'RESOURCE_PICKER_RESULT',
|
|
256
|
+
selectedResources: [cardToResource(card, opts.type)],
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
209
260
|
// 🔴 BRANCH ON THE REPLY CHANNEL, NOT ON `opts.type` (#391). The channel is
|
|
210
261
|
// what the block's consumers read the payload as; the requested type is
|
|
211
262
|
// only what the catalog was filtered by. Branching on the type sent a
|
|
@@ -244,6 +295,17 @@ export function openPickerOverlay(opts) {
|
|
|
244
295
|
dismiss() {
|
|
245
296
|
resolve(null);
|
|
246
297
|
},
|
|
298
|
+
toggleStagedByVersionId(versionId) {
|
|
299
|
+
const found = cards.find((c) => c.versionId === versionId);
|
|
300
|
+
if (found)
|
|
301
|
+
toggleStaged(found);
|
|
302
|
+
},
|
|
303
|
+
get stagedVersionIds() {
|
|
304
|
+
return staged.map((c) => c.versionId);
|
|
305
|
+
},
|
|
306
|
+
commitStaged() {
|
|
307
|
+
commitStaged();
|
|
308
|
+
},
|
|
247
309
|
close() {
|
|
248
310
|
resolve(null);
|
|
249
311
|
},
|
|
@@ -312,7 +374,17 @@ export function openPickerOverlay(opts) {
|
|
|
312
374
|
Object.assign(meta.style, META_STYLE);
|
|
313
375
|
meta.textContent = [card.baseModel, card.versionName].filter(Boolean).join(' · ');
|
|
314
376
|
cell.appendChild(meta);
|
|
315
|
-
|
|
377
|
+
if (multiMax !== null) {
|
|
378
|
+
// A rebuilt grid (new search) keeps what is already staged marked.
|
|
379
|
+
const isStaged = staged.some((c) => c.versionId === card.versionId);
|
|
380
|
+
cell.setAttribute('aria-pressed', String(isStaged));
|
|
381
|
+
if (isStaged)
|
|
382
|
+
cell.style.outline = '2px solid #5ec8a0';
|
|
383
|
+
cell.addEventListener('click', () => toggleStaged(card));
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
cell.addEventListener('click', () => selectCard(card));
|
|
387
|
+
}
|
|
316
388
|
return cell;
|
|
317
389
|
};
|
|
318
390
|
/**
|
|
@@ -524,7 +596,11 @@ export function openPickerOverlay(opts) {
|
|
|
524
596
|
const title = doc.createElement('div');
|
|
525
597
|
Object.assign(title.style, TITLE_STYLE);
|
|
526
598
|
title.textContent =
|
|
527
|
-
|
|
599
|
+
multiMax !== null
|
|
600
|
+
? `Pick up to ${multiMax} LoRAs (dev:live)`
|
|
601
|
+
: opts.type === 'LORA'
|
|
602
|
+
? 'Pick a LoRA (dev:live)'
|
|
603
|
+
: 'Pick a checkpoint (dev:live)';
|
|
528
604
|
const closeBtn = doc.createElement('button');
|
|
529
605
|
closeBtn.type = 'button';
|
|
530
606
|
closeBtn.setAttribute('aria-label', 'Close picker');
|
|
@@ -563,6 +639,16 @@ export function openPickerOverlay(opts) {
|
|
|
563
639
|
modal.appendChild(searchEl);
|
|
564
640
|
modal.appendChild(statusEl);
|
|
565
641
|
modal.appendChild(grid);
|
|
642
|
+
if (multiMax !== null) {
|
|
643
|
+
// Multi-select commit bar. A card click only stages; this resolves.
|
|
644
|
+
commitBtn = doc.createElement('button');
|
|
645
|
+
commitBtn.type = 'button';
|
|
646
|
+
commitBtn.setAttribute('data-picker-commit', '');
|
|
647
|
+
Object.assign(commitBtn.style, COMMIT_STYLE);
|
|
648
|
+
commitBtn.addEventListener('click', () => commitStaged());
|
|
649
|
+
modal.appendChild(commitBtn);
|
|
650
|
+
paintStaged();
|
|
651
|
+
}
|
|
566
652
|
root.appendChild(modal);
|
|
567
653
|
// Escape = dismiss.
|
|
568
654
|
root.addEventListener('keydown', (e) => {
|
|
@@ -611,6 +697,17 @@ const TITLE_STYLE = {
|
|
|
611
697
|
fontSize: '15px',
|
|
612
698
|
fontWeight: '600',
|
|
613
699
|
};
|
|
700
|
+
const COMMIT_STYLE = {
|
|
701
|
+
margin: '10px 14px 14px',
|
|
702
|
+
padding: '8px 14px',
|
|
703
|
+
alignSelf: 'flex-end',
|
|
704
|
+
border: '1px solid #2f6f5a',
|
|
705
|
+
borderRadius: '6px',
|
|
706
|
+
background: '#1f4a3c',
|
|
707
|
+
color: '#e6edf3',
|
|
708
|
+
font: 'inherit',
|
|
709
|
+
cursor: 'pointer',
|
|
710
|
+
};
|
|
614
711
|
const CLOSE_STYLE = {
|
|
615
712
|
background: 'transparent',
|
|
616
713
|
border: 'none',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `dev:live` check of a `TRACK_EVENT` against the manifest's
|
|
3
|
+
* `analytics.events` declaration.
|
|
4
|
+
*
|
|
5
|
+
* Production stores an event only when the APPROVED manifest declares it, and
|
|
6
|
+
* keeps only the declared properties whose value has the declared type. An
|
|
7
|
+
* undeclared event is counted as a drop and its name is not stored; an
|
|
8
|
+
* undeclared property, or a value of the wrong type, is dropped from the row.
|
|
9
|
+
* None of that reaches the block: `TRACK_EVENT` is fire-and-forget, so a typo
|
|
10
|
+
* in an event name is invisible until an owner wonders why the analytics panel
|
|
11
|
+
* is empty. This check makes the same three decisions locally and returns one
|
|
12
|
+
* actionable message per DISTINCT problem, so the console says it once.
|
|
13
|
+
*
|
|
14
|
+
* It is a dev aid, not the contract. The server is authoritative, and the
|
|
15
|
+
* dev server's manifest validation (the schema) is what reports a malformed
|
|
16
|
+
* declaration in detail; this reader only needs enough structure to look a
|
|
17
|
+
* name up.
|
|
18
|
+
*
|
|
19
|
+
* Total: never throws, whatever the manifest or the payload holds.
|
|
20
|
+
*/
|
|
21
|
+
export interface TrackEventChecker {
|
|
22
|
+
/**
|
|
23
|
+
* The messages for this event that have not been returned before. Empty for a
|
|
24
|
+
* declared, valid event, and for any problem already reported.
|
|
25
|
+
*/
|
|
26
|
+
check(payload: unknown): string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @param manifest The app's manifest as the harness imported it
|
|
30
|
+
* (`import manifest from '../block.manifest.json'`). `undefined` = the harness
|
|
31
|
+
* passed none, so nothing can be checked; that is said once.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createTrackEventChecker(manifest: unknown): TrackEventChecker;
|
|
34
|
+
//# sourceMappingURL=trackEventCheck.d.ts.map
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `dev:live` check of a `TRACK_EVENT` against the manifest's
|
|
3
|
+
* `analytics.events` declaration.
|
|
4
|
+
*
|
|
5
|
+
* Production stores an event only when the APPROVED manifest declares it, and
|
|
6
|
+
* keeps only the declared properties whose value has the declared type. An
|
|
7
|
+
* undeclared event is counted as a drop and its name is not stored; an
|
|
8
|
+
* undeclared property, or a value of the wrong type, is dropped from the row.
|
|
9
|
+
* None of that reaches the block: `TRACK_EVENT` is fire-and-forget, so a typo
|
|
10
|
+
* in an event name is invisible until an owner wonders why the analytics panel
|
|
11
|
+
* is empty. This check makes the same three decisions locally and returns one
|
|
12
|
+
* actionable message per DISTINCT problem, so the console says it once.
|
|
13
|
+
*
|
|
14
|
+
* It is a dev aid, not the contract. The server is authoritative, and the
|
|
15
|
+
* dev server's manifest validation (the schema) is what reports a malformed
|
|
16
|
+
* declaration in detail; this reader only needs enough structure to look a
|
|
17
|
+
* name up.
|
|
18
|
+
*
|
|
19
|
+
* Total: never throws, whatever the manifest or the payload holds.
|
|
20
|
+
*/
|
|
21
|
+
/** Where every message points the author. */
|
|
22
|
+
const WHERE = '`analytics.events` in block.manifest.json';
|
|
23
|
+
/** The event/property name rule (the manifest schema's `patternProperties` key). */
|
|
24
|
+
const NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
25
|
+
function isPlainObject(value) {
|
|
26
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
/** A name or value as it appears in a message: JSON-quoted and bounded. */
|
|
29
|
+
function quote(value) {
|
|
30
|
+
let text;
|
|
31
|
+
try {
|
|
32
|
+
text = typeof value === 'string' ? value : String(JSON.stringify(value));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
text = Object.prototype.toString.call(value);
|
|
36
|
+
}
|
|
37
|
+
if (text.length > 80)
|
|
38
|
+
text = `${text.slice(0, 80)}…`;
|
|
39
|
+
return typeof value === 'string' ? JSON.stringify(text) : text;
|
|
40
|
+
}
|
|
41
|
+
/** Reads the declaration, or says why it cannot. An unreadable one declares nothing. */
|
|
42
|
+
function readDeclarations(manifest) {
|
|
43
|
+
const none = new Map();
|
|
44
|
+
if (!isPlainObject(manifest))
|
|
45
|
+
return { declarations: none, problem: 'the manifest is not an object' };
|
|
46
|
+
const analytics = manifest.analytics;
|
|
47
|
+
if (analytics === undefined)
|
|
48
|
+
return { declarations: none };
|
|
49
|
+
if (!isPlainObject(analytics))
|
|
50
|
+
return { declarations: none, problem: '`analytics` is not an object' };
|
|
51
|
+
const events = analytics.events;
|
|
52
|
+
if (events === undefined)
|
|
53
|
+
return { declarations: none };
|
|
54
|
+
if (!isPlainObject(events))
|
|
55
|
+
return { declarations: none, problem: '`analytics.events` is not an object' };
|
|
56
|
+
const out = new Map();
|
|
57
|
+
for (const [eventName, rawEvent] of Object.entries(events)) {
|
|
58
|
+
if (!isPlainObject(rawEvent)) {
|
|
59
|
+
return { declarations: none, problem: `event ${quote(eventName)} is not an object` };
|
|
60
|
+
}
|
|
61
|
+
const props = new Map();
|
|
62
|
+
const rawProps = rawEvent.properties;
|
|
63
|
+
if (rawProps !== undefined && !isPlainObject(rawProps)) {
|
|
64
|
+
return { declarations: none, problem: `${quote(eventName)}.properties is not an object` };
|
|
65
|
+
}
|
|
66
|
+
for (const [propName, rawProp] of Object.entries(rawProps ?? {})) {
|
|
67
|
+
const at = `${quote(eventName)}.properties.${propName}`;
|
|
68
|
+
if (!isPlainObject(rawProp))
|
|
69
|
+
return { declarations: none, problem: `${at} is not an object` };
|
|
70
|
+
if (rawProp.type === 'number' || rawProp.type === 'boolean') {
|
|
71
|
+
props.set(propName, { type: rawProp.type });
|
|
72
|
+
}
|
|
73
|
+
else if (rawProp.type === 'enum') {
|
|
74
|
+
const values = rawProp.values;
|
|
75
|
+
if (!Array.isArray(values) || !values.every((v) => typeof v === 'string')) {
|
|
76
|
+
return { declarations: none, problem: `${at}.values is not an array of strings` };
|
|
77
|
+
}
|
|
78
|
+
props.set(propName, { type: 'enum', values: new Set(values) });
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
return {
|
|
82
|
+
declarations: none,
|
|
83
|
+
problem: `${at}.type is ${quote(rawProp.type)}, not one of enum, number, boolean`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
out.set(eventName, props);
|
|
88
|
+
}
|
|
89
|
+
return { declarations: out };
|
|
90
|
+
}
|
|
91
|
+
/** What a declared property accepts, for a message. */
|
|
92
|
+
function describeDeclared(prop) {
|
|
93
|
+
if (prop.type === 'enum')
|
|
94
|
+
return `one of ${[...prop.values].map((v) => quote(v)).join(', ')}`;
|
|
95
|
+
return prop.type === 'number' ? 'a finite number' : 'a boolean';
|
|
96
|
+
}
|
|
97
|
+
/** Whether production keeps `value` for `prop` (mirrors the ingest's classification). */
|
|
98
|
+
function accepts(prop, value) {
|
|
99
|
+
if (prop.type === 'enum')
|
|
100
|
+
return typeof value === 'string' && prop.values.has(value);
|
|
101
|
+
if (prop.type === 'number')
|
|
102
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
103
|
+
return typeof value === 'boolean';
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* @param manifest The app's manifest as the harness imported it
|
|
107
|
+
* (`import manifest from '../block.manifest.json'`). `undefined` = the harness
|
|
108
|
+
* passed none, so nothing can be checked; that is said once.
|
|
109
|
+
*/
|
|
110
|
+
export function createTrackEventChecker(manifest) {
|
|
111
|
+
const reported = new Set();
|
|
112
|
+
const once = (key, message, out) => {
|
|
113
|
+
if (reported.has(key))
|
|
114
|
+
return;
|
|
115
|
+
reported.add(key);
|
|
116
|
+
out.push(message);
|
|
117
|
+
};
|
|
118
|
+
const hasManifest = manifest !== undefined;
|
|
119
|
+
let read = { declarations: new Map() };
|
|
120
|
+
if (hasManifest) {
|
|
121
|
+
try {
|
|
122
|
+
read = readDeclarations(manifest);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Reachable only via a throwing getter or Proxy; an imported JSON file cannot get here.
|
|
126
|
+
read = { declarations: new Map(), problem: 'reading it threw' };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const { declarations, problem } = read;
|
|
130
|
+
const declaresAny = declarations.size > 0;
|
|
131
|
+
return {
|
|
132
|
+
check(payload) {
|
|
133
|
+
const out = [];
|
|
134
|
+
try {
|
|
135
|
+
if (!hasManifest) {
|
|
136
|
+
once('no-manifest', 'TRACK_EVENT is not checked against your manifest: pass `manifest` to createLiveHost ' +
|
|
137
|
+
"(import it from block.manifest.json) to be warned here about events production will drop.", out);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
if (problem) {
|
|
141
|
+
once('unreadable', `${WHERE} could not be read (${problem}). Production accepts events only against a valid ` +
|
|
142
|
+
'declaration, so it would record none of them; the dev server\'s manifest validation names the problem.', out);
|
|
143
|
+
}
|
|
144
|
+
const body = isPlainObject(payload) ? payload : {};
|
|
145
|
+
const eventName = body.eventName;
|
|
146
|
+
if (typeof eventName !== 'string') {
|
|
147
|
+
once('bad-name', `track() was called with a non-string event name (${quote(eventName)}). Production counts it as a dropped event.`, out);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
const declaredProps = declarations.get(eventName);
|
|
151
|
+
if (!declaredProps) {
|
|
152
|
+
const hint = NAME_RE.test(eventName)
|
|
153
|
+
? ''
|
|
154
|
+
: ' (event names are lowercase snake_case: a letter, then up to 63 of a-z, 0-9 or _)';
|
|
155
|
+
const declared = declaresAny || problem ? '' : ' Your manifest declares no events yet.';
|
|
156
|
+
once(`event:${eventName}`, `track(${quote(eventName)}) is not declared in ${WHERE}${hint}. Production counts it as a dropped ` +
|
|
157
|
+
`event and does not store its name or properties.${declared} Declare it to record it.`, out);
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
const props = body.properties;
|
|
161
|
+
if (props === undefined || props === null)
|
|
162
|
+
return out;
|
|
163
|
+
if (!isPlainObject(props)) {
|
|
164
|
+
once(`props-shape:${eventName}`, `track(${quote(eventName)}) was sent properties that are not an object (${quote(props)}). ` +
|
|
165
|
+
'Production keeps none of them.', out);
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
for (const [propName, value] of Object.entries(props)) {
|
|
169
|
+
// JSON drops an `undefined` value, so the host never forwards one.
|
|
170
|
+
if (value === undefined)
|
|
171
|
+
continue;
|
|
172
|
+
const declared = declaredProps.get(propName);
|
|
173
|
+
if (!declared) {
|
|
174
|
+
once(`prop:${eventName}.${propName}`, `track(${quote(eventName)}) property ${quote(propName)} is not declared under ` +
|
|
175
|
+
`${quote(eventName)}.properties in ${WHERE}. Production drops it from the event.`, out);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!accepts(declared, value)) {
|
|
179
|
+
once(`value:${eventName}.${propName}`, `track(${quote(eventName)}) property ${quote(propName)} is ${quote(value)}, but ${WHERE} ` +
|
|
180
|
+
`declares it as ${declared.type} (${describeDeclared(declared)}). Production drops the value.` +
|
|
181
|
+
(typeof value === 'string' && declared.type === 'enum'
|
|
182
|
+
? ' Free-text strings are never stored: only the declared enum values are.'
|
|
183
|
+
: ''), out);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// A throwing getter or Proxy in a payload. The check is advisory: say nothing.
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=trackEventCheck.js.map
|
|
@@ -131,6 +131,9 @@ const REQUEST_TIMEOUT_CLASS = {
|
|
|
131
131
|
// longer bound (`WORKFLOW_REQUEST_TIMEOUT_MS` in `hooks/useBuzzWorkflow.ts`)
|
|
132
132
|
// for orchestrator latency — a different reason for a different number.
|
|
133
133
|
ESTIMATE_WORKFLOW: 'protocol',
|
|
134
|
+
// The batch twin of ESTIMATE_WORKFLOW: no person in the loop, up to 16 cost
|
|
135
|
+
// quotes behind one reply. Sent under the same `WORKFLOW_REQUEST_TIMEOUT_MS`.
|
|
136
|
+
ESTIMATE_WORKFLOW_BATCH: 'protocol',
|
|
134
137
|
POLL_WORKFLOW: 'protocol',
|
|
135
138
|
SUBMIT_WORKFLOW: 'protocol',
|
|
136
139
|
GET_BUZZ_ACCOUNTS: 'protocol',
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
* helpers. Do not open-code the test again: the two spellings coexisted in one
|
|
54
54
|
* file for a release precisely because it was inline at every call site.
|
|
55
55
|
*/
|
|
56
|
-
import type { BlockInitPayload, BlockWorkflowSnapshot, Theme, WrappedToken } from '@civitai/app-sdk/blocks';
|
|
56
|
+
import type { BlockInitPayload, BlockWorkflowSnapshot, SharedStorageRateLimit, Theme, WrappedToken } from '@civitai/app-sdk/blocks';
|
|
57
57
|
/**
|
|
58
58
|
* Shape check for the wrapped-token shape carried by `BLOCK_INIT.token`,
|
|
59
59
|
* `TOKEN_REFRESH.payload.token`, and `TOKEN_REFRESH_RESPONSE.payload.token`.
|
|
@@ -175,6 +175,24 @@ export declare function isValidWorkflowReply(p: unknown): p is {
|
|
|
175
175
|
snapshot: BlockWorkflowSnapshot;
|
|
176
176
|
requestId?: string;
|
|
177
177
|
};
|
|
178
|
+
/**
|
|
179
|
+
* Reply to a block-initiated `ESTIMATE_WORKFLOW_BATCH`. A well-formed reply
|
|
180
|
+
* carries EITHER an `error` string OR `snapshots` + `aggregate`.
|
|
181
|
+
*
|
|
182
|
+
* 🔴 A PRESENT `error` IS ACCEPTED ON ITS OWN, unlike the single-estimate reply
|
|
183
|
+
* ({@link isValidWorkflowReply}, which requires a snapshot). That is what lets
|
|
184
|
+
* the host's generic `{ requestId, error: 'unsupported on this host' }` reach
|
|
185
|
+
* `useBatchEstimate`, which turns it into the typed error an app falls back to
|
|
186
|
+
* per-cell estimates on. Dropping it would leave the block waiting out the
|
|
187
|
+
* timeout instead. `error` is shape-checked only: the channel carries free-text
|
|
188
|
+
* server messages.
|
|
189
|
+
*
|
|
190
|
+
* Without an `error`, EVERY cell must be a valid workflow snapshot and the
|
|
191
|
+
* aggregate must hold three finite numbers. One malformed cell rejects the whole
|
|
192
|
+
* reply, the same way one malformed snapshot rejects a single-estimate reply:
|
|
193
|
+
* the block dereferences these fields.
|
|
194
|
+
*/
|
|
195
|
+
export declare function isValidEstimateBatchResult(p: unknown): boolean;
|
|
178
196
|
export declare function isValidBuzzPurchaseResult(p: unknown): p is {
|
|
179
197
|
purchased: boolean;
|
|
180
198
|
newBalance?: number;
|
|
@@ -494,6 +512,24 @@ export declare function isValidSharedAppendResult(p: unknown): boolean;
|
|
|
494
512
|
* error path is `{ error }`. Uniform `{ ok, error }` contract (module header).
|
|
495
513
|
*/
|
|
496
514
|
export declare function isValidSharedWithdrawResult(p: unknown): boolean;
|
|
515
|
+
/**
|
|
516
|
+
* Validate the `rateLimit` a host put on a shared-storage refusal. Returns a
|
|
517
|
+
* copy holding ONLY the two known fields, or `undefined` when `value` is
|
|
518
|
+
* missing or malformed.
|
|
519
|
+
*
|
|
520
|
+
* Kept in lockstep with the platform's own parser for the same metadata
|
|
521
|
+
* (`parseRateLimitMeta` in civitai's `src/shared/utils/rate-limit-meta.ts`):
|
|
522
|
+
* `retryAfterSeconds` must be an integer of at least 1, and `retryAt` a string
|
|
523
|
+
* `Date.parse` can read. Anything else — a wrong type, a negative or fractional
|
|
524
|
+
* number, a missing field, a non-object — is malformed.
|
|
525
|
+
*
|
|
526
|
+
* 🔴 NOT A VALIDATOR, deliberately. None of the `isValidShared*Result`
|
|
527
|
+
* predicates look at `rateLimit`, so a malformed one can never DROP a reply:
|
|
528
|
+
* dropping it would hang the block's request to its timeout over an optional
|
|
529
|
+
* hint. The reply is delivered unchanged, and `useSharedStorage` calls this
|
|
530
|
+
* parser: a malformed value is ignored and the plain refusal error is thrown.
|
|
531
|
+
*/
|
|
532
|
+
export declare function parseSharedStorageRateLimit(value: unknown): SharedStorageRateLimit | undefined;
|
|
497
533
|
/**
|
|
498
534
|
* Reply to `OPEN_CHECKPOINT_PICKER`. `selected` is ABSENT when the user
|
|
499
535
|
* dismissed the picker (the hook resolves `{ selected: undefined }`); when
|
|
@@ -501,11 +537,18 @@ export declare function isValidSharedWithdrawResult(p: unknown): boolean;
|
|
|
501
537
|
*/
|
|
502
538
|
export declare function isValidCheckpointPickerResult(p: unknown): boolean;
|
|
503
539
|
/**
|
|
504
|
-
* Reply to `OPEN_RESOURCE_PICKER`.
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
540
|
+
* Reply to `OPEN_RESOURCE_PICKER`.
|
|
541
|
+
*
|
|
542
|
+
* Single pick: `selected` is ABSENT on dismiss (the hook resolves `null`); when
|
|
543
|
+
* present it is a `BlockResourceInfo` projection.
|
|
544
|
+
*
|
|
545
|
+
* Multi-select: `selectedResources` is an array of the same projection (`[]` on
|
|
546
|
+
* dismiss). ONE malformed entry drops the whole reply — each `versionId` is
|
|
547
|
+
* money-adjacent, and handing the caller the well-formed remainder would
|
|
548
|
+
* silently change which resources a generation runs with.
|
|
549
|
+
*
|
|
550
|
+
* `error` (a refusal) is not validated here: the hook reads it by PRESENCE, and
|
|
551
|
+
* a reply carrying only `{ requestId, error }` must reach it.
|
|
509
552
|
*/
|
|
510
553
|
export declare function isValidResourcePickerResult(p: unknown): boolean;
|
|
511
554
|
/**
|