@civitai/blocks-react 0.63.2 → 0.65.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 +188 -6
- package/dist/hooks/useBuzzWorkflow.d.ts +20 -0
- package/dist/hooks/useBuzzWorkflow.js +32 -12
- package/dist/hooks/useCreatePostFromApp.d.ts +7 -2
- package/dist/hooks/useCreatePostFromApp.js +8 -3
- package/dist/hooks/useNestedDocument.d.ts +73 -0
- package/dist/hooks/useNestedDocument.js +160 -0
- package/dist/hooks/usePrepareTrainingDataset.d.ts +99 -0
- package/dist/hooks/usePrepareTrainingDataset.js +157 -0
- package/dist/hooks/usePublishGenerationOutputs.d.ts +6 -0
- package/dist/hooks/usePublishGenerationOutputs.js +6 -0
- package/dist/hooks/useRunTraining.d.ts +151 -0
- package/dist/hooks/useRunTraining.js +212 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/internal/liveHost.d.ts +18 -2
- package/dist/internal/liveHost.js +89 -23
- package/dist/internal/mockHost.d.ts +146 -2
- package/dist/internal/mockHost.js +309 -0
- package/dist/transport/requestTimeouts.d.ts +19 -0
- package/dist/transport/requestTimeouts.js +30 -0
- package/dist/transport/validate.d.ts +27 -0
- package/dist/transport/validate.js +189 -0
- package/package.json +5 -5
|
@@ -255,8 +255,120 @@ export function isValidWorkflowSnapshot(s) {
|
|
|
255
255
|
return false;
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
+
// ---- The host fields mirrored in #524, plus the training-publish pair. ----
|
|
259
|
+
//
|
|
260
|
+
// Same policy as `autoClaim` above: each is OPTIONAL (an older host omits it
|
|
261
|
+
// and the snapshot stays valid), and a PRESENT but malformed value rejects
|
|
262
|
+
// the snapshot, because the block dereferences these fields.
|
|
263
|
+
//
|
|
264
|
+
// 🔴 ONE DELIBERATE DIFFERENCE: the open-ended string unions
|
|
265
|
+
// (`modelSubstitutions[].reason`, `trainedEpochs[].$type`, `toolCalls[].type`)
|
|
266
|
+
// are checked as STRINGS, not against their current members. A rejected snapshot is a dropped
|
|
267
|
+
// poll reply — the block's request hangs to its timeout — so pinning today's
|
|
268
|
+
// members here would turn the host adding a value into a fleet-wide hang in
|
|
269
|
+
// every already-shipped block bundle. The types document that unknown values
|
|
270
|
+
// may arrive.
|
|
271
|
+
if (s.modelSubstitutions !== undefined) {
|
|
272
|
+
if (!Array.isArray(s.modelSubstitutions))
|
|
273
|
+
return false;
|
|
274
|
+
for (const m of s.modelSubstitutions) {
|
|
275
|
+
if (!isObject(m))
|
|
276
|
+
return false;
|
|
277
|
+
if (!isFiniteNumber(m.requested) || !isFiniteNumber(m.applied))
|
|
278
|
+
return false;
|
|
279
|
+
if (typeof m.reason !== 'string')
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (s.textOutputs !== undefined) {
|
|
284
|
+
if (!Array.isArray(s.textOutputs))
|
|
285
|
+
return false;
|
|
286
|
+
if (!s.textOutputs.every((t) => typeof t === 'string'))
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
if (s.textOutputWithheld !== undefined) {
|
|
290
|
+
if (!isObject(s.textOutputWithheld) || typeof s.textOutputWithheld.reason !== 'string') {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (s.toolCalls !== undefined) {
|
|
295
|
+
if (!Array.isArray(s.toolCalls))
|
|
296
|
+
return false;
|
|
297
|
+
for (const c of s.toolCalls) {
|
|
298
|
+
if (!isObject(c))
|
|
299
|
+
return false;
|
|
300
|
+
if (typeof c.id !== 'string' || typeof c.type !== 'string')
|
|
301
|
+
return false;
|
|
302
|
+
if (!isObject(c.function))
|
|
303
|
+
return false;
|
|
304
|
+
if (typeof c.function.name !== 'string' || typeof c.function.arguments !== 'string') {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (s.stepOutputs !== undefined) {
|
|
310
|
+
if (!Array.isArray(s.stepOutputs))
|
|
311
|
+
return false;
|
|
312
|
+
// `output` is `unknown` by contract — only the `$type` label is dereferenced.
|
|
313
|
+
for (const o of s.stepOutputs) {
|
|
314
|
+
if (!isObject(o) || typeof o.$type !== 'string')
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (s.trainedEpochs !== undefined && !isValidTrainedEpochs(s.trainedEpochs))
|
|
319
|
+
return false;
|
|
320
|
+
if (s.publishedModel !== undefined && !isValidPublishedModel(s.publishedModel))
|
|
321
|
+
return false;
|
|
322
|
+
if (s.trainingQuote !== undefined && !isValidTrainingQuote(s.trainingQuote))
|
|
323
|
+
return false;
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Shape check for `trainingQuote` — the quote a `kind: 'training'` estimate
|
|
328
|
+
* returns. The block passes `quoteId` back verbatim and renders `total`, so both
|
|
329
|
+
* are pinned: a non-empty string, a finite number. `quoteId`'s `tq_…` FORMAT is
|
|
330
|
+
* deliberately not pinned — it is opaque, and a host changing it must not turn
|
|
331
|
+
* every estimate reply into a dropped one.
|
|
332
|
+
*/
|
|
333
|
+
function isValidTrainingQuote(q) {
|
|
334
|
+
if (!isObject(q))
|
|
335
|
+
return false;
|
|
336
|
+
if (!isNonEmptyString(q.quoteId))
|
|
337
|
+
return false;
|
|
338
|
+
if (!isFiniteNumber(q.total))
|
|
339
|
+
return false;
|
|
340
|
+
if (!Number.isInteger(q.imageCount) || q.imageCount < 0)
|
|
341
|
+
return false;
|
|
342
|
+
return typeof q.expiresAt === 'string';
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Shape check for `trainedEpochs` — shared by {@link isValidWorkflowSnapshot}
|
|
346
|
+
* and the `AppWorkflow` row guard, which carry the same field. `$type` is
|
|
347
|
+
* checked as a STRING, not against today's members (see the forward-
|
|
348
|
+
* compatibility note in {@link isValidWorkflowSnapshot}).
|
|
349
|
+
*/
|
|
350
|
+
function isValidTrainedEpochs(epochs) {
|
|
351
|
+
if (!Array.isArray(epochs))
|
|
352
|
+
return false;
|
|
353
|
+
for (const e of epochs) {
|
|
354
|
+
if (!isObject(e) || typeof e.$type !== 'string')
|
|
355
|
+
return false;
|
|
356
|
+
if (!Number.isInteger(e.epochNumber))
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
258
359
|
return true;
|
|
259
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Shape check for `publishedModel` — shared by {@link isValidWorkflowSnapshot}
|
|
363
|
+
* and the `AppWorkflow` row guard, which carry the same field.
|
|
364
|
+
*/
|
|
365
|
+
function isValidPublishedModel(p) {
|
|
366
|
+
if (!isObject(p))
|
|
367
|
+
return false;
|
|
368
|
+
if (!isFiniteNumber(p.modelId) || !isFiniteNumber(p.modelVersionId))
|
|
369
|
+
return false;
|
|
370
|
+
return typeof p.published === 'boolean';
|
|
371
|
+
}
|
|
260
372
|
/**
|
|
261
373
|
* Host-pushed token rotation. No `requestId` field; the host is the initiator.
|
|
262
374
|
* Carries the same wrapped-token shape as the reply path.
|
|
@@ -823,6 +935,11 @@ function isValidAppWorkflow(w) {
|
|
|
823
935
|
if (img.nsfwLevel !== null && !isFiniteNumber(img.nsfwLevel))
|
|
824
936
|
return false;
|
|
825
937
|
}
|
|
938
|
+
// OPTIONAL — absent on hosts that predate them; checked only when present.
|
|
939
|
+
if (w.publishedModel !== undefined && !isValidPublishedModel(w.publishedModel))
|
|
940
|
+
return false;
|
|
941
|
+
if (w.trainedEpochs !== undefined && !isValidTrainedEpochs(w.trainedEpochs))
|
|
942
|
+
return false;
|
|
826
943
|
return true;
|
|
827
944
|
}
|
|
828
945
|
/**
|
|
@@ -1160,6 +1277,71 @@ export function isValidCreatePostResult(p) {
|
|
|
1160
1277
|
return false;
|
|
1161
1278
|
return true;
|
|
1162
1279
|
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Reply to `PREPARE_TRAINING_DATASET`. A well-formed reply carries EITHER a
|
|
1282
|
+
* `result` (`{ datasetId: non-empty string, count: integer ≥ 0, rejected: [{
|
|
1283
|
+
* imageId: number, reason: string }] }`) OR an `error`; one with neither is
|
|
1284
|
+
* malformed and dropped.
|
|
1285
|
+
*
|
|
1286
|
+
* 🔴 `error` IS SHAPE-CHECKED ONLY — the same decision as
|
|
1287
|
+
* {@link isValidCreatePostResult}: the host's closed codes share the field with
|
|
1288
|
+
* any server message it forwards, and a membership check would drop those
|
|
1289
|
+
* replies before correlation, leaving the request to its timer.
|
|
1290
|
+
*
|
|
1291
|
+
* `rejected[].reason` is checked as a STRING, not against today's six members —
|
|
1292
|
+
* the host adding a reason must not make every prepare reply that carries it a
|
|
1293
|
+
* dropped one (the forward-compatibility rule in {@link isValidWorkflowSnapshot}).
|
|
1294
|
+
*/
|
|
1295
|
+
export function isValidTrainingDatasetResult(p) {
|
|
1296
|
+
if (!isObject(p))
|
|
1297
|
+
return false;
|
|
1298
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1299
|
+
return false;
|
|
1300
|
+
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1301
|
+
return false;
|
|
1302
|
+
if (p.result !== undefined) {
|
|
1303
|
+
const r = p.result;
|
|
1304
|
+
if (!isObject(r))
|
|
1305
|
+
return false;
|
|
1306
|
+
if (!isNonEmptyString(r.datasetId))
|
|
1307
|
+
return false;
|
|
1308
|
+
if (!Number.isInteger(r.count) || r.count < 0)
|
|
1309
|
+
return false;
|
|
1310
|
+
if (!Array.isArray(r.rejected))
|
|
1311
|
+
return false;
|
|
1312
|
+
for (const x of r.rejected) {
|
|
1313
|
+
if (!isObject(x) || !isFiniteNumber(x.imageId) || typeof x.reason !== 'string') {
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
if (p.result === undefined && p.error === undefined)
|
|
1319
|
+
return false;
|
|
1320
|
+
return true;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Reply to `RUN_TRAINING`. A well-formed reply carries EITHER a `snapshot` (a
|
|
1324
|
+
* valid {@link isValidWorkflowSnapshot} — the block polls its `workflowId`) OR an
|
|
1325
|
+
* `error`; one with neither is malformed and dropped.
|
|
1326
|
+
*
|
|
1327
|
+
* 🔴 `error` IS SHAPE-CHECKED ONLY, and here a membership check would cost the
|
|
1328
|
+
* most: this request carries the TEN-MINUTE consent bound, and the host forwards
|
|
1329
|
+
* server refusals (an expired quote, a changed body, an ineligible image) in this
|
|
1330
|
+
* field. A dropped refusal would leave a Train button spinning for ten minutes.
|
|
1331
|
+
*/
|
|
1332
|
+
export function isValidTrainingResult(p) {
|
|
1333
|
+
if (!isObject(p))
|
|
1334
|
+
return false;
|
|
1335
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1336
|
+
return false;
|
|
1337
|
+
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1338
|
+
return false;
|
|
1339
|
+
if (p.snapshot !== undefined && !isValidWorkflowSnapshot(p.snapshot))
|
|
1340
|
+
return false;
|
|
1341
|
+
if (p.snapshot === undefined && p.error === undefined)
|
|
1342
|
+
return false;
|
|
1343
|
+
return true;
|
|
1344
|
+
}
|
|
1163
1345
|
// ============================================================
|
|
1164
1346
|
// App-storage (per-viewer KV) reply validators
|
|
1165
1347
|
// ============================================================
|
|
@@ -1703,6 +1885,13 @@ export function payloadValidatorFor(type) {
|
|
|
1703
1885
|
// `validate.test.ts`.
|
|
1704
1886
|
case 'CREATE_POST_RESULT':
|
|
1705
1887
|
return isValidCreatePostResult;
|
|
1888
|
+
// Same caveat as CREATE_POST_RESULT: presence is compiler-enforced, the
|
|
1889
|
+
// identity is pinned by test (`validate.test.ts`). TRAINING_RESULT settles a
|
|
1890
|
+
// charged run.
|
|
1891
|
+
case 'TRAINING_DATASET_RESULT':
|
|
1892
|
+
return isValidTrainingDatasetResult;
|
|
1893
|
+
case 'TRAINING_RESULT':
|
|
1894
|
+
return isValidTrainingResult;
|
|
1706
1895
|
case 'IMAGE_UPLOAD_RESULT':
|
|
1707
1896
|
return isValidImageUploadResult;
|
|
1708
1897
|
case 'IMAGE_SCAN_RESOLVED':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civitai/blocks-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"description": "React hooks and iframe transport for Civitai Apps. Pairs with @civitai/app-sdk/blocks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"node": ">=20"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@civitai/
|
|
41
|
-
"@civitai/
|
|
40
|
+
"@civitai/theme": "^0.5.2",
|
|
41
|
+
"@civitai/components": "^0.9.2"
|
|
42
42
|
},
|
|
43
43
|
"comment-peerDependencies": "This floor is DERIVED, not chosen — the lowest published app-sdk that exports every symbol this package imports. Do not edit it without reading ./PEER_FLOOR.md (in-repo, unpublished) and tests/guards/blocks-react-peer-floor.test.mjs.",
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@civitai/app-sdk": ">=0.
|
|
45
|
+
"@civitai/app-sdk": ">=0.57.0 <1.0.0",
|
|
46
46
|
"react": "^18.0.0 || ^19.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"typescript": "^5.9.2",
|
|
59
59
|
"vite": "^8.0.14",
|
|
60
60
|
"vitest": "^4.1.11",
|
|
61
|
-
"@civitai/app-sdk": "^0.
|
|
61
|
+
"@civitai/app-sdk": "^0.58.0"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|