@civitai/blocks-react 0.67.1 → 0.69.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 +520 -8
- package/dist/hooks/useBatchEstimate.d.ts +191 -0
- package/dist/hooks/useBatchEstimate.js +196 -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/useWorkflowBatch.d.ts +196 -0
- package/dist/hooks/useWorkflowBatch.js +352 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -2
- package/dist/internal/liveHost.d.ts +27 -2
- package/dist/internal/liveHost.js +133 -15
- package/dist/internal/mockHost.d.ts +104 -2
- package/dist/internal/mockHost.js +369 -84
- 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 +138 -28
- package/package.json +2 -2
|
@@ -232,6 +232,13 @@ export function isValidWorkflowSnapshot(s) {
|
|
|
232
232
|
if (s.cost !== undefined) {
|
|
233
233
|
if (!isObject(s.cost) || typeof s.cost.total !== 'number')
|
|
234
234
|
return false;
|
|
235
|
+
// OPTIONAL: an older host omits it and the snapshot stays valid. Present, it
|
|
236
|
+
// is subtracted from `total` by the block, so a non-number or a negative
|
|
237
|
+
// would render a wrong price.
|
|
238
|
+
if (s.cost.authorFee !== undefined &&
|
|
239
|
+
(!isFiniteNumber(s.cost.authorFee) || s.cost.authorFee < 0)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
235
242
|
}
|
|
236
243
|
if (s.imageUrls !== undefined) {
|
|
237
244
|
if (!Array.isArray(s.imageUrls))
|
|
@@ -241,6 +248,12 @@ export function isValidWorkflowSnapshot(s) {
|
|
|
241
248
|
}
|
|
242
249
|
if (s.error !== undefined && typeof s.error !== 'string')
|
|
243
250
|
return false;
|
|
251
|
+
// OPTIONAL: set only on the refusals that define a code (today the training
|
|
252
|
+
// price-quote refusal), absent everywhere else. Checked as a STRING, not
|
|
253
|
+
// against today's codes — the open-ended-union rule below: a host adding a
|
|
254
|
+
// code must not turn into a dropped reply in every shipped bundle.
|
|
255
|
+
if (s.errorCode !== undefined && typeof s.errorCode !== 'string')
|
|
256
|
+
return false;
|
|
244
257
|
if (s.autoClaim !== undefined) {
|
|
245
258
|
if (!isObject(s.autoClaim))
|
|
246
259
|
return false;
|
|
@@ -513,6 +526,44 @@ export function isValidWorkflowReply(p) {
|
|
|
513
526
|
return false;
|
|
514
527
|
return true;
|
|
515
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Reply to a block-initiated `ESTIMATE_WORKFLOW_BATCH`. A well-formed reply
|
|
531
|
+
* carries EITHER an `error` string OR `snapshots` + `aggregate`.
|
|
532
|
+
*
|
|
533
|
+
* 🔴 A PRESENT `error` IS ACCEPTED ON ITS OWN, unlike the single-estimate reply
|
|
534
|
+
* ({@link isValidWorkflowReply}, which requires a snapshot). That is what lets
|
|
535
|
+
* the host's generic `{ requestId, error: 'unsupported on this host' }` reach
|
|
536
|
+
* `useBatchEstimate`, which turns it into the typed error an app falls back to
|
|
537
|
+
* per-cell estimates on. Dropping it would leave the block waiting out the
|
|
538
|
+
* timeout instead. `error` is shape-checked only: the channel carries free-text
|
|
539
|
+
* server messages.
|
|
540
|
+
*
|
|
541
|
+
* Without an `error`, EVERY cell must be a valid workflow snapshot and the
|
|
542
|
+
* aggregate must hold three finite numbers. One malformed cell rejects the whole
|
|
543
|
+
* reply, the same way one malformed snapshot rejects a single-estimate reply:
|
|
544
|
+
* the block dereferences these fields.
|
|
545
|
+
*/
|
|
546
|
+
export function isValidEstimateBatchResult(p) {
|
|
547
|
+
if (!isObject(p))
|
|
548
|
+
return false;
|
|
549
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
550
|
+
return false;
|
|
551
|
+
if (p.error !== undefined)
|
|
552
|
+
return typeof p.error === 'string';
|
|
553
|
+
if (!Array.isArray(p.snapshots))
|
|
554
|
+
return false;
|
|
555
|
+
if (!p.snapshots.every((s) => isValidWorkflowSnapshot(s)))
|
|
556
|
+
return false;
|
|
557
|
+
const a = p.aggregate;
|
|
558
|
+
if (!isObject(a))
|
|
559
|
+
return false;
|
|
560
|
+
for (const key of ['total', 'pricedCells', 'cellCount']) {
|
|
561
|
+
const value = a[key];
|
|
562
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
516
567
|
export function isValidBuzzPurchaseResult(p) {
|
|
517
568
|
if (!isObject(p))
|
|
518
569
|
return false;
|
|
@@ -1478,6 +1529,10 @@ export function isValidAppStorageQuotaResult(p) {
|
|
|
1478
1529
|
// uniform). Closes the silent-corrupt-return hole: without these,
|
|
1479
1530
|
// `getCount/getCounts/append/vote/unvote`/`list` returned a missing field as
|
|
1480
1531
|
// `undefined` typed `number`/`string` with no throw, no timeout.
|
|
1532
|
+
//
|
|
1533
|
+
// The optional `rateLimit` on the six write replies is NOT checked by any
|
|
1534
|
+
// predicate here, so it can never drop a reply; the hook parses it (see
|
|
1535
|
+
// `parseSharedStorageRateLimit` below the validators).
|
|
1481
1536
|
/** The moderated `{ title, body?, data? }` value carried by each SHARED entry. */
|
|
1482
1537
|
function isValidSharedValue(v) {
|
|
1483
1538
|
if (!isObject(v))
|
|
@@ -1676,6 +1731,36 @@ export function isValidSharedWithdrawResult(p) {
|
|
|
1676
1731
|
return false;
|
|
1677
1732
|
return true;
|
|
1678
1733
|
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Validate the `rateLimit` a host put on a shared-storage refusal. Returns a
|
|
1736
|
+
* copy holding ONLY the two known fields, or `undefined` when `value` is
|
|
1737
|
+
* missing or malformed.
|
|
1738
|
+
*
|
|
1739
|
+
* Kept in lockstep with the platform's own parser for the same metadata
|
|
1740
|
+
* (`parseRateLimitMeta` in civitai's `src/shared/utils/rate-limit-meta.ts`):
|
|
1741
|
+
* `retryAfterSeconds` must be an integer of at least 1, and `retryAt` a string
|
|
1742
|
+
* `Date.parse` can read. Anything else — a wrong type, a negative or fractional
|
|
1743
|
+
* number, a missing field, a non-object — is malformed.
|
|
1744
|
+
*
|
|
1745
|
+
* 🔴 NOT A VALIDATOR, deliberately. None of the `isValidShared*Result`
|
|
1746
|
+
* predicates look at `rateLimit`, so a malformed one can never DROP a reply:
|
|
1747
|
+
* dropping it would hang the block's request to its timeout over an optional
|
|
1748
|
+
* hint. The reply is delivered unchanged, and `useSharedStorage` calls this
|
|
1749
|
+
* parser: a malformed value is ignored and the plain refusal error is thrown.
|
|
1750
|
+
*/
|
|
1751
|
+
export function parseSharedStorageRateLimit(value) {
|
|
1752
|
+
if (!isObject(value))
|
|
1753
|
+
return undefined;
|
|
1754
|
+
const { retryAfterSeconds, retryAt } = value;
|
|
1755
|
+
if (typeof retryAfterSeconds !== 'number' || !Number.isInteger(retryAfterSeconds)) {
|
|
1756
|
+
return undefined;
|
|
1757
|
+
}
|
|
1758
|
+
if (retryAfterSeconds < 1)
|
|
1759
|
+
return undefined;
|
|
1760
|
+
if (typeof retryAt !== 'string' || Number.isNaN(Date.parse(retryAt)))
|
|
1761
|
+
return undefined;
|
|
1762
|
+
return { retryAfterSeconds, retryAt };
|
|
1763
|
+
}
|
|
1679
1764
|
// ============================================================
|
|
1680
1765
|
// Picker + user-checkpoint reply validators
|
|
1681
1766
|
// ============================================================
|
|
@@ -1723,42 +1808,65 @@ export function isValidCheckpointPickerResult(p) {
|
|
|
1723
1808
|
return true;
|
|
1724
1809
|
}
|
|
1725
1810
|
/**
|
|
1726
|
-
*
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
* `
|
|
1811
|
+
* One `BlockResourceInfo` projection — the 5 base fields plus `modelType` and
|
|
1812
|
+
* the OPTIONAL public recommended-settings (`strength`/`minStrength`/
|
|
1813
|
+
* `maxStrength` finite, `trainedWords` a string[], `clipSkip` a finite number
|
|
1814
|
+
* OR `null`). Shared by the single-pick `selected` and every entry of the
|
|
1815
|
+
* multi-select `selectedResources`, so the two cannot drift on what a
|
|
1816
|
+
* well-formed resource is.
|
|
1817
|
+
*/
|
|
1818
|
+
function isValidPickerResource(s) {
|
|
1819
|
+
if (!isObject(s))
|
|
1820
|
+
return false;
|
|
1821
|
+
if (!isValidPickerResourceBase(s))
|
|
1822
|
+
return false;
|
|
1823
|
+
if (s.modelType !== undefined && typeof s.modelType !== 'string')
|
|
1824
|
+
return false;
|
|
1825
|
+
if (s.strength !== undefined && !isFiniteNumber(s.strength))
|
|
1826
|
+
return false;
|
|
1827
|
+
if (s.minStrength !== undefined && !isFiniteNumber(s.minStrength))
|
|
1828
|
+
return false;
|
|
1829
|
+
if (s.maxStrength !== undefined && !isFiniteNumber(s.maxStrength))
|
|
1830
|
+
return false;
|
|
1831
|
+
if (s.trainedWords !== undefined) {
|
|
1832
|
+
if (!Array.isArray(s.trainedWords))
|
|
1833
|
+
return false;
|
|
1834
|
+
if (!s.trainedWords.every((w) => typeof w === 'string'))
|
|
1835
|
+
return false;
|
|
1836
|
+
}
|
|
1837
|
+
// `clipSkip` is `number | null` — accept a finite number OR null; reject
|
|
1838
|
+
// other present types.
|
|
1839
|
+
if (s.clipSkip !== undefined && s.clipSkip !== null && !isFiniteNumber(s.clipSkip)) {
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
return true;
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Reply to `OPEN_RESOURCE_PICKER`.
|
|
1846
|
+
*
|
|
1847
|
+
* Single pick: `selected` is ABSENT on dismiss (the hook resolves `null`); when
|
|
1848
|
+
* present it is a `BlockResourceInfo` projection.
|
|
1849
|
+
*
|
|
1850
|
+
* Multi-select: `selectedResources` is an array of the same projection (`[]` on
|
|
1851
|
+
* dismiss). ONE malformed entry drops the whole reply — each `versionId` is
|
|
1852
|
+
* money-adjacent, and handing the caller the well-formed remainder would
|
|
1853
|
+
* silently change which resources a generation runs with.
|
|
1854
|
+
*
|
|
1855
|
+
* `error` (a refusal) is not validated here: the hook reads it by PRESENCE, and
|
|
1856
|
+
* a reply carrying only `{ requestId, error }` must reach it.
|
|
1731
1857
|
*/
|
|
1732
1858
|
export function isValidResourcePickerResult(p) {
|
|
1733
1859
|
if (!isObject(p))
|
|
1734
1860
|
return false;
|
|
1735
1861
|
if (!isWireRequestIdShape(p.requestId))
|
|
1736
1862
|
return false;
|
|
1737
|
-
if (p.selected !== undefined)
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
if (!isValidPickerResourceBase(s))
|
|
1742
|
-
return false;
|
|
1743
|
-
if (s.modelType !== undefined && typeof s.modelType !== 'string')
|
|
1744
|
-
return false;
|
|
1745
|
-
if (s.strength !== undefined && !isFiniteNumber(s.strength))
|
|
1746
|
-
return false;
|
|
1747
|
-
if (s.minStrength !== undefined && !isFiniteNumber(s.minStrength))
|
|
1863
|
+
if (p.selected !== undefined && !isValidPickerResource(p.selected))
|
|
1864
|
+
return false;
|
|
1865
|
+
if (p.selectedResources !== undefined) {
|
|
1866
|
+
if (!Array.isArray(p.selectedResources))
|
|
1748
1867
|
return false;
|
|
1749
|
-
if (
|
|
1868
|
+
if (!p.selectedResources.every(isValidPickerResource))
|
|
1750
1869
|
return false;
|
|
1751
|
-
if (s.trainedWords !== undefined) {
|
|
1752
|
-
if (!Array.isArray(s.trainedWords))
|
|
1753
|
-
return false;
|
|
1754
|
-
if (!s.trainedWords.every((w) => typeof w === 'string'))
|
|
1755
|
-
return false;
|
|
1756
|
-
}
|
|
1757
|
-
// `clipSkip` is `number | null` — accept a finite number OR null; reject
|
|
1758
|
-
// other present types.
|
|
1759
|
-
if (s.clipSkip !== undefined && s.clipSkip !== null && !isFiniteNumber(s.clipSkip)) {
|
|
1760
|
-
return false;
|
|
1761
|
-
}
|
|
1762
1870
|
}
|
|
1763
1871
|
return true;
|
|
1764
1872
|
}
|
|
@@ -1862,6 +1970,8 @@ export function payloadValidatorFor(type) {
|
|
|
1862
1970
|
case 'WORKFLOW_STATUS':
|
|
1863
1971
|
case 'WORKFLOW_CANCELED':
|
|
1864
1972
|
return isValidWorkflowReply;
|
|
1973
|
+
case 'ESTIMATE_BATCH_RESULT':
|
|
1974
|
+
return isValidEstimateBatchResult;
|
|
1865
1975
|
case 'BUZZ_PURCHASE_RESULT':
|
|
1866
1976
|
return isValidBuzzPurchaseResult;
|
|
1867
1977
|
case 'BUZZ_BALANCE_RESULT':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civitai/blocks-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.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",
|
|
@@ -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.65.0"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|