@civitai/blocks-react 0.77.0 → 0.78.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 +62 -0
- package/dist/hooks/useResourceFile.d.ts +71 -0
- package/dist/hooks/useResourceFile.js +113 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/internal/liveHost.d.ts +4 -1
- package/dist/internal/liveHost.js +24 -1
- package/dist/internal/mockHost.d.ts +18 -2
- package/dist/internal/mockHost.js +59 -0
- package/dist/transport/requestTimeouts.d.ts +13 -0
- package/dist/transport/requestTimeouts.js +17 -0
- package/dist/transport/validate.d.ts +11 -0
- package/dist/transport/validate.js +76 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1229,6 +1229,67 @@ if (error instanceof WildcardPackError && error.code === 'busy') void refetch();
|
|
|
1229
1229
|
if (!loading && pack) console.log(Object.keys(pack.lists));
|
|
1230
1230
|
```
|
|
1231
1231
|
|
|
1232
|
+
### `useResourceFile(modelVersionId, fileId?)`
|
|
1233
|
+
|
|
1234
|
+
Read a resource file's parsed JSON — for a workflow, its graphs — by model
|
|
1235
|
+
version. The host resolves, fetches, unzips and parses the file **in the
|
|
1236
|
+
viewer's own page session**, enforcing the real download gates, so the iframe
|
|
1237
|
+
never sees the session, a signed URL or the raw bytes. No block scope is needed.
|
|
1238
|
+
Returns `{ file, loading, error, refetch }`.
|
|
1239
|
+
|
|
1240
|
+
- `file.meta` names what was served (`modelId`, `modelVersionId`, `fileId`,
|
|
1241
|
+
`fileName`, `modelName`, `versionName`, `creatorUsername`).
|
|
1242
|
+
- `file.entries` holds one `{ path, format, graph }` per JSON document: one for a
|
|
1243
|
+
plain `.json`, several for a `.zip` (non-JSON entries are skipped). `format`
|
|
1244
|
+
is `'ui'` (a `nodes` array), `'api'` (every top-level value has `class_type`
|
|
1245
|
+
and `inputs`) or `'unknown'`; check it before treating `graph` as a graph.
|
|
1246
|
+
`file.truncated` is `true` when an entry was dropped: past 20 entries, over
|
|
1247
|
+
the 4 MB per-entry or 16 MB total cap, unreadable, or not valid JSON.
|
|
1248
|
+
- Pass `fileId` to pin one file of a multi-file version. Without it the host
|
|
1249
|
+
picks one.
|
|
1250
|
+
- Changing `modelVersionId` or `fileId` resets `file` and `error` to `null`
|
|
1251
|
+
before the new fetch, so a file never shows for ids it does not belong to.
|
|
1252
|
+
- The request waits up to 120 s, because the host downloads and unzips the file.
|
|
1253
|
+
|
|
1254
|
+
On failure `error` is a `ResourceFileError` whose `code` is one of a closed set:
|
|
1255
|
+
|
|
1256
|
+
| `code` | Means | Retry? |
|
|
1257
|
+
|---|---|---|
|
|
1258
|
+
| `not-found` | No such version or file, not a workflow, or not downloadable for free (see below) | No |
|
|
1259
|
+
| `forbidden` | Above the viewer's maturity ceiling on this site, or the app is in review preview | No |
|
|
1260
|
+
| `unauthorized` | The viewer is signed out | No |
|
|
1261
|
+
| `too-large` | Over 32 MB, or every entry was over a size cap (4 MB per entry, 16 MB in total) | No |
|
|
1262
|
+
| `parse-failed` | Not a `.json`/`.zip`, corrupt, or no valid JSON — **or** the host's catch-all: a network error, a failed fetch, no answer within its 110 s deadline, a transient storage failure, or another server error | Once, maybe: it can be transient |
|
|
1263
|
+
| `busy` | Three fetches already in flight in this tab | Yes |
|
|
1264
|
+
| `rate-limited` | The viewer's 30-a-minute budget is spent | Yes, later |
|
|
1265
|
+
|
|
1266
|
+
🔴 **Full-page app hosts only — not the model-page slot, not `dev:live`.** The
|
|
1267
|
+
model-page slot does not answer it (the request times out), and `dev:live`
|
|
1268
|
+
replies `parse-failed`. Develop against `dev:mock`: `createMockHost` takes a
|
|
1269
|
+
`resourceFile` (the canned reply) and a `resourceFileError` (force a code);
|
|
1270
|
+
`setScenario({ resourceFileError: undefined })` clears the forced error.
|
|
1271
|
+
|
|
1272
|
+
🔴 **Only free, public versions are served.** An unpublished draft (the owner's
|
|
1273
|
+
included) and an early-access or paid version answer `not-found` — even for a
|
|
1274
|
+
viewer who bought it. Only the owner gets their own published early-access
|
|
1275
|
+
version. A POI model, or a model or version whose availability is not `Public`
|
|
1276
|
+
or `Unsearchable` (e.g. `Private`), is refused to everyone but the owner,
|
|
1277
|
+
moderators included.
|
|
1278
|
+
|
|
1279
|
+
```tsx
|
|
1280
|
+
const { file, loading, error, refetch } = useResourceFile(modelVersionId);
|
|
1281
|
+
// Retry a retryable code from an effect, after a delay — never during render.
|
|
1282
|
+
const retryable =
|
|
1283
|
+
error instanceof ResourceFileError && (error.code === 'busy' || error.code === 'rate-limited');
|
|
1284
|
+
useEffect(() => {
|
|
1285
|
+
if (!retryable) return;
|
|
1286
|
+
const t = setTimeout(refetch, 2_000);
|
|
1287
|
+
return () => clearTimeout(t);
|
|
1288
|
+
}, [retryable, refetch]);
|
|
1289
|
+
const graphs = file?.entries.filter((e) => e.format !== 'unknown') ?? [];
|
|
1290
|
+
if (!loading && file) console.log(file.meta.fileName, graphs.length, file.truncated);
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1232
1293
|
### `useCollectionFollow()`
|
|
1233
1294
|
|
|
1234
1295
|
Follow / unfollow a collection **for the viewer**, host-mediated over
|
|
@@ -3830,6 +3891,7 @@ Runnable, minimal blocks — one per feature, each with its own README:
|
|
|
3830
3891
|
|
|
3831
3892
|
| `@civitai/blocks-react` | pairs with `@civitai/app-sdk` | adds |
|
|
3832
3893
|
|---|---|---|
|
|
3894
|
+
| `0.78.x` | `^0.79.0` | `useResourceFile()` (`GET_RESOURCE_FILE`). Its types (`BlockResourceFile` and friends) come from the SDK's `0.79.0`; the hook imports no SDK value, so the declared peer floor is unchanged. |
|
|
3833
3895
|
| `0.50.x` | `^0.40.0` | `useCreatePostFromApp()` (`CREATE_POST_FROM_APP`) + the `posts:write:self` scope. Pairing `0.50.x` with an SDK below `0.40.0` fails at `tsc` with `Cannot find name 'BlockCreatePostHostError'`, not at install. |
|
|
3834
3896
|
| `0.48.x` | `^0.38.0` | `useCollectionFollow()` + `<FollowButton>` / `<TipButton>` (`SET_COLLECTION_FOLLOW`). Pairing `0.48.x` with an SDK below `0.38.0` fails at `tsc` with `Cannot find name 'BlockCollectionFollowErrorCode'`, not at install — the floor those releases declared was deliberately wide (#206 — a per-minor floor forced a major on consumers). |
|
|
3835
3897
|
| `0.36.x` | `^0.27.0` | auto-installs the SDK's opaque-origin web-storage shim (`@civitai/app-sdk/safe-storage`) on import |
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { BlockResourceFile, BlockResourceFileEntry, BlockResourceFileEntryFormat, BlockResourceFileErrorCode, BlockResourceFileMeta } from '@civitai/app-sdk/blocks';
|
|
2
|
+
export type { BlockResourceFile, BlockResourceFileEntry, BlockResourceFileEntryFormat, BlockResourceFileErrorCode, BlockResourceFileMeta, };
|
|
3
|
+
/**
|
|
4
|
+
* A resource-file read failure. The host returns one code from a CLOSED set, and
|
|
5
|
+
* this error carries it on `.code` so a block can branch on the exact reason:
|
|
6
|
+
* `busy` and `rate-limited` are retryable; `not-found`, `forbidden`,
|
|
7
|
+
* `unauthorized`, `too-large` and `parse-failed` are terminal. `.message` equals
|
|
8
|
+
* the code.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ResourceFileError extends Error {
|
|
11
|
+
readonly code: BlockResourceFileErrorCode;
|
|
12
|
+
constructor(code: BlockResourceFileErrorCode);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* What {@link useResourceFile} returns.
|
|
16
|
+
*/
|
|
17
|
+
export interface UseResourceFile {
|
|
18
|
+
/**
|
|
19
|
+
* The parsed file for the CURRENT `modelVersionId` / `fileId`, or `null` until
|
|
20
|
+
* it loads. Reset to `null` the moment either id changes, so a file is never
|
|
21
|
+
* shown for an id it does not belong to. A `refetch()` of the same ids keeps it
|
|
22
|
+
* while reloading.
|
|
23
|
+
*/
|
|
24
|
+
file: BlockResourceFile | null;
|
|
25
|
+
/** `true` while a fetch (initial or `refetch`) is in flight. */
|
|
26
|
+
loading: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The last fetch's failure as a {@link ResourceFileError} (`.code` is the
|
|
29
|
+
* reason), or a plain `Error` (a `RequestTimeoutError`) when the host never
|
|
30
|
+
* answered. Cleared at the start of the next fetch, and when either id changes.
|
|
31
|
+
*/
|
|
32
|
+
error: Error | null;
|
|
33
|
+
/** Re-request the file. */
|
|
34
|
+
refetch: () => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read a resource file's parsed JSON (for a workflow: its graphs) by model-version
|
|
38
|
+
* id, through the host-mediated `GET_RESOURCE_FILE` → `RESOURCE_FILE_RESULT`
|
|
39
|
+
* bridge. TOKEN-INDEPENDENT (no block scope): the host resolves, fetches, unzips
|
|
40
|
+
* and parses the file in the viewer's own page session, enforcing the real
|
|
41
|
+
* download gates, and returns only the parsed JSON. The iframe never sees the
|
|
42
|
+
* session, the signed URL or the raw bytes.
|
|
43
|
+
*
|
|
44
|
+
* Pass `fileId` to pin one file of a multi-file version; without it the host
|
|
45
|
+
* picks one. Only free, public versions are served: an unpublished draft or an
|
|
46
|
+
* early-access / paid version answers `not-found`, even for a viewer who bought
|
|
47
|
+
* it. Full-page app hosts only — the model-page slot does not answer it, and
|
|
48
|
+
* `dev:live` replies `parse-failed`; use `dev:mock` to develop against it.
|
|
49
|
+
*
|
|
50
|
+
* Fetches on mount and whenever `modelVersionId` or `fileId` changes; a change
|
|
51
|
+
* clears `file` and `error` first. A non-positive or non-integer
|
|
52
|
+
* `modelVersionId` (or `fileId`, when given) fetches nothing. The request waits
|
|
53
|
+
* up to 120 s (`RESOURCE_FILE_REQUEST_TIMEOUT_MS`), since the host downloads and
|
|
54
|
+
* unzips the file; a host that never answers surfaces as a `RequestTimeoutError`
|
|
55
|
+
* after that. Only the LATEST request may write state: a superseded or
|
|
56
|
+
* post-unmount reply is dropped (#392).
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const { file, loading, error, refetch } = useResourceFile(modelVersionId);
|
|
60
|
+
* // Retry a retryable code from an effect, after a delay — never during render.
|
|
61
|
+
* const retryable =
|
|
62
|
+
* error instanceof ResourceFileError && (error.code === 'busy' || error.code === 'rate-limited');
|
|
63
|
+
* useEffect(() => {
|
|
64
|
+
* if (!retryable) return;
|
|
65
|
+
* const t = setTimeout(refetch, 2_000);
|
|
66
|
+
* return () => clearTimeout(t);
|
|
67
|
+
* }, [retryable, refetch]);
|
|
68
|
+
* const graphs = file?.entries.filter((e) => e.format !== 'unknown') ?? [];
|
|
69
|
+
*/
|
|
70
|
+
export declare function useResourceFile(modelVersionId: number, fileId?: number): UseResourceFile;
|
|
71
|
+
//# sourceMappingURL=useResourceFile.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { RESOURCE_FILE_REQUEST_TIMEOUT_MS } from '../transport/requestTimeouts.js';
|
|
3
|
+
import { getTransport } from '../transport/singleton.js';
|
|
4
|
+
import { sendTypedRequest } from '../transport/transport.js';
|
|
5
|
+
import { useRequestSequencer } from './useRequestSequencer.js';
|
|
6
|
+
import { useTransportConnection } from './useTransportConnection.js';
|
|
7
|
+
/**
|
|
8
|
+
* A resource-file read failure. The host returns one code from a CLOSED set, and
|
|
9
|
+
* this error carries it on `.code` so a block can branch on the exact reason:
|
|
10
|
+
* `busy` and `rate-limited` are retryable; `not-found`, `forbidden`,
|
|
11
|
+
* `unauthorized`, `too-large` and `parse-failed` are terminal. `.message` equals
|
|
12
|
+
* the code.
|
|
13
|
+
*/
|
|
14
|
+
export class ResourceFileError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
constructor(code) {
|
|
17
|
+
super(code);
|
|
18
|
+
this.name = 'ResourceFileError';
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read a resource file's parsed JSON (for a workflow: its graphs) by model-version
|
|
24
|
+
* id, through the host-mediated `GET_RESOURCE_FILE` → `RESOURCE_FILE_RESULT`
|
|
25
|
+
* bridge. TOKEN-INDEPENDENT (no block scope): the host resolves, fetches, unzips
|
|
26
|
+
* and parses the file in the viewer's own page session, enforcing the real
|
|
27
|
+
* download gates, and returns only the parsed JSON. The iframe never sees the
|
|
28
|
+
* session, the signed URL or the raw bytes.
|
|
29
|
+
*
|
|
30
|
+
* Pass `fileId` to pin one file of a multi-file version; without it the host
|
|
31
|
+
* picks one. Only free, public versions are served: an unpublished draft or an
|
|
32
|
+
* early-access / paid version answers `not-found`, even for a viewer who bought
|
|
33
|
+
* it. Full-page app hosts only — the model-page slot does not answer it, and
|
|
34
|
+
* `dev:live` replies `parse-failed`; use `dev:mock` to develop against it.
|
|
35
|
+
*
|
|
36
|
+
* Fetches on mount and whenever `modelVersionId` or `fileId` changes; a change
|
|
37
|
+
* clears `file` and `error` first. A non-positive or non-integer
|
|
38
|
+
* `modelVersionId` (or `fileId`, when given) fetches nothing. The request waits
|
|
39
|
+
* up to 120 s (`RESOURCE_FILE_REQUEST_TIMEOUT_MS`), since the host downloads and
|
|
40
|
+
* unzips the file; a host that never answers surfaces as a `RequestTimeoutError`
|
|
41
|
+
* after that. Only the LATEST request may write state: a superseded or
|
|
42
|
+
* post-unmount reply is dropped (#392).
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const { file, loading, error, refetch } = useResourceFile(modelVersionId);
|
|
46
|
+
* // Retry a retryable code from an effect, after a delay — never during render.
|
|
47
|
+
* const retryable =
|
|
48
|
+
* error instanceof ResourceFileError && (error.code === 'busy' || error.code === 'rate-limited');
|
|
49
|
+
* useEffect(() => {
|
|
50
|
+
* if (!retryable) return;
|
|
51
|
+
* const t = setTimeout(refetch, 2_000);
|
|
52
|
+
* return () => clearTimeout(t);
|
|
53
|
+
* }, [retryable, refetch]);
|
|
54
|
+
* const graphs = file?.entries.filter((e) => e.format !== 'unknown') ?? [];
|
|
55
|
+
*/
|
|
56
|
+
export function useResourceFile(modelVersionId, fileId) {
|
|
57
|
+
useTransportConnection();
|
|
58
|
+
const [file, setFile] = useState(null);
|
|
59
|
+
const [loading, setLoading] = useState(true);
|
|
60
|
+
const [error, setError] = useState(null);
|
|
61
|
+
const seq = useRequestSequencer();
|
|
62
|
+
// The ids the current `file` / `error` belong to. A fetch for different ids
|
|
63
|
+
// clears both first, so `{ file: A, error: B }` can never be returned.
|
|
64
|
+
const key = `${modelVersionId}|${fileId ?? ''}`;
|
|
65
|
+
const keyRef = useRef(null);
|
|
66
|
+
const isPositiveInt = (n) => Number.isInteger(n) && n > 0;
|
|
67
|
+
const valid = isPositiveInt(modelVersionId) && (fileId === undefined || isPositiveInt(fileId));
|
|
68
|
+
const refetch = useCallback(() => {
|
|
69
|
+
// `begin()` runs BEFORE the validity gate, as in `useWildcardPack`: moving to
|
|
70
|
+
// an invalid id must also supersede an in-flight request for the old one.
|
|
71
|
+
const token = seq.begin();
|
|
72
|
+
if (keyRef.current !== key) {
|
|
73
|
+
keyRef.current = key;
|
|
74
|
+
setFile(null);
|
|
75
|
+
setError(null);
|
|
76
|
+
}
|
|
77
|
+
if (!valid) {
|
|
78
|
+
setLoading(false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setLoading(true);
|
|
82
|
+
setError(null);
|
|
83
|
+
sendTypedRequest(getTransport(), {
|
|
84
|
+
type: 'GET_RESOURCE_FILE',
|
|
85
|
+
payload: fileId === undefined ? { modelVersionId } : { modelVersionId, fileId },
|
|
86
|
+
}, 'RESOURCE_FILE_RESULT', { timeoutMs: RESOURCE_FILE_REQUEST_TIMEOUT_MS })
|
|
87
|
+
.then((result) => {
|
|
88
|
+
if (!seq.isCurrent(token))
|
|
89
|
+
return;
|
|
90
|
+
if (result.error || !result.file) {
|
|
91
|
+
// `??`, as in `useWildcardPack`: `isValidResourceFileResult` admits only
|
|
92
|
+
// the closed code set, so `error: ''` never reaches here. The fallback
|
|
93
|
+
// covers only a reply with neither field, which the validator drops too.
|
|
94
|
+
setError(new ResourceFileError(result.error ?? 'parse-failed'));
|
|
95
|
+
setLoading(false);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
setFile(result.file);
|
|
99
|
+
setLoading(false);
|
|
100
|
+
})
|
|
101
|
+
.catch((err) => {
|
|
102
|
+
if (!seq.isCurrent(token))
|
|
103
|
+
return;
|
|
104
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
105
|
+
setLoading(false);
|
|
106
|
+
});
|
|
107
|
+
}, [valid, key, modelVersionId, fileId, seq]);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
refetch();
|
|
110
|
+
}, [refetch]);
|
|
111
|
+
return { file, loading, error, refetch };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=useResourceFile.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ export { useDailyCompensation } from './hooks/useDailyCompensation.js';
|
|
|
57
57
|
export type { UseDailyCompensation } from './hooks/useDailyCompensation.js';
|
|
58
58
|
export { useWildcardPack, WildcardPackError } from './hooks/useWildcardPack.js';
|
|
59
59
|
export type { UseWildcardPack } from './hooks/useWildcardPack.js';
|
|
60
|
+
export { useResourceFile, ResourceFileError } from './hooks/useResourceFile.js';
|
|
61
|
+
export type { UseResourceFile } from './hooks/useResourceFile.js';
|
|
60
62
|
export { useAppWorkflows } from './hooks/useAppWorkflows.js';
|
|
61
63
|
export type { UseAppWorkflows } from './hooks/useAppWorkflows.js';
|
|
62
64
|
export { usePublishGenerationOutputs } from './hooks/usePublishGenerationOutputs.js';
|
|
@@ -77,7 +79,7 @@ export { useRunTraining, RunTrainingError, RUN_TRAINING_ERROR_CODES, isRunTraini
|
|
|
77
79
|
export type { UseRunTraining, QuotedTrainingBody, RunTrainingErrorCode, BlockRunTrainingHostError, BlockTrainingQuote, BlockTrainingDatasetItem, WorkflowBodyTraining, } from './hooks/useRunTraining.js';
|
|
78
80
|
export { useRunConfirmedStep, RunConfirmedStepError, RUN_CONFIRMED_STEP_ERROR_CODES, isRunConfirmedStepErrorCode, isStepQuoteId, } from './hooks/useRunConfirmedStep.js';
|
|
79
81
|
export type { UseRunConfirmedStep, QuotedStepBody, RunConfirmedStepErrorCode, BlockRunConfirmedStepHostError, BlockStepQuote, WorkflowBodyPassThroughStep, } from './hooks/useRunConfirmedStep.js';
|
|
80
|
-
export type { BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, AppWorkflowsParams, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockAppReaction, BlockStoredReaction, BlockReactionEntityType, BlockEntityReactionErrorCode, BlockReactionCounts, BlockEntityReactionState, BlockEntityReactionResult, BlockEntityReactionsResult, } from '@civitai/app-sdk/blocks';
|
|
82
|
+
export type { BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockWildcardPack, BlockWildcardPackErrorCode, BlockResourceFile, BlockResourceFileEntry, BlockResourceFileEntryFormat, BlockResourceFileErrorCode, BlockResourceFileMeta, AppWorkflow, AppWorkflowImage, AppWorkflowsParams, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockAppReaction, BlockStoredReaction, BlockReactionEntityType, BlockEntityReactionErrorCode, BlockReactionCounts, BlockEntityReactionState, BlockEntityReactionResult, BlockEntityReactionsResult, } from '@civitai/app-sdk/blocks';
|
|
81
83
|
export { useCheckpointPicker } from './hooks/useCheckpointPicker.js';
|
|
82
84
|
export type { UseCheckpointPicker } from './hooks/useCheckpointPicker.js';
|
|
83
85
|
export { useResourcePicker, RESOURCE_PICKER_MULTIPLE_MAX } from './hooks/useResourcePicker.js';
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ export { useBuzzTransactions } from './hooks/useBuzzTransactions.js';
|
|
|
46
46
|
export { useBuzzAccounts } from './hooks/useBuzzAccounts.js';
|
|
47
47
|
export { useDailyCompensation } from './hooks/useDailyCompensation.js';
|
|
48
48
|
export { useWildcardPack, WildcardPackError } from './hooks/useWildcardPack.js';
|
|
49
|
+
export { useResourceFile, ResourceFileError } from './hooks/useResourceFile.js';
|
|
49
50
|
export { useAppWorkflows } from './hooks/useAppWorkflows.js';
|
|
50
51
|
export { usePublishGenerationOutputs } from './hooks/usePublishGenerationOutputs.js';
|
|
51
52
|
export { useGatedImages } from './hooks/useGatedImages.js';
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* fetch chokepoints ..... 2
|
|
48
48
|
* REST endpoints ........ GET /api/v1/blocks/me
|
|
49
49
|
* tRPC procedures ....... 29 = blocks.* (14) + apps.shared.* (10) + apps.storage.* (5)
|
|
50
|
-
* switch case labels ....
|
|
50
|
+
* switch case labels .... 53, of which 15 REFUSE (enumerated under SCOPE below)
|
|
51
51
|
* --- END DERIVED ---
|
|
52
52
|
*
|
|
53
53
|
* PICKERS (Phase 1 of "make dev:live a faithful local host"): the live host
|
|
@@ -127,6 +127,9 @@
|
|
|
127
127
|
* `publishError` options. Replies with a refusal.
|
|
128
128
|
* • GET_WILDCARD_PACK — needs the session-authed resolve plus the in-tab
|
|
129
129
|
* zip/yaml parse that lives in civitai, not this SDK. Replies `parse-failed`.
|
|
130
|
+
* • GET_RESOURCE_FILE — the same session-authed resolve plus in-tab
|
|
131
|
+
* fetch/unzip/parse, and only full-page app hosts answer it. Replies
|
|
132
|
+
* `parse-failed`.
|
|
130
133
|
* • OPEN_IMAGE_UPLOAD — needs the host's native modal + session-authed byte
|
|
131
134
|
* pipeline. Replies DISMISSED (the hook resolves `null`).
|
|
132
135
|
* • OPEN_IMAGE_PICKER — needs the host's picker over the viewer's own posted
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* fetch chokepoints ..... 2
|
|
48
48
|
* REST endpoints ........ GET /api/v1/blocks/me
|
|
49
49
|
* tRPC procedures ....... 29 = blocks.* (14) + apps.shared.* (10) + apps.storage.* (5)
|
|
50
|
-
* switch case labels ....
|
|
50
|
+
* switch case labels .... 53, of which 15 REFUSE (enumerated under SCOPE below)
|
|
51
51
|
* --- END DERIVED ---
|
|
52
52
|
*
|
|
53
53
|
* PICKERS (Phase 1 of "make dev:live a faithful local host"): the live host
|
|
@@ -127,6 +127,9 @@
|
|
|
127
127
|
* `publishError` options. Replies with a refusal.
|
|
128
128
|
* • GET_WILDCARD_PACK — needs the session-authed resolve plus the in-tab
|
|
129
129
|
* zip/yaml parse that lives in civitai, not this SDK. Replies `parse-failed`.
|
|
130
|
+
* • GET_RESOURCE_FILE — the same session-authed resolve plus in-tab
|
|
131
|
+
* fetch/unzip/parse, and only full-page app hosts answer it. Replies
|
|
132
|
+
* `parse-failed`.
|
|
130
133
|
* • OPEN_IMAGE_UPLOAD — needs the host's native modal + session-authed byte
|
|
131
134
|
* pipeline. Replies DISMISSED (the hook resolves `null`).
|
|
132
135
|
* • OPEN_IMAGE_PICKER — needs the host's picker over the viewer's own posted
|
|
@@ -1207,6 +1210,26 @@ export function createLiveHost(options) {
|
|
|
1207
1210
|
});
|
|
1208
1211
|
return;
|
|
1209
1212
|
}
|
|
1213
|
+
case 'GET_RESOURCE_FILE': {
|
|
1214
|
+
// UNSUPPORTED in live v1, for the same reason as GET_WILDCARD_PACK
|
|
1215
|
+
// directly above: the real host resolves the file through a
|
|
1216
|
+
// SESSION-authed procedure and fetches + unzips + parses it in the
|
|
1217
|
+
// viewer's page tab. A liveHost holds a BLOCK token, not a session,
|
|
1218
|
+
// so it cannot resolve the file. Reply with an honest `parse-failed`
|
|
1219
|
+
// (never a fabricated file) so the hook surfaces a typed error rather
|
|
1220
|
+
// than hanging. The closed error set has no "unsupported" code, and
|
|
1221
|
+
// `parse-failed` is the one the wildcard refusal already uses.
|
|
1222
|
+
if (!isRoutableRequestId(requestId))
|
|
1223
|
+
return;
|
|
1224
|
+
logOnce('resource-file', 'GET_RESOURCE_FILE is not supported in dev:live (it needs the session-authed ' +
|
|
1225
|
+
'resolve + in-tab fetch and unzip, on a full-page app host). Replying ' +
|
|
1226
|
+
'`parse-failed`. Use dev:mock to exercise the resource-file path.');
|
|
1227
|
+
dispatchToBlock({
|
|
1228
|
+
type: 'RESOURCE_FILE_RESULT',
|
|
1229
|
+
payload: { requestId, error: 'parse-failed' },
|
|
1230
|
+
});
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1210
1233
|
case 'QUERY_APP_WORKFLOWS': {
|
|
1211
1234
|
// App generator SUBQUEUE read via the token-bound
|
|
1212
1235
|
// `blocks.queryAppWorkflows` MUTATION (POST). params (cursor/limit) are
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* called). Only the postMessage bridge round-trips are exercised. Never import
|
|
38
38
|
* this from production code.
|
|
39
39
|
*/
|
|
40
|
-
import { type BlockContext, type BlockResourceInfo, type BlockResourcePickerType, type BlockUploadedImageInfo, type BlockGenerationSourceImageInfo, type BuzzAccountType, type BlockBuzzTransaction, type BlockBuzzAccount, type BlockDailyCompensationResource, type BlockViewer, type BlockWildcardPack, type BlockWildcardPackErrorCode, type AppWorkflow, type BlockModel3dOutput, type BlockPublishedModel, type BlockRunConfirmedStepHostError, type BlockGatedImage, type BlockPickedImageInfo, type BlockCollectionFollowErrorCode, type BlockEntityReactionErrorCode, type BlockCreatePostHostError, type BlockCreatePostResult, type BlockRunTrainingHostError, type BlockTrainingDataImage, type ColorDomain, type SharedStorageValue, type Theme, type ViewerInfo, type WorkflowBody } from '@civitai/app-sdk/blocks';
|
|
40
|
+
import { type BlockContext, type BlockResourceInfo, type BlockResourcePickerType, type BlockUploadedImageInfo, type BlockGenerationSourceImageInfo, type BuzzAccountType, type BlockBuzzTransaction, type BlockBuzzAccount, type BlockDailyCompensationResource, type BlockViewer, type BlockWildcardPack, type BlockWildcardPackErrorCode, type BlockResourceFile, type BlockResourceFileErrorCode, type AppWorkflow, type BlockModel3dOutput, type BlockPublishedModel, type BlockRunConfirmedStepHostError, type BlockGatedImage, type BlockPickedImageInfo, type BlockCollectionFollowErrorCode, type BlockEntityReactionErrorCode, type BlockCreatePostHostError, type BlockCreatePostResult, type BlockRunTrainingHostError, type BlockTrainingDataImage, type ColorDomain, type SharedStorageValue, type Theme, type ViewerInfo, type WorkflowBody } from '@civitai/app-sdk/blocks';
|
|
41
41
|
import { type MockMediaUploads } from './mediaUploadBytes.js';
|
|
42
42
|
import { type MockWorkflowQuoteFailure } from './mockWorkflowGraph.js';
|
|
43
43
|
import { type MockReactionSeed } from './mockHostReactions.js';
|
|
@@ -931,6 +931,21 @@ export interface MockHostOptions {
|
|
|
931
931
|
* returned. Live-tunable via {@link MockHost.setScenario}.
|
|
932
932
|
*/
|
|
933
933
|
wildcardPackError?: BlockWildcardPackErrorCode;
|
|
934
|
+
/**
|
|
935
|
+
* The parsed file returned from `GET_RESOURCE_FILE` (what `useResourceFile`
|
|
936
|
+
* reads). Absent → {@link DEFAULT_RESOURCE_FILE}, one UI-format graph. Ignored
|
|
937
|
+
* when {@link resourceFileError} is set. The mock returns it whatever
|
|
938
|
+
* `modelVersionId` / `fileId` the block asks for.
|
|
939
|
+
*/
|
|
940
|
+
resourceFile?: BlockResourceFile;
|
|
941
|
+
/**
|
|
942
|
+
* Force `GET_RESOURCE_FILE` to reply with this code from the CLOSED set
|
|
943
|
+
* (`not-found` | `forbidden` | `unauthorized` | `too-large` | `parse-failed` |
|
|
944
|
+
* `busy` | `rate-limited`) instead of a file — exercises `useResourceFile`'s
|
|
945
|
+
* error UI. Absent → a file is returned. Live-tunable via
|
|
946
|
+
* {@link MockHost.setScenario}, where passing the key as `undefined` clears it.
|
|
947
|
+
*/
|
|
948
|
+
resourceFileError?: BlockResourceFileErrorCode;
|
|
934
949
|
/**
|
|
935
950
|
* Force `SET_COLLECTION_FOLLOW` to reply with an `error` instead of writing —
|
|
936
951
|
* exercises `useCollectionFollow`'s refusal handling. Pass a
|
|
@@ -1424,7 +1439,7 @@ export interface MockHostOptions {
|
|
|
1424
1439
|
* {@link MockHost.setTheme}, which pushes a real `THEME_CHANGE` — the mock of
|
|
1425
1440
|
* the viewer toggling dark mode with the block already mounted.)
|
|
1426
1441
|
*/
|
|
1427
|
-
export type MockHostScenarioPatch = Pick<MockHostOptions, 'consentGrantable' | 'failMode' | 'cost' | 'pollsUntilDone' | 'cannedPicks' | 'cannedMultiPicks' | 'cannedImagePicks' | 'cannedImageUpload' | 'cannedGenerationSourceUpload' | 'cannedImageScan' | 'generation' | 'buzz' | 'storage' | 'shared' | 'buzzBalanceError' | 'viewerResult' | 'viewerError' | 'buzzTransactions' | 'buzzAccounts' | 'dailyCompensation' | 'buzzReadError' | 'wildcardPack' | 'wildcardPackError' | 'collectionFollowError' | 'reactionStates' | 'reactionError' | 'createPostResult' | 'createPostError' | 'cannedTrainingDataUpload' | 'trainingDataNotUploadedHere' | 'trainingQuoteTotal' | 'runTrainingError' | 'runTrainingCapRefusal' | 'runConfirmedStepError' | 'runConfirmedStepCapRefusal' | 'appWorkflows' | 'appWorkflowsError' | 'publishImageIds' | 'publishError' | 'gatedImages' | 'gatedImagesError' | 'disallowedAccountTypes' | 'saveImageError' | 'uploadImageBytesResult' | 'uploadImageBytesError' | 'cannedMediaUploads' | 'mediaUploadError'>;
|
|
1442
|
+
export type MockHostScenarioPatch = Pick<MockHostOptions, 'consentGrantable' | 'failMode' | 'cost' | 'pollsUntilDone' | 'cannedPicks' | 'cannedMultiPicks' | 'cannedImagePicks' | 'cannedImageUpload' | 'cannedGenerationSourceUpload' | 'cannedImageScan' | 'generation' | 'buzz' | 'storage' | 'shared' | 'buzzBalanceError' | 'viewerResult' | 'viewerError' | 'buzzTransactions' | 'buzzAccounts' | 'dailyCompensation' | 'buzzReadError' | 'wildcardPack' | 'wildcardPackError' | 'resourceFile' | 'resourceFileError' | 'collectionFollowError' | 'reactionStates' | 'reactionError' | 'createPostResult' | 'createPostError' | 'cannedTrainingDataUpload' | 'trainingDataNotUploadedHere' | 'trainingQuoteTotal' | 'runTrainingError' | 'runTrainingCapRefusal' | 'runConfirmedStepError' | 'runConfirmedStepCapRefusal' | 'appWorkflows' | 'appWorkflowsError' | 'publishImageIds' | 'publishError' | 'gatedImages' | 'gatedImagesError' | 'disallowedAccountTypes' | 'saveImageError' | 'uploadImageBytesResult' | 'uploadImageBytesError' | 'cannedMediaUploads' | 'mediaUploadError'>;
|
|
1428
1443
|
/** Runtime Buzz-balance handle exposed on {@link MockHost.buzz}. */
|
|
1429
1444
|
export interface MockBuzzHandle {
|
|
1430
1445
|
/** Current simulated balance, or `undefined` when balance isn't simulated. */
|
|
@@ -1440,6 +1455,7 @@ export interface MockBuzzHandle {
|
|
|
1440
1455
|
*
|
|
1441
1456
|
* After install, a harness UI can drive scenarios live:
|
|
1442
1457
|
* - `setScenario(patch)` — merge new generation/buzz/storage/failMode controls.
|
|
1458
|
+
* `setScenario({ resourceFileError: undefined })` clears a forced resource-file error.
|
|
1443
1459
|
* - `setTheme('light' | 'dark')` — flip the site theme and push `THEME_CHANGE`.
|
|
1444
1460
|
* - `buzz.setBalance(n)` / `buzz.getBalance()` — flip the simulated wallet.
|
|
1445
1461
|
*
|
|
@@ -752,6 +752,38 @@ const DEFAULT_WILDCARD_PACK = {
|
|
|
752
752
|
truncatedLists: [],
|
|
753
753
|
maturity: { browsingLevel: SFW_LEVELS, sfwOnly: true },
|
|
754
754
|
};
|
|
755
|
+
/**
|
|
756
|
+
* Default file reported on `GET_RESOURCE_FILE`: one plain `.json` workflow in the
|
|
757
|
+
* ComfyUI UI format (a `nodes` array), so a block renders a graph out of the box.
|
|
758
|
+
*/
|
|
759
|
+
const DEFAULT_RESOURCE_FILE = {
|
|
760
|
+
meta: {
|
|
761
|
+
modelId: 618700,
|
|
762
|
+
modelVersionId: 691700,
|
|
763
|
+
fileId: 591700,
|
|
764
|
+
fileName: 'sample-workflow.json',
|
|
765
|
+
modelName: 'Sample Workflow',
|
|
766
|
+
versionName: 'v1.0',
|
|
767
|
+
creatorUsername: 'creator',
|
|
768
|
+
},
|
|
769
|
+
entries: [
|
|
770
|
+
{
|
|
771
|
+
path: 'sample-workflow.json',
|
|
772
|
+
format: 'ui',
|
|
773
|
+
graph: {
|
|
774
|
+
last_node_id: 2,
|
|
775
|
+
last_link_id: 1,
|
|
776
|
+
nodes: [
|
|
777
|
+
{ id: 1, type: 'CheckpointLoaderSimple', inputs: [], outputs: [{ name: 'MODEL', links: [1] }] },
|
|
778
|
+
{ id: 2, type: 'KSampler', inputs: [{ name: 'model', link: 1 }], outputs: [] },
|
|
779
|
+
],
|
|
780
|
+
links: [[1, 1, 0, 2, 0, 'MODEL']],
|
|
781
|
+
version: 0.4,
|
|
782
|
+
},
|
|
783
|
+
},
|
|
784
|
+
],
|
|
785
|
+
truncated: false,
|
|
786
|
+
};
|
|
755
787
|
/**
|
|
756
788
|
* Default app generator SUBQUEUE page reported on `QUERY_APP_WORKFLOWS`. A small
|
|
757
789
|
* mixed-status list (a done gen with two images, one still processing) so a block
|
|
@@ -1136,6 +1168,9 @@ export function createMockHost(options = {}) {
|
|
|
1136
1168
|
// Wildcard-pack bridge data + forced discriminated-error knob.
|
|
1137
1169
|
let wildcardPack = options.wildcardPack ?? DEFAULT_WILDCARD_PACK;
|
|
1138
1170
|
let wildcardPackError = options.wildcardPackError;
|
|
1171
|
+
// Resource-file bridge data + forced closed-set error knob.
|
|
1172
|
+
let resourceFile = options.resourceFile ?? DEFAULT_RESOURCE_FILE;
|
|
1173
|
+
let resourceFileError = options.resourceFileError;
|
|
1139
1174
|
// Collection-follow bridge.
|
|
1140
1175
|
//
|
|
1141
1176
|
// 🔴 THERE IS DELIBERATELY NO FOLLOW-STATE MAP HERE, and an earlier version's
|
|
@@ -2616,6 +2651,25 @@ export function createMockHost(options = {}) {
|
|
|
2616
2651
|
});
|
|
2617
2652
|
return;
|
|
2618
2653
|
}
|
|
2654
|
+
case 'GET_RESOURCE_FILE': {
|
|
2655
|
+
// Token-INDEPENDENT read, like GET_WILDCARD_PACK. A forced error
|
|
2656
|
+
// replies with the closed-set code (never free text), as the real
|
|
2657
|
+
// host does. The canned file is returned for any version / file id.
|
|
2658
|
+
if (!isRoutableRequestId(requestId))
|
|
2659
|
+
return;
|
|
2660
|
+
if (resourceFileError !== undefined) {
|
|
2661
|
+
dispatchToBlock({
|
|
2662
|
+
type: 'RESOURCE_FILE_RESULT',
|
|
2663
|
+
payload: { requestId, error: resourceFileError },
|
|
2664
|
+
});
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
dispatchToBlock({
|
|
2668
|
+
type: 'RESOURCE_FILE_RESULT',
|
|
2669
|
+
payload: { requestId, file: resourceFile },
|
|
2670
|
+
});
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2619
2673
|
case 'QUERY_APP_WORKFLOWS': {
|
|
2620
2674
|
// App generator SUBQUEUE read. Drop a request with no requestId
|
|
2621
2675
|
// (unroutable). A forced error replies with the FREE-TEXT error
|
|
@@ -3921,6 +3975,11 @@ export function createMockHost(options = {}) {
|
|
|
3921
3975
|
wildcardPack = patch.wildcardPack;
|
|
3922
3976
|
if (patch.wildcardPackError !== undefined)
|
|
3923
3977
|
wildcardPackError = patch.wildcardPackError;
|
|
3978
|
+
if (patch.resourceFile !== undefined)
|
|
3979
|
+
resourceFile = patch.resourceFile;
|
|
3980
|
+
// Key PRESENCE, not value: `{ resourceFileError: undefined }` clears it.
|
|
3981
|
+
if ('resourceFileError' in patch)
|
|
3982
|
+
resourceFileError = patch.resourceFileError;
|
|
3924
3983
|
if (patch.collectionFollowError !== undefined)
|
|
3925
3984
|
collectionFollowError = patch.collectionFollowError;
|
|
3926
3985
|
if (patch.reactionStates !== undefined || patch.reactionError !== undefined) {
|
|
@@ -59,6 +59,19 @@ export declare const TRAINING_DATA_UPLOAD_TIMEOUT_MS: number;
|
|
|
59
59
|
* caller shares the number instead of re-typing it.
|
|
60
60
|
*/
|
|
61
61
|
export declare const WORKFLOW_REQUEST_TIMEOUT_MS = 120000;
|
|
62
|
+
/**
|
|
63
|
+
* The bound for `GET_RESOURCE_FILE` (`hooks/useResourceFile.ts`), a `'protocol'`
|
|
64
|
+
* request whose HOST work is slow: a session-authed resolve, a fetch of the
|
|
65
|
+
* signed URL (up to 32 MB), then an unzip and parse of up to 16 MB in the
|
|
66
|
+
* viewer's tab. The host bounds all three with ONE 110 s deadline
|
|
67
|
+
* (civitai/civitai#6818 `RESOURCE_FILE_DEADLINE_MS`), after which it replies
|
|
68
|
+
* `parse-failed`. At {@link DEFAULT_REQUEST_TIMEOUT_MS} a large file on a slow
|
|
69
|
+
* connection would time out on the block while the host was still working, and
|
|
70
|
+
* the host's valid reply would be dropped as unroutable. 120 s leaves the host's
|
|
71
|
+
* 110 s deadline room to answer first. Same number as
|
|
72
|
+
* {@link WORKFLOW_REQUEST_TIMEOUT_MS}.
|
|
73
|
+
*/
|
|
74
|
+
export declare const RESOURCE_FILE_REQUEST_TIMEOUT_MS = 120000;
|
|
62
75
|
/** Which bound a block→parent message is answered under. */
|
|
63
76
|
export type RequestTimeoutClass =
|
|
64
77
|
/** Reply waits on a PERSON — must pass {@link HUMAN_INTERACTION_TIMEOUT_MS}. */
|
|
@@ -58,6 +58,19 @@ export const TRAINING_DATA_UPLOAD_TIMEOUT_MS = 60 * 60_000;
|
|
|
58
58
|
* caller shares the number instead of re-typing it.
|
|
59
59
|
*/
|
|
60
60
|
export const WORKFLOW_REQUEST_TIMEOUT_MS = 120_000;
|
|
61
|
+
/**
|
|
62
|
+
* The bound for `GET_RESOURCE_FILE` (`hooks/useResourceFile.ts`), a `'protocol'`
|
|
63
|
+
* request whose HOST work is slow: a session-authed resolve, a fetch of the
|
|
64
|
+
* signed URL (up to 32 MB), then an unzip and parse of up to 16 MB in the
|
|
65
|
+
* viewer's tab. The host bounds all three with ONE 110 s deadline
|
|
66
|
+
* (civitai/civitai#6818 `RESOURCE_FILE_DEADLINE_MS`), after which it replies
|
|
67
|
+
* `parse-failed`. At {@link DEFAULT_REQUEST_TIMEOUT_MS} a large file on a slow
|
|
68
|
+
* connection would time out on the block while the host was still working, and
|
|
69
|
+
* the host's valid reply would be dropped as unroutable. 120 s leaves the host's
|
|
70
|
+
* 110 s deadline room to answer first. Same number as
|
|
71
|
+
* {@link WORKFLOW_REQUEST_TIMEOUT_MS}.
|
|
72
|
+
*/
|
|
73
|
+
export const RESOURCE_FILE_REQUEST_TIMEOUT_MS = 120_000;
|
|
61
74
|
/**
|
|
62
75
|
* 🔴 THE TOTALITY GATE — every block→parent message type, bucketed.
|
|
63
76
|
*
|
|
@@ -158,6 +171,10 @@ const REQUEST_TIMEOUT_CLASS = {
|
|
|
158
171
|
GET_IMAGES_BY_IDS: 'protocol',
|
|
159
172
|
GET_VIEWER: 'protocol',
|
|
160
173
|
GET_WILDCARD_PACK: 'protocol',
|
|
174
|
+
// No person in the loop, so `'protocol'` — but sent under its own longer
|
|
175
|
+
// `RESOURCE_FILE_REQUEST_TIMEOUT_MS`: the host's resolve + fetch + unzip run
|
|
176
|
+
// under a 110 s deadline, which the 30 s default would cut short.
|
|
177
|
+
GET_RESOURCE_FILE: 'protocol',
|
|
161
178
|
QUERY_APP_WORKFLOWS: 'protocol',
|
|
162
179
|
REQUEST_TOKEN: 'protocol',
|
|
163
180
|
// The host fetches the blob and triggers the browser's own download; it replies
|
|
@@ -347,6 +347,17 @@ export declare function isValidDailyCompensationResult(p: unknown): boolean;
|
|
|
347
347
|
* reply, mirroring the host's enum contract.
|
|
348
348
|
*/
|
|
349
349
|
export declare function isValidWildcardPackResult(p: unknown): boolean;
|
|
350
|
+
/**
|
|
351
|
+
* Reply to a block-initiated `GET_RESOURCE_FILE`. Same contract shape as
|
|
352
|
+
* {@link isValidWildcardPackResult}: EITHER a `file` OR an `error` that is one of
|
|
353
|
+
* {@link RESOURCE_FILE_ERROR_CODES}; a free-text or unknown `error`, or a reply
|
|
354
|
+
* with neither, is malformed and dropped. The `file` is checked field by field —
|
|
355
|
+
* `meta` ids positive integers, names strings, `creatorUsername` a string or
|
|
356
|
+
* `null`, `truncated` a boolean, and EVERY entry an object with a string `path`,
|
|
357
|
+
* a known `format` and a `graph` key. `graph` itself is the host's parsed JSON
|
|
358
|
+
* and may be any JSON value.
|
|
359
|
+
*/
|
|
360
|
+
export declare function isValidResourceFileResult(p: unknown): boolean;
|
|
350
361
|
/**
|
|
351
362
|
* Reply to a block-initiated `QUERY_APP_WORKFLOWS`. A well-formed reply carries
|
|
352
363
|
* EITHER a `result` ({ workflows: AppWorkflow[], cursor: string | null }) OR a
|
|
@@ -1040,6 +1040,80 @@ export function isValidWildcardPackResult(p) {
|
|
|
1040
1040
|
return false;
|
|
1041
1041
|
return true;
|
|
1042
1042
|
}
|
|
1043
|
+
/**
|
|
1044
|
+
* The CLOSED `RESOURCE_FILE_RESULT` error set, mirroring
|
|
1045
|
+
* `BlockResourceFileErrorCode` (civitai/civitai-app-starters#664).
|
|
1046
|
+
*/
|
|
1047
|
+
const RESOURCE_FILE_ERROR_CODES = new Set([
|
|
1048
|
+
'not-found',
|
|
1049
|
+
'forbidden',
|
|
1050
|
+
'unauthorized',
|
|
1051
|
+
'too-large',
|
|
1052
|
+
'parse-failed',
|
|
1053
|
+
'busy',
|
|
1054
|
+
'rate-limited',
|
|
1055
|
+
]);
|
|
1056
|
+
const RESOURCE_FILE_ENTRY_FORMATS = new Set(['ui', 'api', 'unknown']);
|
|
1057
|
+
/**
|
|
1058
|
+
* Reply to a block-initiated `GET_RESOURCE_FILE`. Same contract shape as
|
|
1059
|
+
* {@link isValidWildcardPackResult}: EITHER a `file` OR an `error` that is one of
|
|
1060
|
+
* {@link RESOURCE_FILE_ERROR_CODES}; a free-text or unknown `error`, or a reply
|
|
1061
|
+
* with neither, is malformed and dropped. The `file` is checked field by field —
|
|
1062
|
+
* `meta` ids positive integers, names strings, `creatorUsername` a string or
|
|
1063
|
+
* `null`, `truncated` a boolean, and EVERY entry an object with a string `path`,
|
|
1064
|
+
* a known `format` and a `graph` key. `graph` itself is the host's parsed JSON
|
|
1065
|
+
* and may be any JSON value.
|
|
1066
|
+
*/
|
|
1067
|
+
export function isValidResourceFileResult(p) {
|
|
1068
|
+
if (!isObject(p))
|
|
1069
|
+
return false;
|
|
1070
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1071
|
+
return false;
|
|
1072
|
+
if (p.error !== undefined) {
|
|
1073
|
+
if (typeof p.error !== 'string' || !RESOURCE_FILE_ERROR_CODES.has(p.error))
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
if (p.file !== undefined) {
|
|
1077
|
+
const file = p.file;
|
|
1078
|
+
if (!isObject(file))
|
|
1079
|
+
return false;
|
|
1080
|
+
const meta = file.meta;
|
|
1081
|
+
if (!isObject(meta))
|
|
1082
|
+
return false;
|
|
1083
|
+
if (!isPositiveInteger(meta.modelId))
|
|
1084
|
+
return false;
|
|
1085
|
+
if (!isPositiveInteger(meta.modelVersionId))
|
|
1086
|
+
return false;
|
|
1087
|
+
if (!isPositiveInteger(meta.fileId))
|
|
1088
|
+
return false;
|
|
1089
|
+
if (typeof meta.fileName !== 'string')
|
|
1090
|
+
return false;
|
|
1091
|
+
if (typeof meta.modelName !== 'string')
|
|
1092
|
+
return false;
|
|
1093
|
+
if (typeof meta.versionName !== 'string')
|
|
1094
|
+
return false;
|
|
1095
|
+
if (meta.creatorUsername !== null && typeof meta.creatorUsername !== 'string')
|
|
1096
|
+
return false;
|
|
1097
|
+
if (typeof file.truncated !== 'boolean')
|
|
1098
|
+
return false;
|
|
1099
|
+
if (!Array.isArray(file.entries))
|
|
1100
|
+
return false;
|
|
1101
|
+
for (const entry of file.entries) {
|
|
1102
|
+
if (!isObject(entry))
|
|
1103
|
+
return false;
|
|
1104
|
+
if (typeof entry.path !== 'string')
|
|
1105
|
+
return false;
|
|
1106
|
+
if (typeof entry.format !== 'string' || !RESOURCE_FILE_ENTRY_FORMATS.has(entry.format)) {
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
if (!('graph' in entry))
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (p.file === undefined && p.error === undefined)
|
|
1114
|
+
return false;
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1043
1117
|
// ============================================================
|
|
1044
1118
|
// App generator SUBQUEUE reply validators
|
|
1045
1119
|
// ============================================================
|
|
@@ -2302,6 +2376,8 @@ export function payloadValidatorFor(type) {
|
|
|
2302
2376
|
return isValidDailyCompensationResult;
|
|
2303
2377
|
case 'WILDCARD_PACK_RESULT':
|
|
2304
2378
|
return isValidWildcardPackResult;
|
|
2379
|
+
case 'RESOURCE_FILE_RESULT':
|
|
2380
|
+
return isValidResourceFileResult;
|
|
2305
2381
|
case 'APP_WORKFLOWS_RESULT':
|
|
2306
2382
|
return isValidAppWorkflowsResult;
|
|
2307
2383
|
case 'CANCEL_APP_WORKFLOW_RESULT':
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/* AUTOGENERATED from package.json by scripts/gen-version.mjs. DO NOT EDIT. */
|
|
2
2
|
/** This package's version, reported to the host as `sdkVersion` in `BLOCK_READY`. */
|
|
3
|
-
export const VERSION = "0.
|
|
3
|
+
export const VERSION = "0.78.0";
|
|
4
4
|
//# sourceMappingURL=version.generated.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civitai/blocks-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.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.79.0"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|