@civitai/blocks-react 0.67.1 → 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 +357 -8
- 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/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 +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))
|
|
@@ -513,6 +520,44 @@ export function isValidWorkflowReply(p) {
|
|
|
513
520
|
return false;
|
|
514
521
|
return true;
|
|
515
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Reply to a block-initiated `ESTIMATE_WORKFLOW_BATCH`. A well-formed reply
|
|
525
|
+
* carries EITHER an `error` string OR `snapshots` + `aggregate`.
|
|
526
|
+
*
|
|
527
|
+
* 🔴 A PRESENT `error` IS ACCEPTED ON ITS OWN, unlike the single-estimate reply
|
|
528
|
+
* ({@link isValidWorkflowReply}, which requires a snapshot). That is what lets
|
|
529
|
+
* the host's generic `{ requestId, error: 'unsupported on this host' }` reach
|
|
530
|
+
* `useBatchEstimate`, which turns it into the typed error an app falls back to
|
|
531
|
+
* per-cell estimates on. Dropping it would leave the block waiting out the
|
|
532
|
+
* timeout instead. `error` is shape-checked only: the channel carries free-text
|
|
533
|
+
* server messages.
|
|
534
|
+
*
|
|
535
|
+
* Without an `error`, EVERY cell must be a valid workflow snapshot and the
|
|
536
|
+
* aggregate must hold three finite numbers. One malformed cell rejects the whole
|
|
537
|
+
* reply, the same way one malformed snapshot rejects a single-estimate reply:
|
|
538
|
+
* the block dereferences these fields.
|
|
539
|
+
*/
|
|
540
|
+
export function isValidEstimateBatchResult(p) {
|
|
541
|
+
if (!isObject(p))
|
|
542
|
+
return false;
|
|
543
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
544
|
+
return false;
|
|
545
|
+
if (p.error !== undefined)
|
|
546
|
+
return typeof p.error === 'string';
|
|
547
|
+
if (!Array.isArray(p.snapshots))
|
|
548
|
+
return false;
|
|
549
|
+
if (!p.snapshots.every((s) => isValidWorkflowSnapshot(s)))
|
|
550
|
+
return false;
|
|
551
|
+
const a = p.aggregate;
|
|
552
|
+
if (!isObject(a))
|
|
553
|
+
return false;
|
|
554
|
+
for (const key of ['total', 'pricedCells', 'cellCount']) {
|
|
555
|
+
const value = a[key];
|
|
556
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
516
561
|
export function isValidBuzzPurchaseResult(p) {
|
|
517
562
|
if (!isObject(p))
|
|
518
563
|
return false;
|
|
@@ -1478,6 +1523,10 @@ export function isValidAppStorageQuotaResult(p) {
|
|
|
1478
1523
|
// uniform). Closes the silent-corrupt-return hole: without these,
|
|
1479
1524
|
// `getCount/getCounts/append/vote/unvote`/`list` returned a missing field as
|
|
1480
1525
|
// `undefined` typed `number`/`string` with no throw, no timeout.
|
|
1526
|
+
//
|
|
1527
|
+
// The optional `rateLimit` on the six write replies is NOT checked by any
|
|
1528
|
+
// predicate here, so it can never drop a reply; the hook parses it (see
|
|
1529
|
+
// `parseSharedStorageRateLimit` below the validators).
|
|
1481
1530
|
/** The moderated `{ title, body?, data? }` value carried by each SHARED entry. */
|
|
1482
1531
|
function isValidSharedValue(v) {
|
|
1483
1532
|
if (!isObject(v))
|
|
@@ -1676,6 +1725,36 @@ export function isValidSharedWithdrawResult(p) {
|
|
|
1676
1725
|
return false;
|
|
1677
1726
|
return true;
|
|
1678
1727
|
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Validate the `rateLimit` a host put on a shared-storage refusal. Returns a
|
|
1730
|
+
* copy holding ONLY the two known fields, or `undefined` when `value` is
|
|
1731
|
+
* missing or malformed.
|
|
1732
|
+
*
|
|
1733
|
+
* Kept in lockstep with the platform's own parser for the same metadata
|
|
1734
|
+
* (`parseRateLimitMeta` in civitai's `src/shared/utils/rate-limit-meta.ts`):
|
|
1735
|
+
* `retryAfterSeconds` must be an integer of at least 1, and `retryAt` a string
|
|
1736
|
+
* `Date.parse` can read. Anything else — a wrong type, a negative or fractional
|
|
1737
|
+
* number, a missing field, a non-object — is malformed.
|
|
1738
|
+
*
|
|
1739
|
+
* 🔴 NOT A VALIDATOR, deliberately. None of the `isValidShared*Result`
|
|
1740
|
+
* predicates look at `rateLimit`, so a malformed one can never DROP a reply:
|
|
1741
|
+
* dropping it would hang the block's request to its timeout over an optional
|
|
1742
|
+
* hint. The reply is delivered unchanged, and `useSharedStorage` calls this
|
|
1743
|
+
* parser: a malformed value is ignored and the plain refusal error is thrown.
|
|
1744
|
+
*/
|
|
1745
|
+
export function parseSharedStorageRateLimit(value) {
|
|
1746
|
+
if (!isObject(value))
|
|
1747
|
+
return undefined;
|
|
1748
|
+
const { retryAfterSeconds, retryAt } = value;
|
|
1749
|
+
if (typeof retryAfterSeconds !== 'number' || !Number.isInteger(retryAfterSeconds)) {
|
|
1750
|
+
return undefined;
|
|
1751
|
+
}
|
|
1752
|
+
if (retryAfterSeconds < 1)
|
|
1753
|
+
return undefined;
|
|
1754
|
+
if (typeof retryAt !== 'string' || Number.isNaN(Date.parse(retryAt)))
|
|
1755
|
+
return undefined;
|
|
1756
|
+
return { retryAfterSeconds, retryAt };
|
|
1757
|
+
}
|
|
1679
1758
|
// ============================================================
|
|
1680
1759
|
// Picker + user-checkpoint reply validators
|
|
1681
1760
|
// ============================================================
|
|
@@ -1723,42 +1802,65 @@ export function isValidCheckpointPickerResult(p) {
|
|
|
1723
1802
|
return true;
|
|
1724
1803
|
}
|
|
1725
1804
|
/**
|
|
1726
|
-
*
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
* `
|
|
1805
|
+
* One `BlockResourceInfo` projection — the 5 base fields plus `modelType` and
|
|
1806
|
+
* the OPTIONAL public recommended-settings (`strength`/`minStrength`/
|
|
1807
|
+
* `maxStrength` finite, `trainedWords` a string[], `clipSkip` a finite number
|
|
1808
|
+
* OR `null`). Shared by the single-pick `selected` and every entry of the
|
|
1809
|
+
* multi-select `selectedResources`, so the two cannot drift on what a
|
|
1810
|
+
* well-formed resource is.
|
|
1811
|
+
*/
|
|
1812
|
+
function isValidPickerResource(s) {
|
|
1813
|
+
if (!isObject(s))
|
|
1814
|
+
return false;
|
|
1815
|
+
if (!isValidPickerResourceBase(s))
|
|
1816
|
+
return false;
|
|
1817
|
+
if (s.modelType !== undefined && typeof s.modelType !== 'string')
|
|
1818
|
+
return false;
|
|
1819
|
+
if (s.strength !== undefined && !isFiniteNumber(s.strength))
|
|
1820
|
+
return false;
|
|
1821
|
+
if (s.minStrength !== undefined && !isFiniteNumber(s.minStrength))
|
|
1822
|
+
return false;
|
|
1823
|
+
if (s.maxStrength !== undefined && !isFiniteNumber(s.maxStrength))
|
|
1824
|
+
return false;
|
|
1825
|
+
if (s.trainedWords !== undefined) {
|
|
1826
|
+
if (!Array.isArray(s.trainedWords))
|
|
1827
|
+
return false;
|
|
1828
|
+
if (!s.trainedWords.every((w) => typeof w === 'string'))
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
// `clipSkip` is `number | null` — accept a finite number OR null; reject
|
|
1832
|
+
// other present types.
|
|
1833
|
+
if (s.clipSkip !== undefined && s.clipSkip !== null && !isFiniteNumber(s.clipSkip)) {
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
return true;
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Reply to `OPEN_RESOURCE_PICKER`.
|
|
1840
|
+
*
|
|
1841
|
+
* Single pick: `selected` is ABSENT on dismiss (the hook resolves `null`); when
|
|
1842
|
+
* present it is a `BlockResourceInfo` projection.
|
|
1843
|
+
*
|
|
1844
|
+
* Multi-select: `selectedResources` is an array of the same projection (`[]` on
|
|
1845
|
+
* dismiss). ONE malformed entry drops the whole reply — each `versionId` is
|
|
1846
|
+
* money-adjacent, and handing the caller the well-formed remainder would
|
|
1847
|
+
* silently change which resources a generation runs with.
|
|
1848
|
+
*
|
|
1849
|
+
* `error` (a refusal) is not validated here: the hook reads it by PRESENCE, and
|
|
1850
|
+
* a reply carrying only `{ requestId, error }` must reach it.
|
|
1731
1851
|
*/
|
|
1732
1852
|
export function isValidResourcePickerResult(p) {
|
|
1733
1853
|
if (!isObject(p))
|
|
1734
1854
|
return false;
|
|
1735
1855
|
if (!isWireRequestIdShape(p.requestId))
|
|
1736
1856
|
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))
|
|
1857
|
+
if (p.selected !== undefined && !isValidPickerResource(p.selected))
|
|
1858
|
+
return false;
|
|
1859
|
+
if (p.selectedResources !== undefined) {
|
|
1860
|
+
if (!Array.isArray(p.selectedResources))
|
|
1748
1861
|
return false;
|
|
1749
|
-
if (
|
|
1862
|
+
if (!p.selectedResources.every(isValidPickerResource))
|
|
1750
1863
|
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
1864
|
}
|
|
1763
1865
|
return true;
|
|
1764
1866
|
}
|
|
@@ -1862,6 +1964,8 @@ export function payloadValidatorFor(type) {
|
|
|
1862
1964
|
case 'WORKFLOW_STATUS':
|
|
1863
1965
|
case 'WORKFLOW_CANCELED':
|
|
1864
1966
|
return isValidWorkflowReply;
|
|
1967
|
+
case 'ESTIMATE_BATCH_RESULT':
|
|
1968
|
+
return isValidEstimateBatchResult;
|
|
1865
1969
|
case 'BUZZ_PURCHASE_RESULT':
|
|
1866
1970
|
return isValidBuzzPurchaseResult;
|
|
1867
1971
|
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.68.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.64.0"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|