@civitai/blocks-react 0.70.0 → 0.72.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.
@@ -37,7 +37,8 @@
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 BlockPublishedModel, type BlockGatedImage, type BlockCollectionFollowErrorCode, 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 AppWorkflow, type BlockModel3dOutput, type BlockPublishedModel, type BlockRunConfirmedStepHostError, type BlockGatedImage, type BlockCollectionFollowErrorCode, 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';
41
+ import { type MockWorkflowQuoteFailure } from './mockWorkflowGraph.js';
41
42
  /**
42
43
  * How submits behave. `'none'` = everything succeeds; `'all'` / `'insufficient'`
43
44
  * = every submit fails as if the viewer were OUT OF BUZZ, until an
@@ -77,7 +78,22 @@ export type MockCannedImageScan = 'scanned' | {
77
78
  * A per-generation cost: a fixed number, or a function of the submitted
78
79
  * {@link WorkflowBody} (so a dev can vary cost by model / step count).
79
80
  */
80
- export type CostSpec = number | ((req: WorkflowBody) => number);
81
+ export type CostSpec = number | ((req: WorkflowBody, step?: MockWorkflowStepInfo) => number);
82
+ /**
83
+ * The step a {@link CostSpec} function is pricing, passed as its SECOND
84
+ * argument when the body is `kind: 'workflow'`: the mock calls
85
+ * {@link MockGenerationScenario.costPerGen} once per step and sums the results.
86
+ * `undefined` for every other kind, which is priced with one call.
87
+ */
88
+ export type { MockWorkflowQuoteFailure };
89
+ export interface MockWorkflowStepInfo {
90
+ /** Zero-based position in the body's `steps`. */
91
+ index: number;
92
+ /** The step's `name`, or `'$<index>'` for an unnamed step. */
93
+ name: string;
94
+ $type: string;
95
+ input: Record<string, unknown>;
96
+ }
81
97
  /**
82
98
  * A result image url: a fixed string, or a function of the submitted body
83
99
  * (so a dev can echo the prompt into a placeholder).
@@ -91,6 +107,12 @@ export interface MockGenerationScenario {
91
107
  /**
92
108
  * Cost reported on `ESTIMATE_RESULT` + the succeeded snapshot. A number, or
93
109
  * a `(body) => number`. Overrides the legacy top-level `cost`. Default `8`.
110
+ *
111
+ * A `kind: 'workflow'` body is priced PER STEP: this is called once for each
112
+ * step (a function receives the step as its second argument, see
113
+ * {@link MockWorkflowStepInfo}), multiplied by that step's generations (a
114
+ * `repeat` counts its iterations, nested repeats multiplied), and summed, so a
115
+ * plain number prices an N-step workflow with no `repeat` at N times it.
94
116
  */
95
117
  costPerGen?: CostSpec;
96
118
  /**
@@ -100,9 +122,10 @@ export interface MockGenerationScenario {
100
122
  * real host does. A number, or a `(body) => number`. Default `0`, which is
101
123
  * reported as `authorFee: 0` ("no fee on this request").
102
124
  *
103
- * Like the real host, only a `textToImage` or registered-`step` body is
104
- * itemised; `customComfy`, pass-through `step` and `training` quotes carry no
105
- * `authorFee`. The succeeded snapshot's `cost.total` stays {@link costPerGen}
125
+ * Like the real host, only a `textToImage`, registered-`step` or `workflow`
126
+ * body is itemised; `customComfy`, pass-through `step` and `training` quotes
127
+ * carry no `authorFee`. A `workflow` body is charged this ONCE, however many
128
+ * steps it has. The succeeded snapshot's `cost.total` stays {@link costPerGen}
106
129
  * (a submitted workflow reports the generation's own cost), while a simulated
107
130
  * {@link MockBuzzScenario.balance} is debited `costPerGen + authorFee`.
108
131
  *
@@ -265,7 +288,54 @@ export interface MockGenerationScenario {
265
288
  * Default: unset.
266
289
  */
267
290
  submitCapRefusal?: string | true;
268
- /** A single result image url (or `(body) => url`). */
291
+ /**
292
+ * Make ONE step of a `kind: 'workflow'` run fail, so a block can exercise a
293
+ * PARTIAL failure. Name the step as its snapshot entry names it: its `name`,
294
+ * or `'$<index>'` for a step the body left unnamed. The poll that finishes
295
+ * the workflow then answers `status: 'failed'` with:
296
+ * - `steps`: every step before it `'succeeded'`, the named step `'failed'`
297
+ * with an `error`, every step after it `'canceled'`;
298
+ * - `cost.total`: the sum of the steps that succeeded (they stay charged), so
299
+ * it is `> 0` unless the FIRST step is the one that fails;
300
+ * - `imageUrls`: the images of the succeeded steps that did not set
301
+ * `suppressOutput`.
302
+ *
303
+ * What the real orchestrator reports for the steps AFTER a failed one is its
304
+ * own business; `'canceled'` here is the mock's stand-in for "did not run".
305
+ *
306
+ * Ignored by every other kind, and when no step matches. Takes precedence
307
+ * over {@link workflowOutcome} for the workflow it applies to. Live-tunable
308
+ * via `setScenario({ generation: { failWorkflowStep } })`. Default: unset.
309
+ */
310
+ failWorkflowStep?: string;
311
+ /**
312
+ * Simulate the host getting NO price quote for a `kind: 'workflow'` body, and
313
+ * say why: `'rejected'` (the orchestrator refused the body), `'unavailable'`
314
+ * (no usable answer — the one cause a retry can clear) or `'unpriced'` (an
315
+ * answer without a price). `true` means `'unavailable'`.
316
+ *
317
+ * - A workflow with a training step (`errorCode: 'training-quote-<cause>'`)
318
+ * or a `repeat` step at any depth (`'workflow_repeat_unbounded'`) is
319
+ * refused on BOTH `estimate()` and `submit()`, before any reservation.
320
+ * - Any other workflow: `estimate()` FAILS with `'workflow_quote_<cause>'`
321
+ * and no `cost`, while `submit()` still PROCEEDS, reserved at the CEILING —
322
+ * {@link MockHostOptions.buzzBudget} × the number of steps — with no author
323
+ * fee (no quote, no fee base). A failed estimate does not predict a refused
324
+ * submit.
325
+ *
326
+ * `error` is the host's sentence for the cause. The mock has no orchestrator
327
+ * reason to pass on, so `'rejected'` always reads the host's reason-less form.
328
+ * Ignored by every other kind. Live-tunable via
329
+ * `setScenario({ generation: { workflowUnpriced } })`. Default: unset (every
330
+ * workflow is quoted at its per-step `costPerGen`).
331
+ */
332
+ workflowUnpriced?: boolean | MockWorkflowQuoteFailure;
333
+ /**
334
+ * A single result image url (or `(body) => url`). For a `kind: 'workflow'`
335
+ * run this (and {@link images}) is the LAST step that did not set
336
+ * `suppressOutput`; each earlier visible step gets its own labelled
337
+ * placeholder.
338
+ */
269
339
  image?: ImageSpec;
270
340
  /**
271
341
  * Multiple result image urls (or `(body) => url[]`). Takes precedence over
@@ -313,6 +383,22 @@ export interface MockGenerationScenario {
313
383
  * `appWorkflows`.
314
384
  */
315
385
  trainingPublishedModel?: BlockPublishedModel;
386
+ /**
387
+ * The 3D models a succeeded PASS-THROUGH step reports on
388
+ * {@link BlockWorkflowSnapshot.model3dOutputs} — what a `polyGen` job returns.
389
+ * As on the real host, every labelled `url` is ALSO in the snapshot's
390
+ * `imageUrls`: when set, the mock answers with exactly these urls there, in
391
+ * this order, unless {@link images} / {@link image} say otherwise (then the
392
+ * labels are sent as given and it is on you to keep the two consistent).
393
+ *
394
+ * Applies only to a pass-through `{ kind: 'step', $type }` body — the only arm
395
+ * the host labels. Default: unset, and the field is OMITTED, as the host omits
396
+ * it for a workflow that produced no 3D model.
397
+ *
398
+ * Snapshot-only, like {@link trainedEpochs}: for the app-queue read put
399
+ * `model3dOutputs` on the rows you pass as {@link MockHostOptions.appWorkflows}.
400
+ */
401
+ model3dOutputs?: BlockModel3dOutput[];
316
402
  }
317
403
  /**
318
404
  * The training bounds the mock enforces — COPIES of `@civitai/app-sdk/blocks`'s
@@ -347,6 +433,41 @@ export declare const MOCK_TRAINING_DATASET_GONE_ERROR = "training dataset not fo
347
433
  */
348
434
  export declare const MOCK_TRAINING_NONE_ELIGIBLE_ERROR = "none of the requested images can be used for training";
349
435
  export declare const MOCK_TRAINING_NONE_IMPORTED_ERROR = "none of the requested images could be prepared for training";
436
+ /**
437
+ * The per-job ceiling of a confirmed step — a COPY of `@civitai/app-sdk/blocks`'s
438
+ * `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB`, not value-imported for the reason
439
+ * given on {@link MOCK_TRAINING_BOUNDS}. Pinned to the SDK's value by
440
+ * `test/confirmedStepFlow.test.tsx`, which imports both.
441
+ */
442
+ export declare const MOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB = 5000;
443
+ /**
444
+ * The real server's refusal when the host reads back a step quote it does not
445
+ * hold — unknown, expired, or already used by an earlier run. This is the
446
+ * PREVIEW's message, which is the one `RUN_CONFIRMED_STEP` reaches first.
447
+ */
448
+ export declare const MOCK_STEP_QUOTE_GONE_ERROR = "step quote not found or expired";
449
+ /** The real server's refusal for a confirmed run whose body is not the quoted one. */
450
+ export declare const MOCK_STEP_BODY_MISMATCH_ERROR = "the step body differs from the one that was quoted \u2014 estimate again";
451
+ /**
452
+ * The real server's refusal for a `quoteId` body sent as a plain
453
+ * `SUBMIT_WORKFLOW`: nothing but `RUN_CONFIRMED_STEP`'s dialog records the
454
+ * viewer's confirmation.
455
+ */
456
+ export declare const MOCK_STEP_NOT_CONFIRMED_ERROR = "this step has not been confirmed by the viewer in this session";
457
+ /**
458
+ * The real server's refusal for a TRAINING `$type` (`training`,
459
+ * `imageResourceTraining`) on the confirmed-step path — at the estimate, where a
460
+ * quote would otherwise be granted, and at the confirmed submit. Training runs
461
+ * have their own kind.
462
+ */
463
+ export declare const MOCK_STEP_TRAINING_TYPE_ERROR = "a training step type cannot run as a confirmed step \u2014 use kind:'training'";
464
+ /**
465
+ * The fixed PREFIX of the real server's refusal for a confirmed-step body whose
466
+ * `input` is not plain JSON. The full message continues with the offending path
467
+ * and what was found there (`body.input.when is a Date`), which varies — match on
468
+ * this prefix.
469
+ */
470
+ export declare const MOCK_STEP_NON_JSON_INPUT_ERROR_PREFIX = "a confirmed step needs plain JSON input (objects, arrays, strings, finite numbers, booleans, null) \u2014 ";
350
471
  /**
351
472
  * BUZZ scenario controls — simulate a viewer who runs out of Buzz. The mock host
352
473
  * treats `balance` as a spendable wallet: each succeeding generation DEBITS its
@@ -910,6 +1031,51 @@ export interface MockHostOptions {
910
1031
  * `setScenario({ runTrainingCapRefusal: undefined })` clears it.
911
1032
  */
912
1033
  runTrainingCapRefusal?: string;
1034
+ /**
1035
+ * Force `RUN_CONFIRMED_STEP` to reply with an `error` instead of starting the
1036
+ * job — a {@link BlockRunConfirmedStepHostError} or any other string for a
1037
+ * server message. Absent → the viewer "confirms" and the job starts.
1038
+ *
1039
+ * - `'declined'` — the viewer dismissed the confirm. No job.
1040
+ * - `'daily-limit-exceeded'` — the job would exceed the viewer's own daily
1041
+ * Buzz limit for the app. No job; the real host explains it in its chrome.
1042
+ * - `'submission-unconfirmed'` — the job MAY exist.
1043
+ *
1044
+ * A CONFIRMED STEP IS QUOTED BY THE ESTIMATE, with no knob of its own: a
1045
+ * pass-through `{ kind: 'step', $type }` estimate carries
1046
+ * {@link BlockWorkflowSnapshot.stepQuote} whenever the mock's price for it
1047
+ * ({@link MockGenerationScenario.costPerGen}, or `cost`) is above the token's
1048
+ * per-call budget ({@link MockHostOptions.buzzBudget}, default 200) and at most
1049
+ * 5,000, for a signed-in viewer. So `generation: { costPerGen: 800 }` is all it
1050
+ * takes to reach this path. ONLY a single `ESTIMATE_WORKFLOW` quotes one: a
1051
+ * batch-estimate cell never carries `stepQuote`, as on the server. And where a
1052
+ * quote would be granted, two bodies make the estimate FAIL instead, with the
1053
+ * server's message: a training `$type` (`training`, `imageResourceTraining`,
1054
+ * in any casing), and an `input` that is not plain JSON (a `Date`, `Map`,
1055
+ * `Set`, `BigInt`, `NaN`, an empty slot in a sparse array, …).
1056
+ *
1057
+ * Whether the QUOTE survives follows the real flow: the host's own gate,
1058
+ * dialog and daily-limit codes settle before the server claims the quote, so
1059
+ * it stays runnable; `submission-unconfirmed` and any server message settle
1060
+ * after the claim, so it is used up — running it again gets the quote-gone
1061
+ * error. Live-tunable via {@link MockHost.setScenario}; `undefined` clears it.
1062
+ * URL: `?confirmedStep=declined|daily-limit|unconfirmed`.
1063
+ *
1064
+ * 🔴 Same real gap as {@link runTrainingError}: the confirm is HOST chrome, so
1065
+ * the mock settles immediately where the real host waits on a click. The mock
1066
+ * also prices every estimate as FIXED — the real host gives a variable or
1067
+ * metered price no `stepQuote` at all.
1068
+ */
1069
+ runConfirmedStepError?: BlockRunConfirmedStepHostError | string;
1070
+ /**
1071
+ * Make `RUN_CONFIRMED_STEP` resolve the server's spend-cap refusal instead of
1072
+ * a job: a snapshot `{ workflowId: 'failed', status: 'failed', cost: { total:
1073
+ * <quote> }, error: <this string> }` (refunded, no job).
1074
+ * `useRunConfirmedStep()` rejects it with `.refused`. 🔴 The quote is CONSUMED,
1075
+ * as on the server. Absent → the job starts. Live-tunable via
1076
+ * {@link MockHost.setScenario}; `undefined` clears it.
1077
+ */
1078
+ runConfirmedStepCapRefusal?: string;
913
1079
  /**
914
1080
  * Force `SAVE_IMAGE` to reply with this `error` instead of saving — any of
915
1081
  * the host's free-text refusals (`'image url is not allowed'`,
@@ -1098,6 +1264,9 @@ export interface MockHostOptions {
1098
1264
  * shape carries the field, so a context you pass WITHOUT a `theme` key still
1099
1265
  * gets the harness theme (and still follows a later `setTheme`). Only a slot
1100
1266
  * this SDK has no shape for is left alone — there is no `theme` field to set.
1267
+ *
1268
+ * The slot also decides `SET_USER_CHECKPOINT`, as in production: refused on
1269
+ * `app.page` (the default), persisted on a model slot.
1101
1270
  */
1102
1271
  context?: BlockContext;
1103
1272
  /**
@@ -1172,7 +1341,7 @@ export interface MockHostOptions {
1172
1341
  * {@link MockHost.setTheme}, which pushes a real `THEME_CHANGE` — the mock of
1173
1342
  * the viewer toggling dark mode with the block already mounted.)
1174
1343
  */
1175
- export type MockHostScenarioPatch = Pick<MockHostOptions, 'consentGrantable' | 'failMode' | 'cost' | 'pollsUntilDone' | 'cannedPicks' | 'cannedMultiPicks' | 'cannedImageUpload' | 'cannedGenerationSourceUpload' | 'cannedImageScan' | 'generation' | 'buzz' | 'storage' | 'shared' | 'buzzBalanceError' | 'viewerResult' | 'viewerError' | 'buzzTransactions' | 'buzzAccounts' | 'dailyCompensation' | 'buzzReadError' | 'wildcardPack' | 'wildcardPackError' | 'collectionFollowError' | 'createPostResult' | 'createPostError' | 'trainingDatasetRejected' | 'trainingDatasetError' | 'trainingQuoteTotal' | 'runTrainingError' | 'runTrainingCapRefusal' | 'appWorkflows' | 'appWorkflowsError' | 'publishImageIds' | 'publishError' | 'gatedImages' | 'gatedImagesError' | 'disallowedAccountTypes' | 'saveImageError' | 'uploadImageBytesResult' | 'uploadImageBytesError'>;
1344
+ export type MockHostScenarioPatch = Pick<MockHostOptions, 'consentGrantable' | 'failMode' | 'cost' | 'pollsUntilDone' | 'cannedPicks' | 'cannedMultiPicks' | 'cannedImageUpload' | 'cannedGenerationSourceUpload' | 'cannedImageScan' | 'generation' | 'buzz' | 'storage' | 'shared' | 'buzzBalanceError' | 'viewerResult' | 'viewerError' | 'buzzTransactions' | 'buzzAccounts' | 'dailyCompensation' | 'buzzReadError' | 'wildcardPack' | 'wildcardPackError' | 'collectionFollowError' | 'createPostResult' | 'createPostError' | 'trainingDatasetRejected' | 'trainingDatasetError' | 'trainingQuoteTotal' | 'runTrainingError' | 'runTrainingCapRefusal' | 'runConfirmedStepError' | 'runConfirmedStepCapRefusal' | 'appWorkflows' | 'appWorkflowsError' | 'publishImageIds' | 'publishError' | 'gatedImages' | 'gatedImagesError' | 'disallowedAccountTypes' | 'saveImageError' | 'uploadImageBytesResult' | 'uploadImageBytesError'>;
1176
1345
  /** Runtime Buzz-balance handle exposed on {@link MockHost.buzz}. */
1177
1346
  export interface MockBuzzHandle {
1178
1347
  /** Current simulated balance, or `undefined` when balance isn't simulated. */
@@ -1221,6 +1390,8 @@ export declare function disallowedAccountError(accountType: BuzzAccountType): st
1221
1390
  * map onto the new scenario groups so a dev can flip out-of-Buzz /
1222
1391
  * failures / latency without editing code. `?capRefusal=1` (or `=<text>`) sets
1223
1392
  * {@link MockGenerationScenario.submitCapRefusal}.
1393
+ * `?confirmedStep=declined|daily-limit|unconfirmed` sets
1394
+ * {@link MockHostOptions.runConfirmedStepError}.
1224
1395
  *
1225
1396
  * Returns a partial overlay applied ON TOP of explicit {@link MockHostOptions}
1226
1397
  * (URL wins — it's the interactive dev knob). No-op outside a browser.