@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.
@@ -1,5 +1,5 @@
1
- import type { BlockRunTrainingHostError, BlockTrainingQuote, BlockWorkflowSnapshot, WorkflowBodyTraining } from '@civitai/app-sdk/blocks';
2
- export type { BlockRunTrainingHostError, BlockTrainingQuote, WorkflowBodyTraining };
1
+ import type { BlockRunTrainingHostError, BlockTrainingDatasetItem, BlockTrainingQuote, BlockWorkflowSnapshot, WorkflowBodyTraining } from '@civitai/app-sdk/blocks';
2
+ export type { BlockRunTrainingHostError, BlockTrainingDatasetItem, BlockTrainingQuote, WorkflowBodyTraining, };
3
3
  /**
4
4
  * A training body ready to RUN: {@link WorkflowBodyTraining} with the `quoteId`
5
5
  * its estimate returned. Required here so a body without one is a compile error
@@ -16,7 +16,7 @@ export type QuotedTrainingBody = WorkflowBodyTraining & {
16
16
  * a body that differs from the quoted one) arrive in the same field — see
17
17
  * {@link isRunTrainingErrorCode}.
18
18
  */
19
- export declare const RUN_TRAINING_ERROR_CODES: readonly ["review-mode", "block is not ready", "sign in to train", "invalid training request", "no block token", "declined", "submission-unconfirmed"];
19
+ export declare const RUN_TRAINING_ERROR_CODES: readonly ["review-mode", "block is not ready", "sign in to train", "invalid training request", "no block token", "training data not uploaded here", "declined", "submission-unconfirmed"];
20
20
  /**
21
21
  * The `code` a {@link RunTrainingError} can carry: one of the host's closed
22
22
  * refusal codes, or `'refused'` — assigned by THIS SDK, never sent by the host,
@@ -49,9 +49,15 @@ export declare function isRunTrainingErrorCode(error: string): error is BlockRun
49
49
  * `.snapshot` the refusal it arrived in. Retrying needs a new estimate and
50
50
  * usually a later time; buying Buzz does not lift these caps.
51
51
  * - `.signInRequired` — no session. Route into `useRequestSignIn()`.
52
+ * - `.trainingDataNotUploaded` (`code: 'training data not uploaded here'`) —
53
+ * the quote names an image THIS host did not upload for this block. The host
54
+ * remembers only the blobs its own `useImageUpload({ purpose: 'trainingData' })`
55
+ * uploaded, and forgets them on a page reload. Refused before any dialog:
56
+ * NO RUN, nothing charged. Ask the viewer to upload the images again, then
57
+ * estimate and run the new quote.
52
58
  * - anything else — a refusal before any submit (an expired or used quote, a
53
- * body that no longer matches its quote, an ineligible image). Estimate again
54
- * before retrying; `.message` is the host's or server's text.
59
+ * body that no longer matches its quote). Estimate again before retrying;
60
+ * `.message` is the host's or server's text.
55
61
  */
56
62
  export declare class RunTrainingError extends Error {
57
63
  /**
@@ -78,6 +84,12 @@ export declare class RunTrainingError extends Error {
78
84
  readonly timedOut: boolean;
79
85
  /** There is no session (`sign in to train`, or a bare `UNAUTHORIZED`). */
80
86
  readonly signInRequired: boolean;
87
+ /**
88
+ * The quote names an image this host did not upload for this block
89
+ * (`training data not uploaded here`). No run, nothing charged: upload the
90
+ * images again, estimate again, and run that quote.
91
+ */
92
+ readonly trainingDataNotUploaded: boolean;
81
93
  constructor(error: string, opts?: {
82
94
  timedOut?: boolean;
83
95
  refusedSnapshot?: BlockWorkflowSnapshot;
@@ -112,15 +124,19 @@ export interface UseRunTraining {
112
124
  * viewer. Refused from `dev:live` and from review sessions — build the flow
113
125
  * against the mock host. See `WorkflowBodyTraining` in `@civitai/app-sdk`.
114
126
  *
115
- * The flow: `usePrepareTrainingDataset()` → `useBuzzWorkflow().estimate(body)`
116
- * (its `trainingQuote.quoteId`) → `runTraining({ ...body, quoteId })` →
117
- * `useBuzzWorkflow().watch(workflowId)`. Change nothing in the body between the
118
- * estimate and the run: the server checks it against the quoted one.
127
+ * The flow: `useImageUpload({ purpose: 'trainingData' }).open()` (the viewer
128
+ * uploads the images; you caption them into `items: [{ air, caption }]`) →
129
+ * `useBuzzWorkflow().estimate(body)` (its `trainingQuote.quoteId`) →
130
+ * `runTraining({ ...body, quoteId })` → `useBuzzWorkflow().watch(workflowId)`.
131
+ * Change nothing in the body between the estimate and the run: the server checks
132
+ * it against the quoted one. Upload and run in the same page session: the host
133
+ * refuses a quote naming an image it did not upload for this block.
119
134
  *
120
135
  * 🔴 THE CONSENT DIALOG IS CIVITAI'S, AND IT IS THE CHARGE. Every number on it is
121
136
  * read back from the server's stored quote — never from your body. A run may cost
122
137
  * more than the token's per-call budget (the viewer confirms the exact price), up
123
- * to `BLOCK_TRAINING_MAX_BUZZ_PER_RUN` (5,000 Buzz). Images are the viewer's own.
138
+ * to `BLOCK_TRAINING_MAX_BUZZ_PER_RUN` (5,000 Buzz). Images are the ones the
139
+ * viewer uploaded through the host.
124
140
  *
125
141
  * 🔴 NO AUTOMATIC CONSENT RETRY, unlike the other consent-gated hooks — on
126
142
  * purpose. `RUN_TRAINING` carries no idempotency key and has an outcome
@@ -132,8 +148,12 @@ export interface UseRunTraining {
132
148
  * the viewer clicks or dismisses its dialog.
133
149
  *
134
150
  * @example
151
+ * const { open } = useImageUpload({ purpose: 'trainingData' });
135
152
  * const { estimate, watch } = useBuzzWorkflow();
136
153
  * const { runTraining } = useRunTraining();
154
+ * const images = await open();
155
+ * if (!images) return; // cancelled
156
+ * const body = { ...settings, items: images.map((i) => ({ air: i.air, caption: captionFor(i) })) };
137
157
  * const quote = (await estimate(body)).trainingQuote!;
138
158
  * try {
139
159
  * const started = await runTraining({ ...body, quoteId: quote.quoteId });
@@ -142,6 +162,7 @@ export interface UseRunTraining {
142
162
  * if (!(e instanceof RunTrainingError)) throw e;
143
163
  * if (e.declined) return; // no run — say nothing
144
164
  * if (e.signInRequired) return requestSignIn();
165
+ * if (e.trainingDataNotUploaded) return askToUploadAgain(); // no run — re-upload, re-estimate
145
166
  * if (e.unconfirmed) return showCheckYourTrainings(); // may be running — never auto-retry
146
167
  * if (e.refused) return showError(e.message); // a cap / availability refusal; no run, nothing charged
147
168
  * showError('Could not start training. Get a new price and try again.');
@@ -17,9 +17,12 @@ export const RUN_TRAINING_ERROR_CODES = [
17
17
  'sign in to train',
18
18
  'invalid training request',
19
19
  'no block token',
20
+ 'training data not uploaded here',
20
21
  'declined',
21
22
  'submission-unconfirmed',
22
23
  ];
24
+ /** The host code for a quote naming an image this host did not upload — see {@link RunTrainingError.trainingDataNotUploaded}. */
25
+ const NOT_UPLOADED_HERE = 'training data not uploaded here';
23
26
  const CODE_SET = new Set(RUN_TRAINING_ERROR_CODES);
24
27
  /**
25
28
  * The `workflowId` the host's training submit stamps on a snapshot that is NOT a
@@ -55,9 +58,15 @@ export function isRunTrainingErrorCode(error) {
55
58
  * `.snapshot` the refusal it arrived in. Retrying needs a new estimate and
56
59
  * usually a later time; buying Buzz does not lift these caps.
57
60
  * - `.signInRequired` — no session. Route into `useRequestSignIn()`.
61
+ * - `.trainingDataNotUploaded` (`code: 'training data not uploaded here'`) —
62
+ * the quote names an image THIS host did not upload for this block. The host
63
+ * remembers only the blobs its own `useImageUpload({ purpose: 'trainingData' })`
64
+ * uploaded, and forgets them on a page reload. Refused before any dialog:
65
+ * NO RUN, nothing charged. Ask the viewer to upload the images again, then
66
+ * estimate and run the new quote.
58
67
  * - anything else — a refusal before any submit (an expired or used quote, a
59
- * body that no longer matches its quote, an ineligible image). Estimate again
60
- * before retrying; `.message` is the host's or server's text.
68
+ * body that no longer matches its quote). Estimate again before retrying;
69
+ * `.message` is the host's or server's text.
61
70
  */
62
71
  export class RunTrainingError extends Error {
63
72
  /**
@@ -84,6 +93,12 @@ export class RunTrainingError extends Error {
84
93
  timedOut;
85
94
  /** There is no session (`sign in to train`, or a bare `UNAUTHORIZED`). */
86
95
  signInRequired;
96
+ /**
97
+ * The quote names an image this host did not upload for this block
98
+ * (`training data not uploaded here`). No run, nothing charged: upload the
99
+ * images again, estimate again, and run that quote.
100
+ */
101
+ trainingDataNotUploaded;
87
102
  constructor(error, opts) {
88
103
  super(error);
89
104
  this.name = 'RunTrainingError';
@@ -99,6 +114,7 @@ export class RunTrainingError extends Error {
99
114
  this.declined = error === 'declined';
100
115
  this.unconfirmed = this.timedOut || error === 'submission-unconfirmed';
101
116
  this.signInRequired = error === 'sign in to train' || error === 'UNAUTHORIZED';
117
+ this.trainingDataNotUploaded = error === NOT_UPLOADED_HERE;
102
118
  }
103
119
  }
104
120
  /**
@@ -110,15 +126,19 @@ export class RunTrainingError extends Error {
110
126
  * viewer. Refused from `dev:live` and from review sessions — build the flow
111
127
  * against the mock host. See `WorkflowBodyTraining` in `@civitai/app-sdk`.
112
128
  *
113
- * The flow: `usePrepareTrainingDataset()` → `useBuzzWorkflow().estimate(body)`
114
- * (its `trainingQuote.quoteId`) → `runTraining({ ...body, quoteId })` →
115
- * `useBuzzWorkflow().watch(workflowId)`. Change nothing in the body between the
116
- * estimate and the run: the server checks it against the quoted one.
129
+ * The flow: `useImageUpload({ purpose: 'trainingData' }).open()` (the viewer
130
+ * uploads the images; you caption them into `items: [{ air, caption }]`) →
131
+ * `useBuzzWorkflow().estimate(body)` (its `trainingQuote.quoteId`) →
132
+ * `runTraining({ ...body, quoteId })` → `useBuzzWorkflow().watch(workflowId)`.
133
+ * Change nothing in the body between the estimate and the run: the server checks
134
+ * it against the quoted one. Upload and run in the same page session: the host
135
+ * refuses a quote naming an image it did not upload for this block.
117
136
  *
118
137
  * 🔴 THE CONSENT DIALOG IS CIVITAI'S, AND IT IS THE CHARGE. Every number on it is
119
138
  * read back from the server's stored quote — never from your body. A run may cost
120
139
  * more than the token's per-call budget (the viewer confirms the exact price), up
121
- * to `BLOCK_TRAINING_MAX_BUZZ_PER_RUN` (5,000 Buzz). Images are the viewer's own.
140
+ * to `BLOCK_TRAINING_MAX_BUZZ_PER_RUN` (5,000 Buzz). Images are the ones the
141
+ * viewer uploaded through the host.
122
142
  *
123
143
  * 🔴 NO AUTOMATIC CONSENT RETRY, unlike the other consent-gated hooks — on
124
144
  * purpose. `RUN_TRAINING` carries no idempotency key and has an outcome
@@ -130,8 +150,12 @@ export class RunTrainingError extends Error {
130
150
  * the viewer clicks or dismisses its dialog.
131
151
  *
132
152
  * @example
153
+ * const { open } = useImageUpload({ purpose: 'trainingData' });
133
154
  * const { estimate, watch } = useBuzzWorkflow();
134
155
  * const { runTraining } = useRunTraining();
156
+ * const images = await open();
157
+ * if (!images) return; // cancelled
158
+ * const body = { ...settings, items: images.map((i) => ({ air: i.air, caption: captionFor(i) })) };
135
159
  * const quote = (await estimate(body)).trainingQuote!;
136
160
  * try {
137
161
  * const started = await runTraining({ ...body, quoteId: quote.quoteId });
@@ -140,6 +164,7 @@ export class RunTrainingError extends Error {
140
164
  * if (!(e instanceof RunTrainingError)) throw e;
141
165
  * if (e.declined) return; // no run — say nothing
142
166
  * if (e.signInRequired) return requestSignIn();
167
+ * if (e.trainingDataNotUploaded) return askToUploadAgain(); // no run — re-upload, re-estimate
143
168
  * if (e.unconfirmed) return showCheckYourTrainings(); // may be running — never auto-retry
144
169
  * if (e.refused) return showError(e.message); // a cap / availability refusal; no run, nothing charged
145
170
  * showError('Could not start training. Get a new price and try again.');
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';
@@ -73,19 +75,17 @@ export { useReactions, ReactionError, REACTIONS, REACTION_ERROR_CODES, isReactio
73
75
  export type { UseReactions, UseReactionsOptions } from './hooks/useReactions.js';
74
76
  export { useCreatePostFromApp, CreatePostError, CREATE_POST_ERROR_CODES, isCreatePostErrorCode, } from './hooks/useCreatePostFromApp.js';
75
77
  export type { UseCreatePostFromApp, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, } from './hooks/useCreatePostFromApp.js';
76
- export { usePrepareTrainingDataset, PrepareTrainingDatasetError, PREPARE_TRAINING_DATASET_ERROR_CODES, isPrepareTrainingDatasetErrorCode, } from './hooks/usePrepareTrainingDataset.js';
77
- export type { UsePrepareTrainingDataset, BlockPrepareTrainingDatasetHostError, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, } from './hooks/usePrepareTrainingDataset.js';
78
78
  export { useRunTraining, RunTrainingError, RUN_TRAINING_ERROR_CODES, isRunTrainingErrorCode, } from './hooks/useRunTraining.js';
79
- export type { UseRunTraining, QuotedTrainingBody, RunTrainingErrorCode, BlockRunTrainingHostError, BlockTrainingQuote, WorkflowBodyTraining, } from './hooks/useRunTraining.js';
79
+ export type { UseRunTraining, QuotedTrainingBody, RunTrainingErrorCode, BlockRunTrainingHostError, BlockTrainingQuote, BlockTrainingDatasetItem, WorkflowBodyTraining, } from './hooks/useRunTraining.js';
80
80
  export { useRunConfirmedStep, RunConfirmedStepError, RUN_CONFIRMED_STEP_ERROR_CODES, isRunConfirmedStepErrorCode, isStepQuoteId, } from './hooks/useRunConfirmedStep.js';
81
81
  export type { UseRunConfirmedStep, QuotedStepBody, RunConfirmedStepErrorCode, BlockRunConfirmedStepHostError, BlockStepQuote, WorkflowBodyPassThroughStep, } from './hooks/useRunConfirmedStep.js';
82
- 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';
83
83
  export { useCheckpointPicker } from './hooks/useCheckpointPicker.js';
84
84
  export type { UseCheckpointPicker } from './hooks/useCheckpointPicker.js';
85
85
  export { useResourcePicker, RESOURCE_PICKER_MULTIPLE_MAX } from './hooks/useResourcePicker.js';
86
86
  export type { UseResourcePicker, ResourcePickerOpenOptions, ResourcePickerMultiple, } from './hooks/useResourcePicker.js';
87
87
  export { useImageUpload } from './hooks/useImageUpload.js';
88
- export type { UseImageUpload, UseImageUploadAsyncScan, UseImageUploadGenerationSource, UseImageUploadOptions, } from './hooks/useImageUpload.js';
88
+ export type { UseImageUpload, UseImageUploadAsyncScan, UseImageUploadGenerationSource, UseImageUploadTrainingData, UseImageUploadOptions, BlockTrainingDataImage, } from './hooks/useImageUpload.js';
89
89
  export { useUploadImageBytes } from './hooks/useUploadImageBytes.js';
90
90
  export type { UploadImageBytesOptions, UseUploadImageBytes, } from './hooks/useUploadImageBytes.js';
91
91
  export { useMediaUpload } from './hooks/useMediaUpload.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';
@@ -62,7 +63,6 @@ export { InvalidIdempotencyKeyError } from './transport/transport.js';
62
63
  export { useCollectionFollow, CollectionFollowError, COLLECTION_FOLLOW_ERROR_CODES, isCollectionFollowErrorCode, } from './hooks/useCollectionFollow.js';
63
64
  export { useReactions, ReactionError, REACTIONS, REACTION_ERROR_CODES, isReactionErrorCode, } from './hooks/useReactions.js';
64
65
  export { useCreatePostFromApp, CreatePostError, CREATE_POST_ERROR_CODES, isCreatePostErrorCode, } from './hooks/useCreatePostFromApp.js';
65
- export { usePrepareTrainingDataset, PrepareTrainingDatasetError, PREPARE_TRAINING_DATASET_ERROR_CODES, isPrepareTrainingDatasetErrorCode, } from './hooks/usePrepareTrainingDataset.js';
66
66
  export { useRunTraining, RunTrainingError, RUN_TRAINING_ERROR_CODES, isRunTrainingErrorCode, } from './hooks/useRunTraining.js';
67
67
  export { useRunConfirmedStep, RunConfirmedStepError, RUN_CONFIRMED_STEP_ERROR_CODES, isRunConfirmedStepErrorCode, isStepQuoteId, } from './hooks/useRunConfirmedStep.js';
68
68
  export { useCheckpointPicker } from './hooks/useCheckpointPicker.js';
@@ -113,12 +113,9 @@
113
113
  * `entity-unavailable`.
114
114
  * • CREATE_POST_FROM_APP — needs the server-resolved preview plus host-chrome
115
115
  * confirm before a PUBLIC post is written. Replies with a refusal.
116
- * • PREPARE_TRAINING_DATASET — the server refuses every `kind: 'training'`
117
- * request from a dev token, so there is nothing to forward. Replies with a
118
- * refusal.
119
- * • RUN_TRAINING — the same dev-token refusal, plus the host-chrome consent
120
- * dialog a run is confirmed in, which this harness does not have. Replies
121
- * with a refusal.
116
+ * • RUN_TRAINING — the server refuses every `kind: 'training'` request from a
117
+ * dev token, and a run is confirmed in a host-chrome consent dialog this
118
+ * harness does not have. Replies with a refusal.
122
119
  * • RUN_CONFIRMED_STEP — the server refuses a confirmed step from a dev token
123
120
  * and the estimate never returns a `stepQuote` for one, and the price is
124
121
  * confirmed in host chrome this harness does not have. Replies with a
@@ -130,6 +127,9 @@
130
127
  * `publishError` options. Replies with a refusal.
131
128
  * • GET_WILDCARD_PACK — needs the session-authed resolve plus the in-tab
132
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`.
133
133
  * • OPEN_IMAGE_UPLOAD — needs the host's native modal + session-authed byte
134
134
  * pipeline. Replies DISMISSED (the hook resolves `null`).
135
135
  * • OPEN_IMAGE_PICKER — needs the host's picker over the viewer's own posted
@@ -239,8 +239,7 @@ export interface LiveHostOptions {
239
239
  manifest?: unknown;
240
240
  }
241
241
  /**
242
- * The reply both training bridges get in dev:live — see their handlers. One
243
- * string so the two cannot drift.
242
+ * The reply `RUN_TRAINING` gets in dev:live — see its handler.
244
243
  */
245
244
  export declare const TRAINING_UNSUPPORTED_ERROR = "training is not supported in dev:live \u2014 use dev:mock";
246
245
  /**
@@ -113,12 +113,9 @@
113
113
  * `entity-unavailable`.
114
114
  * • CREATE_POST_FROM_APP — needs the server-resolved preview plus host-chrome
115
115
  * confirm before a PUBLIC post is written. Replies with a refusal.
116
- * • PREPARE_TRAINING_DATASET — the server refuses every `kind: 'training'`
117
- * request from a dev token, so there is nothing to forward. Replies with a
118
- * refusal.
119
- * • RUN_TRAINING — the same dev-token refusal, plus the host-chrome consent
120
- * dialog a run is confirmed in, which this harness does not have. Replies
121
- * with a refusal.
116
+ * • RUN_TRAINING — the server refuses every `kind: 'training'` request from a
117
+ * dev token, and a run is confirmed in a host-chrome consent dialog this
118
+ * harness does not have. Replies with a refusal.
122
119
  * • RUN_CONFIRMED_STEP — the server refuses a confirmed step from a dev token
123
120
  * and the estimate never returns a `stepQuote` for one, and the price is
124
121
  * confirmed in host chrome this harness does not have. Replies with a
@@ -130,6 +127,9 @@
130
127
  * `publishError` options. Replies with a refusal.
131
128
  * • GET_WILDCARD_PACK — needs the session-authed resolve plus the in-tab
132
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`.
133
133
  * • OPEN_IMAGE_UPLOAD — needs the host's native modal + session-authed byte
134
134
  * pipeline. Replies DISMISSED (the hook resolves `null`).
135
135
  * • OPEN_IMAGE_PICKER — needs the host's picker over the viewer's own posted
@@ -174,8 +174,7 @@ const DEFAULT_BACKEND_BASE_URL = 'https://civitai.com';
174
174
  /** The budgeted-spend scope the workflow procedures require. */
175
175
  const BUDGETED_SCOPE = 'ai:write:budgeted';
176
176
  /**
177
- * The reply both training bridges get in dev:live — see their handlers. One
178
- * string so the two cannot drift.
177
+ * The reply `RUN_TRAINING` gets in dev:live — see its handler.
179
178
  */
180
179
  export const TRAINING_UNSUPPORTED_ERROR = 'training is not supported in dev:live — use dev:mock';
181
180
  /** The once-per-session console line for a refused training bridge. */
@@ -1139,35 +1138,25 @@ export function createLiveHost(options) {
1139
1138
  });
1140
1139
  return;
1141
1140
  }
1142
- case 'PREPARE_TRAINING_DATASET': {
1141
+ case 'RUN_TRAINING': {
1143
1142
  // UNSUPPORTED in live, and here the SERVER decides it, not this
1144
1143
  // harness: every `kind: 'training'` procedure refuses a dev token
1145
1144
  // ("training is not available from a development or review session"
1146
1145
  // — `assertTrainingRequestAllowed` in civitai/civitai), because there
1147
1146
  // is no host page whose chrome can show the viewer a price. A
1148
1147
  // training ESTIMATE is still forwarded (ESTIMATE_WORKFLOW above) and
1149
- // comes back carrying that same server refusal.
1148
+ // comes back carrying that same server refusal. A run is also
1149
+ // CHARGED and confirmed in host chrome this harness does not have, so
1150
+ // faking either half would let dev prove out a flow production does
1151
+ // not have. The upload half (`OPEN_IMAGE_UPLOAD { purpose:
1152
+ // 'trainingData' }`) is answered as a dismissal (`null`), like every
1153
+ // other upload mode — see OPEN_IMAGE_UPLOAD below.
1150
1154
  //
1151
1155
  // FREE TEXT on the message's own reply channel; no host code is
1152
1156
  // honest about "this harness cannot do it". Use dev:mock — its
1153
- // `trainingDatasetError` / `trainingDatasetRejected` knobs cover the
1154
- // refusal set.
1155
- if (!isRoutableRequestId(requestId))
1156
- return;
1157
- logOnce('training-dataset', trainingUnsupportedLog('PREPARE_TRAINING_DATASET'));
1158
- dispatchToBlock({
1159
- type: 'TRAINING_DATASET_RESULT',
1160
- payload: { requestId, error: TRAINING_UNSUPPORTED_ERROR },
1161
- });
1162
- return;
1163
- }
1164
- case 'RUN_TRAINING': {
1165
- // UNSUPPORTED in live for the same server-side reason as
1166
- // PREPARE_TRAINING_DATASET directly above — and doubly so: a run is
1167
- // CHARGED and confirmed in host chrome this harness does not have, so
1168
- // faking either half would let dev prove out a flow production does
1169
- // not have. Use dev:mock — its `runTrainingError` knob covers
1170
- // `declined`, `submission-unconfirmed` and the rest.
1157
+ // `cannedTrainingDataUpload`, `trainingDataNotUploadedHere` and
1158
+ // `runTrainingError` knobs cover the upload, the not-uploaded-here
1159
+ // refusal, `declined`, `submission-unconfirmed` and the rest.
1171
1160
  if (!isRoutableRequestId(requestId))
1172
1161
  return;
1173
1162
  logOnce('training-run', trainingUnsupportedLog('RUN_TRAINING'));
@@ -1221,6 +1210,26 @@ export function createLiveHost(options) {
1221
1210
  });
1222
1211
  return;
1223
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
+ }
1224
1233
  case 'QUERY_APP_WORKFLOWS': {
1225
1234
  // App generator SUBQUEUE read via the token-bound
1226
1235
  // `blocks.queryAppWorkflows` MUTATION (POST). params (cursor/limit) are
@@ -1391,6 +1400,12 @@ export function createLiveHost(options) {
1391
1400
  'Test the bytes upload against the real site, or use ' +
1392
1401
  'createMockHost({ uploadImageBytesResult }) in dev:mock.');
1393
1402
  }
1403
+ else if (typed.payload?.purpose === 'trainingData') {
1404
+ logOnce('open-image-upload-training-data', "OPEN_IMAGE_UPLOAD { purpose: 'trainingData' } received but live dev mode " +
1405
+ 'cannot serve the training-data upload modal — resolving the upload as cancelled (null). ' +
1406
+ 'Test the training flow against the real site, or use ' +
1407
+ 'createMockHost({ cannedTrainingDataUpload }) in dev:mock.');
1408
+ }
1394
1409
  else {
1395
1410
  logOnce('open-image-upload', 'OPEN_IMAGE_UPLOAD received but live dev mode cannot serve the native upload ' +
1396
1411
  'modal — resolving the upload as dismissed (null). Test the image-upload flow ' +
@@ -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 BlockPrepareTrainingDatasetHostError, type BlockRunTrainingHostError, type BlockTrainingRejectionReason, 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';
@@ -412,7 +412,7 @@ export interface MockGenerationScenario {
412
412
  */
413
413
  export declare const MOCK_TRAINING_BOUNDS: {
414
414
  readonly datasetMaxItems: 50;
415
- readonly captionMaxChars: 1000;
415
+ readonly captionMaxChars: 8192;
416
416
  readonly maxBuzzPerRun: 5000;
417
417
  };
418
418
  /**
@@ -425,16 +425,15 @@ export declare const MOCK_TRAINING_BOUNDS: {
425
425
  export declare const MOCK_SPEND_SCOPE_ERROR = "block lacks ai:write:budgeted scope";
426
426
  /** The real server's refusal for an unknown, expired or spent quote on `RUN_TRAINING`. */
427
427
  export declare const MOCK_TRAINING_QUOTE_GONE_ERROR = "training quote not found, expired or already used \u2014 estimate again";
428
- /** The real server's refusal for a training estimate naming a dataset it does not hold. */
429
- export declare const MOCK_TRAINING_DATASET_GONE_ERROR = "training dataset not found or expired";
430
428
  /**
431
- * The real server's two refusals for a dataset with NOTHING admitted
432
- * (`prepareBlockTrainingDataset`): every image failed eligibility, or every
433
- * eligible image failed its import. The server THROWS rather than returning
434
- * `count: 0`, so a successful reply always admits at least one image.
429
+ * The estimate's refusals for `items` outside the server's schema. The first two
430
+ * are the server's own custom messages (`blockTrainingDatasetItemsSchema`); the
431
+ * third stands for every other zod issue (count, caption length, missing or extra
432
+ * keys), whose real text is zod's formatting and not a contract.
435
433
  */
436
- export declare const MOCK_TRAINING_NONE_ELIGIBLE_ERROR = "none of the requested images can be used for training";
437
- export declare const MOCK_TRAINING_NONE_IMPORTED_ERROR = "none of the requested images could be prepared for training";
434
+ export declare const MOCK_TRAINING_AIR_ERROR = "air must be a consumer blob key or its Civitai-hosted blob url";
435
+ export declare const MOCK_TRAINING_DUPLICATE_ERROR = "a training dataset names each image once";
436
+ export declare const MOCK_TRAINING_ITEMS_ERROR = "training items must be 1 to 50 { air, caption } entries with captions of at most 8192 characters";
438
437
  /**
439
438
  * The per-job ceiling of a confirmed step — a COPY of `@civitai/app-sdk/blocks`'s
440
439
  * `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB`, not value-imported for the reason
@@ -932,6 +931,21 @@ export interface MockHostOptions {
932
931
  * returned. Live-tunable via {@link MockHost.setScenario}.
933
932
  */
934
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;
935
949
  /**
936
950
  * Force `SET_COLLECTION_FOLLOW` to reply with an `error` instead of writing —
937
951
  * exercises `useCollectionFollow`'s refusal handling. Pass a
@@ -1014,41 +1028,34 @@ export interface MockHostOptions {
1014
1028
  */
1015
1029
  createPostError?: BlockCreatePostHostError | string;
1016
1030
  /**
1017
- * Images `PREPARE_TRAINING_DATASET` leaves out, with their reasons — what
1018
- * `usePrepareTrainingDataset()` reports on `rejected`. Only entries whose
1019
- * `imageId` the request actually named are reported, and `count` is the rest,
1020
- * exactly as the server derives it. Absent → every image is admitted.
1031
+ * The images an `OPEN_IMAGE_UPLOAD { purpose: 'trainingData' }` returns — what
1032
+ * `useImageUpload({ purpose: 'trainingData' }).open()` resolves with. Absent →
1033
+ * {@link DEFAULT_TRAINING_DATA_UPLOAD_COUNT} images with FRESH keys on every
1034
+ * upload, so two uploads never collide. `null` (or `[]`) → the viewer cancelled.
1021
1035
  * Live-tunable via {@link MockHost.setScenario}.
1022
1036
  *
1023
- * When this rejects EVERY named image the reply is an `error`, as on the real
1024
- * server — {@link MOCK_TRAINING_NONE_ELIGIBLE_ERROR}, or
1025
- * {@link MOCK_TRAINING_NONE_IMPORTED_ERROR} when every image that passed
1026
- * eligibility was rejected with an `import-*` reason. A success never has
1027
- * `count: 0`.
1028
- *
1029
- * KIND-FAITHFUL, like the rest of the training path: the mock holds the
1030
- * datasets it prepared, an estimate naming any other `datasetId` fails as the
1031
- * server's does, a quote is run at most once, and every training call needs
1032
- * `ai:write:budgeted` on the token ({@link MockHostOptions.consentGranted}) and
1033
- * a signed-in viewer. What it cannot model: image ownership and moderation (it
1034
- * has no images), the flag, and the page-only / dev-token refusals.
1035
- *
1036
- * ⚠️ The quote↔body check is NARROWER than the server's: the mock compares only
1037
- * `datasetId`, while the server hashes the whole body (minus `quoteId`) and
1038
- * refuses any change. A body edited between estimate and run passes here and
1039
- * fails in production.
1040
- */
1041
- trainingDatasetRejected?: Array<{
1042
- imageId: number;
1043
- reason: BlockTrainingRejectionReason;
1044
- }>;
1045
- /**
1046
- * Force `PREPARE_TRAINING_DATASET` to reply with an `error` — a
1047
- * {@link BlockPrepareTrainingDatasetHostError} for a host refusal, or any other
1048
- * string for the free-text server-message variant. Absent → it prepares.
1049
- * Live-tunable via {@link MockHost.setScenario}.
1037
+ * The mock REMEMBERS every key it hands out, per install, as the real host
1038
+ * does per block instance: `RUN_TRAINING` refuses a quote naming any other key
1039
+ * with `training data not uploaded here`, before any other check, and keeps the
1040
+ * quote. Keys compare case-insensitively; an item may name its blob by the
1041
+ * `url` too.
1042
+ *
1043
+ * KIND-FAITHFUL, like the rest of the training path: the estimate refuses items
1044
+ * outside the server's schema, a quote is run at most once and is checked
1045
+ * against the whole body it was quoted for (minus `quoteId`), and every
1046
+ * training call needs `ai:write:budgeted` on the token
1047
+ * ({@link MockHostOptions.consentGranted}) and a signed-in viewer. What it
1048
+ * cannot model: blob storage, the Civitai-hosted-url rule on `air`, the flag,
1049
+ * and the page-only / dev-token refusals.
1050
+ */
1051
+ cannedTrainingDataUpload?: BlockTrainingDataImage[] | null;
1052
+ /**
1053
+ * Make `RUN_TRAINING` refuse EVERY quote with `training data not uploaded
1054
+ * here`, as the real host does after a page reload forgot the uploads. Settles
1055
+ * before the server claims the quote, so the quote survives. Live-tunable via
1056
+ * {@link MockHost.setScenario}; `undefined` clears it.
1050
1057
  */
1051
- trainingDatasetError?: BlockPrepareTrainingDatasetHostError | string;
1058
+ trainingDataNotUploadedHere?: boolean;
1052
1059
  /**
1053
1060
  * The Buzz total a `kind: 'training'` estimate quotes. Default `500`. Above
1054
1061
  * `BLOCK_TRAINING_MAX_BUZZ_PER_RUN` (5,000) the estimate fails exactly as the
@@ -1064,7 +1071,8 @@ export interface MockHostOptions {
1064
1071
  *
1065
1072
  * Whether the QUOTE survives follows the real flow: the host's own gate and
1066
1073
  * dialog codes (`declined`, `review-mode`, `block is not ready`, `sign in to
1067
- * train`, `invalid training request`, `no block token`) settle before the
1074
+ * train`, `invalid training request`, `no block token`, `training data not
1075
+ * uploaded here`) settle before the
1068
1076
  * server claims the quote, so it stays runnable; `submission-unconfirmed` and
1069
1077
  * any server message settle after the claim, so it is used up — re-running it
1070
1078
  * gets the quote-gone error. Live-tunable via {@link MockHost.setScenario};
@@ -1431,7 +1439,7 @@ export interface MockHostOptions {
1431
1439
  * {@link MockHost.setTheme}, which pushes a real `THEME_CHANGE` — the mock of
1432
1440
  * the viewer toggling dark mode with the block already mounted.)
1433
1441
  */
1434
- 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' | 'trainingDatasetRejected' | 'trainingDatasetError' | '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'>;
1435
1443
  /** Runtime Buzz-balance handle exposed on {@link MockHost.buzz}. */
1436
1444
  export interface MockBuzzHandle {
1437
1445
  /** Current simulated balance, or `undefined` when balance isn't simulated. */
@@ -1447,6 +1455,7 @@ export interface MockBuzzHandle {
1447
1455
  *
1448
1456
  * After install, a harness UI can drive scenarios live:
1449
1457
  * - `setScenario(patch)` — merge new generation/buzz/storage/failMode controls.
1458
+ * `setScenario({ resourceFileError: undefined })` clears a forced resource-file error.
1450
1459
  * - `setTheme('light' | 'dark')` — flip the site theme and push `THEME_CHANGE`.
1451
1460
  * - `buzz.setBalance(n)` / `buzz.getBalance()` — flip the simulated wallet.
1452
1461
  *