@civitai/blocks-react 0.76.1 → 0.77.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 +63 -52
- package/dist/hooks/useImageUpload.d.ts +40 -6
- package/dist/hooks/useImageUpload.js +19 -3
- package/dist/hooks/useRunTraining.d.ts +31 -10
- package/dist/hooks/useRunTraining.js +32 -7
- package/dist/index.d.ts +2 -4
- package/dist/index.js +0 -1
- package/dist/internal/liveHost.d.ts +5 -9
- package/dist/internal/liveHost.js +21 -29
- package/dist/internal/mockHost.d.ts +37 -44
- package/dist/internal/mockHost.js +160 -84
- package/dist/transport/requestTimeouts.d.ts +19 -7
- package/dist/transport/requestTimeouts.js +19 -12
- package/dist/transport/validate.d.ts +7 -17
- package/dist/transport/validate.js +21 -47
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +2 -2
- package/dist/hooks/usePrepareTrainingDataset.d.ts +0 -99
- package/dist/hooks/usePrepareTrainingDataset.js +0 -159
|
@@ -27,6 +27,23 @@ export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
|
27
27
|
* no deep hook paths, so the rename touches no reachable API.
|
|
28
28
|
*/
|
|
29
29
|
export const HUMAN_INTERACTION_TIMEOUT_MS = 10 * 60_000;
|
|
30
|
+
/**
|
|
31
|
+
* The bound for `OPEN_IMAGE_UPLOAD { purpose: 'trainingData' }` (civitai/civitai#6461),
|
|
32
|
+
* which is longer than {@link HUMAN_INTERACTION_TIMEOUT_MS} on purpose.
|
|
33
|
+
*
|
|
34
|
+
* The host replies ONCE, when its modal closes — after the viewer picked up to 50
|
|
35
|
+
* images AND the host uploaded every original. That second half is network work
|
|
36
|
+
* the viewer does not control, so ten minutes from `open()` can expire while the
|
|
37
|
+
* host is still uploading. The block would then reject, drop the late reply, and
|
|
38
|
+
* never learn the keys the host now remembers for `RUN_TRAINING`. The host always
|
|
39
|
+
* answers on close (images, or a bare cancel), so the bound only has to end a wait
|
|
40
|
+
* on a host that will never answer; an hour does that without cutting off a slow
|
|
41
|
+
* upload.
|
|
42
|
+
*
|
|
43
|
+
* Still in the `'human'` bucket of {@link REQUEST_TIMEOUT_CLASS}: it is at least
|
|
44
|
+
* the human bound, which is what that bucket guarantees.
|
|
45
|
+
*/
|
|
46
|
+
export const TRAINING_DATA_UPLOAD_TIMEOUT_MS = 60 * 60_000;
|
|
30
47
|
/**
|
|
31
48
|
* The bound for a `'protocol'` request whose SERVER WORK is itself slow — the
|
|
32
49
|
* host forwards it to the orchestrator and replies only once that answers. No
|
|
@@ -35,14 +52,9 @@ export const HUMAN_INTERACTION_TIMEOUT_MS = 10 * 60_000;
|
|
|
35
52
|
*
|
|
36
53
|
* Callers: the workflow trio in `hooks/useBuzzWorkflow.ts` (a submit is a whatif
|
|
37
54
|
* preflight plus the real submit plus a prompt audit), and
|
|
38
|
-
* `
|
|
39
|
-
* server moderates the captions and imports up to 50 images into the
|
|
40
|
-
* orchestrator, the import alone under a 60s budget
|
|
41
|
-
* (`BLOCK_TRAINING_IMPORT_BUDGET_MS` in civitai/civitai's
|
|
42
|
-
* `block-training-dataset.service.ts`) — so the 30s default would reject a
|
|
43
|
-
* healthy prepare mid-import.
|
|
55
|
+
* `ESTIMATE_WORKFLOW_BATCH` in `hooks/useBatchEstimate.ts`.
|
|
44
56
|
*
|
|
45
|
-
* Was a module-local constant in `useBuzzWorkflow.ts`; moved here so
|
|
57
|
+
* Was a module-local constant in `useBuzzWorkflow.ts`; moved here so every
|
|
46
58
|
* caller shares the number instead of re-typing it.
|
|
47
59
|
*/
|
|
48
60
|
export const WORKFLOW_REQUEST_TIMEOUT_MS = 120_000;
|
|
@@ -130,11 +142,6 @@ const REQUEST_TIMEOUT_CLASS = {
|
|
|
130
142
|
APP_STORAGE_SET: 'protocol',
|
|
131
143
|
CANCEL_APP_WORKFLOW: 'protocol',
|
|
132
144
|
CANCEL_WORKFLOW: 'protocol',
|
|
133
|
-
// No dialog — preparing a dataset charges nothing and the host replies when
|
|
134
|
-
// the server does. Server-bound, so `'protocol'`; but sent under
|
|
135
|
-
// `WORKFLOW_REQUEST_TIMEOUT_MS`, not the 30s default, because the server's
|
|
136
|
-
// image import alone may take 60s. Same reasoning as the workflow trio below.
|
|
137
|
-
PREPARE_TRAINING_DATASET: 'protocol',
|
|
138
145
|
// The workflow trio is server-bound, not person-bound. It carries its OWN
|
|
139
146
|
// longer bound (`WORKFLOW_REQUEST_TIMEOUT_MS` in `hooks/useBuzzWorkflow.ts`)
|
|
140
147
|
// for orchestrator latency — a different reason for a different number.
|
|
@@ -245,7 +245,8 @@ export declare function isValidViewerResult(p: unknown): p is {
|
|
|
245
245
|
* value, `url` non-empty).
|
|
246
246
|
* • `'generationSource'` → the source `{ url, width, height }` (positive finite
|
|
247
247
|
* dimensions, non-empty url; no imageId/nsfwLevel).
|
|
248
|
-
*
|
|
248
|
+
* • `'trainingData'` → `{ images: [{ url, air }] }` (non-empty strings).
|
|
249
|
+
* The guard accepts ANY of the shapes (the result carries no purpose discriminator).
|
|
249
250
|
* Kept in lockstep with the host's `IMAGE_UPLOAD_RESULT` (civitai/civitai
|
|
250
251
|
* `PageBlockHost.tsx` / `BlockImageUploadModal.tsx` /
|
|
251
252
|
* `BlockGenerationSourceUploadModal.tsx`).
|
|
@@ -265,6 +266,11 @@ export declare function isValidImageUploadResult(p: unknown): p is {
|
|
|
265
266
|
status: 'pending';
|
|
266
267
|
imageId: number;
|
|
267
268
|
url: string;
|
|
269
|
+
} | {
|
|
270
|
+
images: Array<{
|
|
271
|
+
url: string;
|
|
272
|
+
air: string;
|
|
273
|
+
}>;
|
|
268
274
|
};
|
|
269
275
|
error?: string;
|
|
270
276
|
};
|
|
@@ -442,22 +448,6 @@ export declare function isValidEntityReactionsResult(p: unknown): boolean;
|
|
|
442
448
|
* so a drop at least times out visibly.
|
|
443
449
|
*/
|
|
444
450
|
export declare function isValidCreatePostResult(p: unknown): boolean;
|
|
445
|
-
/**
|
|
446
|
-
* Reply to `PREPARE_TRAINING_DATASET`. A well-formed reply carries EITHER a
|
|
447
|
-
* `result` (`{ datasetId: non-empty string, count: integer ≥ 0, rejected: [{
|
|
448
|
-
* imageId: number, reason: string }] }`) OR an `error`; one with neither is
|
|
449
|
-
* malformed and dropped.
|
|
450
|
-
*
|
|
451
|
-
* 🔴 `error` IS SHAPE-CHECKED ONLY — the same decision as
|
|
452
|
-
* {@link isValidCreatePostResult}: the host's closed codes share the field with
|
|
453
|
-
* any server message it forwards, and a membership check would drop those
|
|
454
|
-
* replies before correlation, leaving the request to its timer.
|
|
455
|
-
*
|
|
456
|
-
* `rejected[].reason` is checked as a STRING, not against today's six members —
|
|
457
|
-
* the host adding a reason must not make every prepare reply that carries it a
|
|
458
|
-
* dropped one (the forward-compatibility rule in {@link isValidWorkflowSnapshot}).
|
|
459
|
-
*/
|
|
460
|
-
export declare function isValidTrainingDatasetResult(p: unknown): boolean;
|
|
461
451
|
/**
|
|
462
452
|
* Reply to `RUN_TRAINING`. A well-formed reply carries EITHER a `snapshot` (a
|
|
463
453
|
* valid {@link isValidWorkflowSnapshot} — the block polls its `workflowId`) OR an
|
|
@@ -770,6 +770,18 @@ function isPendingUpload(s) {
|
|
|
770
770
|
return false;
|
|
771
771
|
return true;
|
|
772
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* The `purpose:'trainingData'` `IMAGE_UPLOAD_RESULT.selected` shape
|
|
775
|
+
* (civitai/civitai#6461): `{ images: [{ url, air }] }`, each `url` and `air` a
|
|
776
|
+
* non-empty string. Mirrors the host's `TrainingDataUpload`. `air` is opaque —
|
|
777
|
+
* its key format is the host's to check, at the estimate — so only its type is
|
|
778
|
+
* pinned here.
|
|
779
|
+
*/
|
|
780
|
+
function isTrainingDataUpload(s) {
|
|
781
|
+
if (!Array.isArray(s.images))
|
|
782
|
+
return false;
|
|
783
|
+
return s.images.every((img) => isObject(img) && isNonEmptyString(img.url) && isNonEmptyString(img.air));
|
|
784
|
+
}
|
|
773
785
|
/**
|
|
774
786
|
* Reply to a block-initiated `OPEN_IMAGE_UPLOAD`. `selected` is ABSENT when the
|
|
775
787
|
* user dismissed the upload modal (the block's hook resolves to `null`). When
|
|
@@ -779,7 +791,8 @@ function isPendingUpload(s) {
|
|
|
779
791
|
* value, `url` non-empty).
|
|
780
792
|
* • `'generationSource'` → the source `{ url, width, height }` (positive finite
|
|
781
793
|
* dimensions, non-empty url; no imageId/nsfwLevel).
|
|
782
|
-
*
|
|
794
|
+
* • `'trainingData'` → `{ images: [{ url, air }] }` (non-empty strings).
|
|
795
|
+
* The guard accepts ANY of the shapes (the result carries no purpose discriminator).
|
|
783
796
|
* Kept in lockstep with the host's `IMAGE_UPLOAD_RESULT` (civitai/civitai
|
|
784
797
|
* `PageBlockHost.tsx` / `BlockImageUploadModal.tsx` /
|
|
785
798
|
* `BlockGenerationSourceUploadModal.tsx`).
|
|
@@ -797,13 +810,18 @@ export function isValidImageUploadResult(p) {
|
|
|
797
810
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
798
811
|
return false;
|
|
799
812
|
// `selected` absent → cancelled upload (valid). When present, it must match
|
|
800
|
-
// one of the purpose-keyed shapes (moderated / generationSource / pending
|
|
813
|
+
// one of the purpose-keyed shapes (moderated / generationSource / pending /
|
|
814
|
+
// trainingData).
|
|
801
815
|
if (p.selected !== undefined) {
|
|
802
816
|
const s = p.selected;
|
|
803
817
|
if (!isObject(s))
|
|
804
818
|
return false;
|
|
805
|
-
if (!isModeratedUpload(s) &&
|
|
819
|
+
if (!isModeratedUpload(s) &&
|
|
820
|
+
!isGenerationSourceUpload(s) &&
|
|
821
|
+
!isPendingUpload(s) &&
|
|
822
|
+
!isTrainingDataUpload(s)) {
|
|
806
823
|
return false;
|
|
824
|
+
}
|
|
807
825
|
}
|
|
808
826
|
return true;
|
|
809
827
|
}
|
|
@@ -1533,48 +1551,6 @@ export function isValidCreatePostResult(p) {
|
|
|
1533
1551
|
return false;
|
|
1534
1552
|
return true;
|
|
1535
1553
|
}
|
|
1536
|
-
/**
|
|
1537
|
-
* Reply to `PREPARE_TRAINING_DATASET`. A well-formed reply carries EITHER a
|
|
1538
|
-
* `result` (`{ datasetId: non-empty string, count: integer ≥ 0, rejected: [{
|
|
1539
|
-
* imageId: number, reason: string }] }`) OR an `error`; one with neither is
|
|
1540
|
-
* malformed and dropped.
|
|
1541
|
-
*
|
|
1542
|
-
* 🔴 `error` IS SHAPE-CHECKED ONLY — the same decision as
|
|
1543
|
-
* {@link isValidCreatePostResult}: the host's closed codes share the field with
|
|
1544
|
-
* any server message it forwards, and a membership check would drop those
|
|
1545
|
-
* replies before correlation, leaving the request to its timer.
|
|
1546
|
-
*
|
|
1547
|
-
* `rejected[].reason` is checked as a STRING, not against today's six members —
|
|
1548
|
-
* the host adding a reason must not make every prepare reply that carries it a
|
|
1549
|
-
* dropped one (the forward-compatibility rule in {@link isValidWorkflowSnapshot}).
|
|
1550
|
-
*/
|
|
1551
|
-
export function isValidTrainingDatasetResult(p) {
|
|
1552
|
-
if (!isObject(p))
|
|
1553
|
-
return false;
|
|
1554
|
-
if (!isWireRequestIdShape(p.requestId))
|
|
1555
|
-
return false;
|
|
1556
|
-
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1557
|
-
return false;
|
|
1558
|
-
if (p.result !== undefined) {
|
|
1559
|
-
const r = p.result;
|
|
1560
|
-
if (!isObject(r))
|
|
1561
|
-
return false;
|
|
1562
|
-
if (!isNonEmptyString(r.datasetId))
|
|
1563
|
-
return false;
|
|
1564
|
-
if (!Number.isInteger(r.count) || r.count < 0)
|
|
1565
|
-
return false;
|
|
1566
|
-
if (!Array.isArray(r.rejected))
|
|
1567
|
-
return false;
|
|
1568
|
-
for (const x of r.rejected) {
|
|
1569
|
-
if (!isObject(x) || !isFiniteNumber(x.imageId) || typeof x.reason !== 'string') {
|
|
1570
|
-
return false;
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
if (p.result === undefined && p.error === undefined)
|
|
1575
|
-
return false;
|
|
1576
|
-
return true;
|
|
1577
|
-
}
|
|
1578
1554
|
/**
|
|
1579
1555
|
* Reply to `RUN_TRAINING`. A well-formed reply carries EITHER a `snapshot` (a
|
|
1580
1556
|
* valid {@link isValidWorkflowSnapshot} — the block polls its `workflowId`) OR an
|
|
@@ -2349,8 +2325,6 @@ export function payloadValidatorFor(type) {
|
|
|
2349
2325
|
// Same caveat as CREATE_POST_RESULT: presence is compiler-enforced, the
|
|
2350
2326
|
// identity is pinned by test (`validate.test.ts`). TRAINING_RESULT settles a
|
|
2351
2327
|
// charged run.
|
|
2352
|
-
case 'TRAINING_DATASET_RESULT':
|
|
2353
|
-
return isValidTrainingDatasetResult;
|
|
2354
2328
|
case 'TRAINING_RESULT':
|
|
2355
2329
|
return isValidTrainingResult;
|
|
2356
2330
|
// Settles a charged job, like TRAINING_RESULT directly above.
|
|
@@ -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.77.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.77.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.77.0"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import type { BlockPrepareTrainingDatasetHostError, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason } from '@civitai/app-sdk/blocks';
|
|
2
|
-
import type { ConsentRetryOptions } from './consentRetryOptions.js';
|
|
3
|
-
export type { BlockPrepareTrainingDatasetHostError, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, };
|
|
4
|
-
/**
|
|
5
|
-
* The host's CLOSED refusal codes for `PREPARE_TRAINING_DATASET`, as a runtime
|
|
6
|
-
* list. Mirrors civitai/civitai's `PREPARE_TRAINING_DATASET_HOST_ERRORS`.
|
|
7
|
-
*
|
|
8
|
-
* 🔴 THE ERROR CHANNEL IS NOT AN ENUM: it also carries free-text server
|
|
9
|
-
* messages, so "is this a code?" is a membership question at runtime — see
|
|
10
|
-
* {@link isPrepareTrainingDatasetErrorCode}.
|
|
11
|
-
*/
|
|
12
|
-
export declare const PREPARE_TRAINING_DATASET_ERROR_CODES: readonly ["review-mode", "block is not ready", "sign in to train", "invalid training dataset", "no block token"];
|
|
13
|
-
/** `true` when `error` is one of the host's closed refusal codes rather than a server message. */
|
|
14
|
-
export declare function isPrepareTrainingDatasetErrorCode(error: string): error is BlockPrepareTrainingDatasetHostError;
|
|
15
|
-
/**
|
|
16
|
-
* A dataset-preparation failure.
|
|
17
|
-
*
|
|
18
|
-
* `.code` is the host's refusal code when the HOST refused, `undefined` for a
|
|
19
|
-
* server message or a transport timeout. Check `.timedOut` before rendering
|
|
20
|
-
* `.message` — a timeout's message is an SDK-internal string.
|
|
21
|
-
*
|
|
22
|
-
* Preparing charges nothing, so no failure here costs the viewer Buzz.
|
|
23
|
-
*/
|
|
24
|
-
export declare class PrepareTrainingDatasetError extends Error {
|
|
25
|
-
/** The closed host refusal code, or `undefined` for a server/transport error. */
|
|
26
|
-
readonly code?: BlockPrepareTrainingDatasetHostError;
|
|
27
|
-
/**
|
|
28
|
-
* No reply arrived within the bound. The server may still have prepared a
|
|
29
|
-
* dataset; nothing was charged either way, and preparing again simply makes a
|
|
30
|
-
* new handle.
|
|
31
|
-
*/
|
|
32
|
-
readonly timedOut: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* There is no session — the viewer is signed out (`sign in to train`), or their
|
|
35
|
-
* session ended (a bare `UNAUTHORIZED`). Route into `useRequestSignIn()`.
|
|
36
|
-
*/
|
|
37
|
-
readonly signInRequired: boolean;
|
|
38
|
-
constructor(error: string, opts?: {
|
|
39
|
-
timedOut?: boolean;
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
/** What {@link usePrepareTrainingDataset} returns. */
|
|
43
|
-
export interface UsePrepareTrainingDataset {
|
|
44
|
-
/**
|
|
45
|
-
* Ask the host to prepare a training dataset from the viewer's OWN images.
|
|
46
|
-
* Resolves with the opaque `datasetId`, the admitted `count` and the
|
|
47
|
-
* `rejected` images. REJECTS with a {@link PrepareTrainingDatasetError}.
|
|
48
|
-
*
|
|
49
|
-
* A resolved result admitted AT LEAST ONE image (`count >= 1`) — when nothing
|
|
50
|
-
* is admitted the server refuses instead, and this rejects with its message
|
|
51
|
-
* (`none of the requested images can be used for training` / `… could be
|
|
52
|
-
* prepared for training`, no `.code`). Read `rejected` for what was left out:
|
|
53
|
-
* `pending-scan` and `import-unavailable` are retryable; the rest are not.
|
|
54
|
-
* The per-image reasons are not available when everything was rejected.
|
|
55
|
-
*/
|
|
56
|
-
prepareDataset: (items: BlockTrainingDatasetItem[], options?: ConsentRetryOptions) => Promise<BlockTrainingDatasetResult>;
|
|
57
|
-
/** `true` while a request is in flight. */
|
|
58
|
-
pending: boolean;
|
|
59
|
-
/** The last request's failure, or `null`. Cleared at the start of the next call. */
|
|
60
|
-
error: PrepareTrainingDatasetError | null;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Step 1 of the App Blocks `kind: 'training'` flow: turn the viewer's own images
|
|
64
|
-
* and your captions into a dataset handle for `WorkflowBodyTraining.datasetId`,
|
|
65
|
-
* through the host-mediated `PREPARE_TRAINING_DATASET` → `TRAINING_DATASET_RESULT`
|
|
66
|
-
* bridge.
|
|
67
|
-
*
|
|
68
|
-
* 🔴 AVAILABILITY: page apps only, behind the host flag `app-blocks-training-kind`
|
|
69
|
-
* (ships off), with `ai:write:budgeted` declared in the manifest and granted by a
|
|
70
|
-
* signed-in viewer. Refused from `dev:live` and from review sessions — build the
|
|
71
|
-
* flow against the mock host. See `WorkflowBodyTraining` in `@civitai/app-sdk`.
|
|
72
|
-
*
|
|
73
|
-
* Only the viewer's OWN scanned, unflagged images within the page's maturity
|
|
74
|
-
* ceiling are admitted; captions are moderated. Bounds: 1–50 items, captions up
|
|
75
|
-
* to 1,000 characters (`BLOCK_TRAINING_DATASET_MAX_ITEMS`,
|
|
76
|
-
* `BLOCK_TRAINING_CAPTION_MAX_CHARS`); the host refuses anything else with
|
|
77
|
-
* `invalid training dataset` before calling the server.
|
|
78
|
-
*
|
|
79
|
-
* No dialog and no charge. When the token lacks `ai:write:budgeted`, the call
|
|
80
|
-
* opens the host's consent prompt and retries once on a grant (opt out with
|
|
81
|
-
* `{ autoRequestConsent: false }`).
|
|
82
|
-
*
|
|
83
|
-
* Sent under the 120s server-work bound, not the 30s protocol default: the server
|
|
84
|
-
* imports up to 50 images into the orchestrator within its own 60s budget.
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* const { prepareDataset } = usePrepareTrainingDataset();
|
|
88
|
-
* try {
|
|
89
|
-
* const dataset = await prepareDataset(
|
|
90
|
-
* picked.map((img) => ({ imageId: img.id, caption: img.caption })),
|
|
91
|
-
* );
|
|
92
|
-
* if (dataset.rejected.length > 0) showNotice(`${dataset.rejected.length} image(s) left out`);
|
|
93
|
-
* } catch (e) {
|
|
94
|
-
* // Includes "none of the requested images can be used for training".
|
|
95
|
-
* if (e instanceof PrepareTrainingDatasetError) showError(e.message);
|
|
96
|
-
* }
|
|
97
|
-
*/
|
|
98
|
-
export declare function usePrepareTrainingDataset(): UsePrepareTrainingDataset;
|
|
99
|
-
//# sourceMappingURL=usePrepareTrainingDataset.d.ts.map
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { BLOCK_SCOPES } from '@civitai/app-sdk/blocks';
|
|
3
|
-
import { withConsentRetry } from '../internal/withConsentRetry.js';
|
|
4
|
-
import { WORKFLOW_REQUEST_TIMEOUT_MS } from '../transport/requestTimeouts.js';
|
|
5
|
-
import { getTransport } from '../transport/singleton.js';
|
|
6
|
-
import { RequestTimeoutError, sendTypedRequest } from '../transport/transport.js';
|
|
7
|
-
import { useTransportConnection } from './useTransportConnection.js';
|
|
8
|
-
/**
|
|
9
|
-
* The consent-gated scope the training flow requires — `ai:write:budgeted`. The
|
|
10
|
-
* server refuses a dataset request from a token without it (it is the spend
|
|
11
|
-
* scope, and preparing is gated exactly like the run it prepares for). From
|
|
12
|
-
* {@link BLOCK_SCOPES}, never a literal: a typo would make the automatic consent
|
|
13
|
-
* prompt silently do nothing.
|
|
14
|
-
*/
|
|
15
|
-
const TRAINING_SCOPES = [BLOCK_SCOPES.AI_WRITE_BUDGETED];
|
|
16
|
-
/**
|
|
17
|
-
* The host's CLOSED refusal codes for `PREPARE_TRAINING_DATASET`, as a runtime
|
|
18
|
-
* list. Mirrors civitai/civitai's `PREPARE_TRAINING_DATASET_HOST_ERRORS`.
|
|
19
|
-
*
|
|
20
|
-
* 🔴 THE ERROR CHANNEL IS NOT AN ENUM: it also carries free-text server
|
|
21
|
-
* messages, so "is this a code?" is a membership question at runtime — see
|
|
22
|
-
* {@link isPrepareTrainingDatasetErrorCode}.
|
|
23
|
-
*/
|
|
24
|
-
export const PREPARE_TRAINING_DATASET_ERROR_CODES = [
|
|
25
|
-
'review-mode',
|
|
26
|
-
'block is not ready',
|
|
27
|
-
'sign in to train',
|
|
28
|
-
'invalid training dataset',
|
|
29
|
-
'no block token',
|
|
30
|
-
];
|
|
31
|
-
const CODE_SET = new Set(PREPARE_TRAINING_DATASET_ERROR_CODES);
|
|
32
|
-
/** `true` when `error` is one of the host's closed refusal codes rather than a server message. */
|
|
33
|
-
export function isPrepareTrainingDatasetErrorCode(error) {
|
|
34
|
-
return CODE_SET.has(error);
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* A dataset-preparation failure.
|
|
38
|
-
*
|
|
39
|
-
* `.code` is the host's refusal code when the HOST refused, `undefined` for a
|
|
40
|
-
* server message or a transport timeout. Check `.timedOut` before rendering
|
|
41
|
-
* `.message` — a timeout's message is an SDK-internal string.
|
|
42
|
-
*
|
|
43
|
-
* Preparing charges nothing, so no failure here costs the viewer Buzz.
|
|
44
|
-
*/
|
|
45
|
-
export class PrepareTrainingDatasetError extends Error {
|
|
46
|
-
/** The closed host refusal code, or `undefined` for a server/transport error. */
|
|
47
|
-
code;
|
|
48
|
-
/**
|
|
49
|
-
* No reply arrived within the bound. The server may still have prepared a
|
|
50
|
-
* dataset; nothing was charged either way, and preparing again simply makes a
|
|
51
|
-
* new handle.
|
|
52
|
-
*/
|
|
53
|
-
timedOut;
|
|
54
|
-
/**
|
|
55
|
-
* There is no session — the viewer is signed out (`sign in to train`), or their
|
|
56
|
-
* session ended (a bare `UNAUTHORIZED`). Route into `useRequestSignIn()`.
|
|
57
|
-
*/
|
|
58
|
-
signInRequired;
|
|
59
|
-
constructor(error, opts) {
|
|
60
|
-
super(error);
|
|
61
|
-
this.name = 'PrepareTrainingDatasetError';
|
|
62
|
-
this.timedOut = opts?.timedOut === true;
|
|
63
|
-
if (isPrepareTrainingDatasetErrorCode(error))
|
|
64
|
-
this.code = error;
|
|
65
|
-
this.signInRequired = error === 'sign in to train' || error === 'UNAUTHORIZED';
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Step 1 of the App Blocks `kind: 'training'` flow: turn the viewer's own images
|
|
70
|
-
* and your captions into a dataset handle for `WorkflowBodyTraining.datasetId`,
|
|
71
|
-
* through the host-mediated `PREPARE_TRAINING_DATASET` → `TRAINING_DATASET_RESULT`
|
|
72
|
-
* bridge.
|
|
73
|
-
*
|
|
74
|
-
* 🔴 AVAILABILITY: page apps only, behind the host flag `app-blocks-training-kind`
|
|
75
|
-
* (ships off), with `ai:write:budgeted` declared in the manifest and granted by a
|
|
76
|
-
* signed-in viewer. Refused from `dev:live` and from review sessions — build the
|
|
77
|
-
* flow against the mock host. See `WorkflowBodyTraining` in `@civitai/app-sdk`.
|
|
78
|
-
*
|
|
79
|
-
* Only the viewer's OWN scanned, unflagged images within the page's maturity
|
|
80
|
-
* ceiling are admitted; captions are moderated. Bounds: 1–50 items, captions up
|
|
81
|
-
* to 1,000 characters (`BLOCK_TRAINING_DATASET_MAX_ITEMS`,
|
|
82
|
-
* `BLOCK_TRAINING_CAPTION_MAX_CHARS`); the host refuses anything else with
|
|
83
|
-
* `invalid training dataset` before calling the server.
|
|
84
|
-
*
|
|
85
|
-
* No dialog and no charge. When the token lacks `ai:write:budgeted`, the call
|
|
86
|
-
* opens the host's consent prompt and retries once on a grant (opt out with
|
|
87
|
-
* `{ autoRequestConsent: false }`).
|
|
88
|
-
*
|
|
89
|
-
* Sent under the 120s server-work bound, not the 30s protocol default: the server
|
|
90
|
-
* imports up to 50 images into the orchestrator within its own 60s budget.
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* const { prepareDataset } = usePrepareTrainingDataset();
|
|
94
|
-
* try {
|
|
95
|
-
* const dataset = await prepareDataset(
|
|
96
|
-
* picked.map((img) => ({ imageId: img.id, caption: img.caption })),
|
|
97
|
-
* );
|
|
98
|
-
* if (dataset.rejected.length > 0) showNotice(`${dataset.rejected.length} image(s) left out`);
|
|
99
|
-
* } catch (e) {
|
|
100
|
-
* // Includes "none of the requested images can be used for training".
|
|
101
|
-
* if (e instanceof PrepareTrainingDatasetError) showError(e.message);
|
|
102
|
-
* }
|
|
103
|
-
*/
|
|
104
|
-
export function usePrepareTrainingDataset() {
|
|
105
|
-
useTransportConnection();
|
|
106
|
-
const [pending, setPending] = useState(false);
|
|
107
|
-
const [error, setError] = useState(null);
|
|
108
|
-
// The consent wait can outlive the component; see `useCreatePostFromApp`.
|
|
109
|
-
const mountedRef = useRef(true);
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
mountedRef.current = true;
|
|
112
|
-
return () => {
|
|
113
|
-
mountedRef.current = false;
|
|
114
|
-
};
|
|
115
|
-
}, []);
|
|
116
|
-
/** ONE round-trip + its result contract. Re-invoked verbatim on a consent retry. */
|
|
117
|
-
const prepareOnce = useCallback(async (items) => {
|
|
118
|
-
const reply = await sendTypedRequest(getTransport(), { type: 'PREPARE_TRAINING_DATASET', payload: { items } }, 'TRAINING_DATASET_RESULT', { timeoutMs: WORKFLOW_REQUEST_TIMEOUT_MS }).catch((err) => {
|
|
119
|
-
// Stamped INSIDE the closure `withConsentRetry` re-invokes, so its rule 4
|
|
120
|
-
// sees it: a 120s wait is not repeated behind a consent prompt.
|
|
121
|
-
if (err instanceof RequestTimeoutError) {
|
|
122
|
-
throw new PrepareTrainingDatasetError(err.message, { timedOut: true });
|
|
123
|
-
}
|
|
124
|
-
throw err instanceof Error ? err : new Error(String(err));
|
|
125
|
-
});
|
|
126
|
-
if (reply.error || !reply.result) {
|
|
127
|
-
// `||`, not `??`: the validator shape-checks `error`, so `''` is a valid
|
|
128
|
-
// reply that must not become an Error with an empty message. The
|
|
129
|
-
// fallback is deliberately NOT a host code — `invalid training dataset`
|
|
130
|
-
// would claim "nothing reached the server", which an empty error does
|
|
131
|
-
// not tell us.
|
|
132
|
-
throw new PrepareTrainingDatasetError(reply.error || 'training dataset could not be prepared');
|
|
133
|
-
}
|
|
134
|
-
return reply.result;
|
|
135
|
-
}, []);
|
|
136
|
-
const prepareDataset = useCallback(async (items, options) => {
|
|
137
|
-
if (mountedRef.current) {
|
|
138
|
-
setPending(true);
|
|
139
|
-
setError(null);
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
return await withConsentRetry(getTransport(), TRAINING_SCOPES, () => prepareOnce(items), options, () => mountedRef.current);
|
|
143
|
-
}
|
|
144
|
-
catch (err) {
|
|
145
|
-
const wrapped = err instanceof PrepareTrainingDatasetError
|
|
146
|
-
? err
|
|
147
|
-
: new PrepareTrainingDatasetError(err instanceof Error ? err.message : String(err));
|
|
148
|
-
if (mountedRef.current)
|
|
149
|
-
setError(wrapped);
|
|
150
|
-
throw wrapped;
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
if (mountedRef.current)
|
|
154
|
-
setPending(false);
|
|
155
|
-
}
|
|
156
|
-
}, [prepareOnce]);
|
|
157
|
-
return { prepareDataset, pending, error };
|
|
158
|
-
}
|
|
159
|
-
//# sourceMappingURL=usePrepareTrainingDataset.js.map
|