@civitai/blocks-react 0.76.1 → 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.
@@ -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
- * The guard accepts EITHER shape (the result carries no purpose discriminator).
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) && !isGenerationSourceUpload(s) && !isPendingUpload(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
  }
@@ -1022,6 +1040,80 @@ export function isValidWildcardPackResult(p) {
1022
1040
  return false;
1023
1041
  return true;
1024
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
+ }
1025
1117
  // ============================================================
1026
1118
  // App generator SUBQUEUE reply validators
1027
1119
  // ============================================================
@@ -1533,48 +1625,6 @@ export function isValidCreatePostResult(p) {
1533
1625
  return false;
1534
1626
  return true;
1535
1627
  }
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
1628
  /**
1579
1629
  * Reply to `RUN_TRAINING`. A well-formed reply carries EITHER a `snapshot` (a
1580
1630
  * valid {@link isValidWorkflowSnapshot} — the block polls its `workflowId`) OR an
@@ -2326,6 +2376,8 @@ export function payloadValidatorFor(type) {
2326
2376
  return isValidDailyCompensationResult;
2327
2377
  case 'WILDCARD_PACK_RESULT':
2328
2378
  return isValidWildcardPackResult;
2379
+ case 'RESOURCE_FILE_RESULT':
2380
+ return isValidResourceFileResult;
2329
2381
  case 'APP_WORKFLOWS_RESULT':
2330
2382
  return isValidAppWorkflowsResult;
2331
2383
  case 'CANCEL_APP_WORKFLOW_RESULT':
@@ -2349,8 +2401,6 @@ export function payloadValidatorFor(type) {
2349
2401
  // Same caveat as CREATE_POST_RESULT: presence is compiler-enforced, the
2350
2402
  // identity is pinned by test (`validate.test.ts`). TRAINING_RESULT settles a
2351
2403
  // charged run.
2352
- case 'TRAINING_DATASET_RESULT':
2353
- return isValidTrainingDatasetResult;
2354
2404
  case 'TRAINING_RESULT':
2355
2405
  return isValidTrainingResult;
2356
2406
  // Settles a charged job, like TRAINING_RESULT directly above.
@@ -1,3 +1,3 @@
1
1
  /** This package's version, reported to the host as `sdkVersion` in `BLOCK_READY`. */
2
- export declare const VERSION = "0.76.1";
2
+ export declare const VERSION = "0.78.0";
3
3
  //# sourceMappingURL=version.generated.d.ts.map
@@ -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.76.1";
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.76.1",
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.75.0"
61
+ "@civitai/app-sdk": "^0.79.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