@civitai/blocks-react 0.43.0 → 0.44.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 +116 -22
- package/dist/hooks/useAppStorage.d.ts.map +1 -1
- package/dist/hooks/useAppStorage.js +17 -4
- package/dist/hooks/useAppStorage.js.map +1 -1
- package/dist/hooks/useBuzzWorkflow.d.ts +303 -39
- package/dist/hooks/useBuzzWorkflow.d.ts.map +1 -1
- package/dist/hooks/useBuzzWorkflow.js +350 -39
- package/dist/hooks/useBuzzWorkflow.js.map +1 -1
- package/dist/hooks/useCheckpointPicker.d.ts.map +1 -1
- package/dist/hooks/useCheckpointPicker.js +7 -2
- package/dist/hooks/useCheckpointPicker.js.map +1 -1
- package/dist/hooks/useSaveImage.d.ts.map +1 -1
- package/dist/hooks/useSaveImage.js +6 -2
- package/dist/hooks/useSaveImage.js.map +1 -1
- package/dist/hooks/useSharedStorage.d.ts.map +1 -1
- package/dist/hooks/useSharedStorage.js +20 -4
- package/dist/hooks/useSharedStorage.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/liveHost.d.ts.map +1 -1
- package/dist/internal/liveHost.js +17 -0
- package/dist/internal/liveHost.js.map +1 -1
- package/dist/internal/mockHost.d.ts +48 -5
- package/dist/internal/mockHost.d.ts.map +1 -1
- package/dist/internal/mockHost.js +65 -3
- package/dist/internal/mockHost.js.map +1 -1
- package/dist/internal/validate.d.ts +66 -16
- package/dist/internal/validate.d.ts.map +1 -1
- package/dist/internal/validate.js +89 -27
- package/dist/internal/validate.js.map +1 -1
- package/package.json +2 -2
|
@@ -111,46 +111,57 @@ export interface WatchWorkflowOptions {
|
|
|
111
111
|
* reproduces the bug one layer down: a caller who logs `message` expecting the
|
|
112
112
|
* server's words gets a constant and is exactly as stuck as before.
|
|
113
113
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
114
|
+
* 🔴 THREE FIELDS, THREE AUDIENCES — AND **NONE OF THEM IS VIEWER-FACING COPY**.
|
|
115
|
+
* The viewer-facing string is one the APP owns; this error carries nothing you
|
|
116
|
+
* may render as-is. Two apps migrating to `0.43.0` each had a `catch` that piped
|
|
117
|
+
* `err.message` straight into rendered UI, and each would have shipped
|
|
118
|
+
* `estimate did not return a usable price (failed) — reason on .snapshot.error`
|
|
119
|
+
* to end users; both were caught only by a test asserting that exact string.
|
|
120
|
+
*
|
|
121
|
+
* - {@link WorkflowEstimateError.snapshot}`.error` — **the DIAGNOSTIC read.**
|
|
122
|
+
* The server's own words, verbatim. **Server-authored and UNSANITISED**:
|
|
123
|
+
* civitai's `errorHandling.ts` documents that raw upstream text —
|
|
124
|
+
* Prisma/`pg` column and constraint names among it — can reach this field.
|
|
125
|
+
* Log it, or show it in a developer-facing error surface. **NEVER render it
|
|
126
|
+
* verbatim into markup**, and think before shipping it to a third-party
|
|
127
|
+
* error tracker.
|
|
128
|
+
* - {@link WorkflowEstimateError.message} — **DEVELOPER-FACING.** A CONSTANT
|
|
129
|
+
* template with only `code` interpolated. It contains no server text and
|
|
130
|
+
* nothing to sanitise, so it is safe to LOG and safe to let a stack trace
|
|
131
|
+
* print — that is deliberate, because `message` is what an uncaught
|
|
122
132
|
* rejection prints and what a third-party block's error reporter ships
|
|
123
133
|
* upstream by default, and this is a package consumed by third-party code.
|
|
124
|
-
*
|
|
125
|
-
*
|
|
134
|
+
* **It is NOT intended for display to viewers**: it names an internal field
|
|
135
|
+
* path, it is not localised, and **its exact wording is NOT a contract** —
|
|
136
|
+
* it can change in any release, so a UI built on it silently rots.
|
|
137
|
+
* - {@link WorkflowEstimateError.code} — **the BRANCH TARGET**, and the only
|
|
138
|
+
* stable one. Switch on it to pick a viewer-facing string YOUR APP owns
|
|
139
|
+
* (see the mapper in the `@example` on {@link useBuzzWorkflow}).
|
|
126
140
|
*
|
|
127
141
|
* 🔴 DO NOT "SIMPLIFY" THIS BY PUTTING `snapshot.error` BACK ON `message`. The
|
|
128
142
|
* two fields are split on purpose and the split is what keeps database internals
|
|
129
143
|
* off a third party's default-printed surface. `test/useBuzzWorkflow.test.tsx`
|
|
130
144
|
* pins it with a realistic `Unique constraint failed: Key (email)=(…)` fixture.
|
|
131
145
|
*
|
|
132
|
-
* 🔴
|
|
133
|
-
* `WORKFLOW_SUBMITTED`
|
|
134
|
-
* `status
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* own blast radius on the recovery path (a fix MUST keep the budget-rejection arm
|
|
140
|
-
* RESOLVING, or the top-up flow breaks) and is deliberately NOT attempted here —
|
|
141
|
-
* so `submit` is left resolving BOTH, and the accompanying test pins only the
|
|
142
|
-
* budget-rejection producer, not a claim that submit is correct.
|
|
143
|
-
*
|
|
144
|
-
* 🔴 TRACKED, NOT FORGOTTEN: civitai/civitai-app-starters#251. Read it before
|
|
145
|
-
* touching `submit`'s failure handling.
|
|
146
|
+
* 🔴 `submit` HAS ITS OWN CLASS — {@link WorkflowSubmitError} — BECAUSE ITS
|
|
147
|
+
* DISCRIMINATOR IS DIFFERENT. On `WORKFLOW_SUBMITTED` both producers report
|
|
148
|
+
* `status:'failed'`, so `status` separates nothing there and `cost` presence is
|
|
149
|
+
* what does. Do not merge the two classes or reuse this one for submit: the
|
|
150
|
+
* question each answers is different ("was a usable price returned?" vs "was a
|
|
151
|
+
* workflow actually queued?") and so is the arm that must keep resolving.
|
|
152
|
+
* (civitai/civitai-app-starters#251, the `submit` half of civitai/civitai#4159.)
|
|
146
153
|
*/
|
|
147
154
|
export declare class WorkflowEstimateError extends Error {
|
|
148
155
|
/**
|
|
149
156
|
* WHICH PRODUCER this was, structurally — so a caller branches on an enum
|
|
150
157
|
* rather than on prose. It is the ONLY stable branch target here:
|
|
151
158
|
* `snapshot.error` is server-authored and can change without notice, and
|
|
152
|
-
* {@link WorkflowEstimateError.message} is a generic summary
|
|
153
|
-
* wording is not a contract either.
|
|
159
|
+
* {@link WorkflowEstimateError.message} is a generic developer-facing summary
|
|
160
|
+
* whose exact wording is not a contract either.
|
|
161
|
+
*
|
|
162
|
+
* 🔴 THIS IS WHAT A VIEWER-FACING MESSAGE MUST BE DERIVED FROM. Branch on
|
|
163
|
+
* `code` and return a string your app owns and localises; never render
|
|
164
|
+
* `message` or `snapshot.error` to a viewer.
|
|
154
165
|
*
|
|
155
166
|
* - `'failed'` — the reply's `status` is `'failed'`. USUALLY that means
|
|
156
167
|
* `blocks.estimateWorkflow` threw server-side and the host posted its
|
|
@@ -175,12 +186,176 @@ export declare class WorkflowEstimateError extends Error {
|
|
|
175
186
|
* will not price — the whole point of civitai/civitai#4159), but civitai's
|
|
176
187
|
* `errorHandling.ts` documents that raw upstream text — Prisma/`pg` column and
|
|
177
188
|
* constraint names among it — can reach that field. Log it, show it in a
|
|
178
|
-
* developer-facing error surface,
|
|
179
|
-
* ship it to a third-party error tracker without thinking about it.
|
|
189
|
+
* developer-facing error surface, **NEVER render it verbatim into markup**,
|
|
190
|
+
* and do not ship it to a third-party error tracker without thinking about it.
|
|
180
191
|
*/
|
|
181
192
|
readonly snapshot: BlockWorkflowSnapshot;
|
|
182
193
|
constructor(snapshot: BlockWorkflowSnapshot, code: 'failed' | 'no-cost');
|
|
183
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Why {@link UseBuzzWorkflowReturn.submit} rejected. **The two differ on whether
|
|
197
|
+
* money may already have moved** — see {@link WorkflowSubmitError.code}.
|
|
198
|
+
*/
|
|
199
|
+
export type WorkflowSubmitErrorCode = 'exception' | 'workflow-failed';
|
|
200
|
+
/**
|
|
201
|
+
* Thrown by {@link UseBuzzWorkflowReturn.submit} when the host's reply carries no
|
|
202
|
+
* usable workflow outcome — either the submit ERRORED before anything was queued,
|
|
203
|
+
* or a workflow-shaped reply came back already failed with no price.
|
|
204
|
+
*
|
|
205
|
+
* 🔴 READ {@link WorkflowSubmitError.code} BEFORE SAYING ANYTHING ABOUT MONEY.
|
|
206
|
+
* The two codes differ on exactly that, and getting it wrong is expensive in both
|
|
207
|
+
* directions — see the per-code notes below.
|
|
208
|
+
*
|
|
209
|
+
* 🔴 WHY THIS EXISTS — civitai/civitai-app-starters#251, the `submit` half of
|
|
210
|
+
* civitai/civitai#4159. A server-side `blocks.submitWorkflow` throw cannot reach
|
|
211
|
+
* the block as a rejection; the reply crosses `postMessage`. The host instead
|
|
212
|
+
* posts a well-formed `WORKFLOW_SUBMITTED` carrying its `failureSnapshot(err)` —
|
|
213
|
+
* `{ workflowId: 'failed', status: 'failed', error: '<server message>' }`, with
|
|
214
|
+
* **no `cost`**. That snapshot is perfectly legal, so `submit()` resolved it and
|
|
215
|
+
* moved the hook to `'done'`, handing the caller a "workflow" that does not
|
|
216
|
+
* exist.
|
|
217
|
+
*
|
|
218
|
+
* 🔴 TWO PRODUCERS, ONE `status` — AND HERE `status` DISCRIMINATES NOTHING. This
|
|
219
|
+
* is what makes submit's version of the defect different from estimate's, and
|
|
220
|
+
* why {@link WorkflowEstimateError}'s guard could not simply be copied:
|
|
221
|
+
*
|
|
222
|
+
* - **budget / spend-cap REJECTION** — a legitimate OUTCOME. The server quotes
|
|
223
|
+
* the price it refused to charge, so the snapshot carries a numeric
|
|
224
|
+
* `cost.total`. Blocks recover from it (open a top-up flow), so it MUST keep
|
|
225
|
+
* RESOLVING. Every such exit on the server attaches a cost: the per-call
|
|
226
|
+
* `buzzBudget` gate, the per-user daily Buzz cap, the per-app aggregate and
|
|
227
|
+
* velocity caps, and the dev-tunnel session cap — on all three body kinds.
|
|
228
|
+
* - **caught server EXCEPTION** — `failureSnapshot(err)`, no `cost`, and the
|
|
229
|
+
* `'failed'` sentinel id. The host had no workflow to report — usually
|
|
230
|
+
* nothing was queued, but a lost response or an in-progress idempotency
|
|
231
|
+
* conflict lands here too. Rejects as `'exception'`.
|
|
232
|
+
* - **a reply that came back failed and unpriced with a NON-sentinel id** —
|
|
233
|
+
* normally a genuine orchestrator id, no `cost` (the server's
|
|
234
|
+
* `snapshotFromWorkflow` omits the key whenever `workflow.cost?.total` is not
|
|
235
|
+
* numeric). Rejects as `'workflow-failed'`, and **money may already be
|
|
236
|
+
* committed** — see that code's note.
|
|
237
|
+
*
|
|
238
|
+
* So the rule is `cost` presence, and ONLY among failure-shaped replies: an
|
|
239
|
+
* ordinary in-flight submit (`{ workflowId:'wf_…', status:'pending' }`) is
|
|
240
|
+
* cost-less too and must keep resolving, which is why the guard is not keyed on
|
|
241
|
+
* `cost` alone either. Both clauses are load-bearing; each has its own control
|
|
242
|
+
* in `test/useBuzzWorkflow.test.tsx`.
|
|
243
|
+
*
|
|
244
|
+
* 🔴 THE GUARD IS DELIBERATELY NOT WIDENED TO ALL TERMINAL STATUSES. A cost-less
|
|
245
|
+
* `succeeded` / `canceled` / `expired` reply is a legitimate outcome and must
|
|
246
|
+
* RESOLVE; only `'failed'` is unusable. Swapping the clause for
|
|
247
|
+
* `TERMINAL_STATUSES.has(...)` is a silent WIDENING that a delete-only mutation
|
|
248
|
+
* sweep cannot see — `test/useBuzzWorkflow.test.tsx` pins each of those three
|
|
249
|
+
* statuses explicitly for exactly that reason.
|
|
250
|
+
*
|
|
251
|
+
* 🔴 THREE FIELDS, THREE AUDIENCES — AND **NONE OF THEM IS VIEWER-FACING COPY**,
|
|
252
|
+
* exactly as on {@link WorkflowEstimateError}. Two apps migrating to `0.43.0`
|
|
253
|
+
* piped that class's `err.message` straight into rendered UI
|
|
254
|
+
* (civitai/civitai-app-starters#253); the same split applies here so the same
|
|
255
|
+
* mistake is not re-enabled on the submit path.
|
|
256
|
+
*
|
|
257
|
+
* - {@link WorkflowSubmitError.snapshot}`.error` — **the DIAGNOSTIC read.** The
|
|
258
|
+
* server's own words, verbatim. **Server-authored and UNSANITISED**:
|
|
259
|
+
* civitai's `errorHandling.ts` documents that raw upstream text —
|
|
260
|
+
* Prisma/`pg` column and constraint names among it — can reach this field.
|
|
261
|
+
* Log it, or show it in a developer-facing error surface. **NEVER render it
|
|
262
|
+
* verbatim into markup**, and think before shipping it to a third-party
|
|
263
|
+
* error tracker.
|
|
264
|
+
* - {@link WorkflowSubmitError.message} — **DEVELOPER-FACING.** A CONSTANT
|
|
265
|
+
* template with only `code` interpolated. It contains no server text, so it
|
|
266
|
+
* is safe to LOG and safe to let a stack trace print — deliberate, because
|
|
267
|
+
* `message` is what an uncaught rejection prints and what a third-party
|
|
268
|
+
* block's error reporter ships upstream by default. **It is NOT intended for
|
|
269
|
+
* display to viewers** and **its exact wording is NOT a contract.**
|
|
270
|
+
* - {@link WorkflowSubmitError.code} — **the BRANCH TARGET**, and the only
|
|
271
|
+
* stable one.
|
|
272
|
+
*
|
|
273
|
+
* 🔴 DO NOT "SIMPLIFY" THIS BY PUTTING `snapshot.error` BACK ON `message`, and do
|
|
274
|
+
* not widen the guard to reject a budget rejection. The first re-opens #253; the
|
|
275
|
+
* second breaks the top-up recovery flow, which is the one thing #251 says must
|
|
276
|
+
* not break.
|
|
277
|
+
*/
|
|
278
|
+
export declare class WorkflowSubmitError extends Error {
|
|
279
|
+
/**
|
|
280
|
+
* WHICH PRODUCER this was, structurally — so a caller branches on an enum
|
|
281
|
+
* rather than on prose. It is the ONLY stable branch target here:
|
|
282
|
+
* `snapshot.error` is server-authored and can change without notice, and
|
|
283
|
+
* {@link WorkflowSubmitError.message} is a generic developer-facing summary
|
|
284
|
+
* whose exact wording is not a contract either.
|
|
285
|
+
*
|
|
286
|
+
* 🔴 THE TWO CODES DIFFER ON WHETHER MONEY MOVED. Do not collapse them, and do
|
|
287
|
+
* not write recovery copy that ignores the distinction.
|
|
288
|
+
*
|
|
289
|
+
* - `'exception'` — the host built this reply itself
|
|
290
|
+
* (`failureSnapshot(err)`, id {@link HOST_SYNTHESISED_WORKFLOW_ID}), from a
|
|
291
|
+
* `catch` OR from a non-catch short-circuit such as the moderator-review
|
|
292
|
+
* nack. **It means the host had no workflow to report — NOT that nothing
|
|
293
|
+
* happened.**
|
|
294
|
+
*
|
|
295
|
+
* 🔴 USUALLY nothing was queued and nothing was charged, and a retry is the
|
|
296
|
+
* sensible recovery. But the same shape is reachable when a workflow MAY have
|
|
297
|
+
* been created and charged, because the failure happened after the request
|
|
298
|
+
* left the block:
|
|
299
|
+
* - a **lost response** — the server's own catch concedes that "the
|
|
300
|
+
* orchestrator externalId dedupe still protects a retry that DID create a
|
|
301
|
+
* workflow server-side despite a lost response". What that path refunds
|
|
302
|
+
* is its own reservation and cap counters; the Buzz debit happens inside
|
|
303
|
+
* the orchestrator's submit.
|
|
304
|
+
* - an **in-progress idempotency CONFLICT** — a concurrent first attempt is
|
|
305
|
+
* still in flight and may be committing right now. Retrying blind is the
|
|
306
|
+
* worst option there.
|
|
307
|
+
* - a **transient transport failure** (5xx / 408 / 429 / 401), which the
|
|
308
|
+
* `dev:live` host collapses into this same shape.
|
|
309
|
+
*
|
|
310
|
+
* So prefer **reusing the same {@link SubmitWorkflowOptions.idempotencyKey}**
|
|
311
|
+
* on retry rather than letting a fresh one be minted, and do not render
|
|
312
|
+
* "nothing was charged" to a viewer as a certainty. There is no workflow id
|
|
313
|
+
* on this arm to poll.
|
|
314
|
+
*
|
|
315
|
+
* - `'workflow-failed'` — the id was NOT the host's sentinel, so the reply came
|
|
316
|
+
* from the server's `snapshotFromWorkflow`: normally a real orchestrator id,
|
|
317
|
+
* already `'failed'` and with no price.
|
|
318
|
+
*
|
|
319
|
+
* 🔴 **DO NOT TELL THE VIEWER NOTHING WAS CHARGED, AND DO NOT BLIND-RETRY.**
|
|
320
|
+
* Server-side, `blocks.submitWorkflow` treats *any resolved* submit as
|
|
321
|
+
* money-COMMITTED: its own comment is "A resolved submit is money-COMMITTED
|
|
322
|
+
* (the reservation is kept regardless of snapshot status)… we do NOT refund
|
|
323
|
+
* on a non-throwing failed snapshot", and `finalizeGenIdempotency` runs on
|
|
324
|
+
* that path. So Buzz may already be spent for this call.
|
|
325
|
+
*
|
|
326
|
+
* A retry is a SECOND reservation unless you reuse the same
|
|
327
|
+
* {@link SubmitWorkflowOptions.idempotencyKey} — `submit()` mints a fresh key
|
|
328
|
+
* per call by default, so an automatic retry double-reserves.
|
|
329
|
+
*
|
|
330
|
+
* 🔴 `err.snapshot.workflowId` IS USUALLY POLLABLE, BUT CHECK IT FIRST — this
|
|
331
|
+
* arm is defined by what the id is NOT. The server emits `workflow.id ??
|
|
332
|
+
* 'whatif'`, and it treats **both** `'failed'` and `'whatif'` as non-workflow
|
|
333
|
+
* sentinels (it skips its own persistence and settle steps on either). So
|
|
334
|
+
* `'whatif'` lands here — correctly, because the cautious money reading still
|
|
335
|
+
* applies — but there is nothing to poll. Guard with
|
|
336
|
+
* `err.snapshot.workflowId !== 'whatif'` before calling
|
|
337
|
+
* {@link UseBuzzWorkflowReturn.watch} / {@link UseBuzzWorkflowReturn.poll} to
|
|
338
|
+
* learn the workflow's actual fate before spending again.
|
|
339
|
+
*
|
|
340
|
+
* A union rather than a boolean so a future producer gets its own code without
|
|
341
|
+
* breaking a caller's `switch`.
|
|
342
|
+
*
|
|
343
|
+
* 🔴 A BUDGET REJECTION NEVER ARRIVES HERE. It resolves, and is read off the
|
|
344
|
+
* returned snapshot as `status === 'failed'` with a numeric `cost.total`.
|
|
345
|
+
*/
|
|
346
|
+
readonly code: WorkflowSubmitErrorCode;
|
|
347
|
+
/**
|
|
348
|
+
* The snapshot the host replied with, VERBATIM — including `snapshot.error`,
|
|
349
|
+
* the server's own words, when there are any.
|
|
350
|
+
*
|
|
351
|
+
* 🔴 `snapshot.error` IS SERVER-AUTHORED AND UNSANITISED. It is where the
|
|
352
|
+
* reason lives, but raw upstream text — Prisma/`pg` column and constraint
|
|
353
|
+
* names among it — can reach that field. Log it, show it in a developer-facing
|
|
354
|
+
* error surface, **NEVER render it verbatim into markup**.
|
|
355
|
+
*/
|
|
356
|
+
readonly snapshot: BlockWorkflowSnapshot;
|
|
357
|
+
constructor(snapshot: BlockWorkflowSnapshot, code: WorkflowSubmitErrorCode);
|
|
358
|
+
}
|
|
184
359
|
/** Optional per-submit controls. */
|
|
185
360
|
export interface SubmitWorkflowOptions {
|
|
186
361
|
/**
|
|
@@ -189,6 +364,11 @@ export interface SubmitWorkflowOptions {
|
|
|
189
364
|
* host+orchestrator collapse it to ONE Buzz charge instead of double-charging.
|
|
190
365
|
* Omit → the hook generates a fresh key per `submit()` call (each call is a new
|
|
191
366
|
* logical submit); pass a stable id (e.g. a grid-cell id) to make a retry safe.
|
|
367
|
+
*
|
|
368
|
+
* 🔴 THIS IS THE FIELD THAT MAKES A RETRY AFTER A `'workflow-failed'` REJECTION
|
|
369
|
+
* SAFE. That code means a workflow probably exists and its spend may already
|
|
370
|
+
* be committed server-side; retrying WITHOUT reusing the key mints a fresh one
|
|
371
|
+
* and therefore a SECOND reservation. See {@link WorkflowSubmitError.code}.
|
|
192
372
|
*/
|
|
193
373
|
idempotencyKey?: string;
|
|
194
374
|
}
|
|
@@ -216,6 +396,43 @@ interface UseBuzzWorkflowReturn {
|
|
|
216
396
|
* estimate's price sitting in `result` for a Confirm gate to read.
|
|
217
397
|
*/
|
|
218
398
|
estimate: (body: WorkflowBody) => Promise<BlockWorkflowSnapshot>;
|
|
399
|
+
/**
|
|
400
|
+
* Queue a workflow. Resolves ONLY with a reply that represents a real workflow
|
|
401
|
+
* OUTCOME — one that was queued, or one the server priced and then refused.
|
|
402
|
+
*
|
|
403
|
+
* 🔴 REJECTS with {@link WorkflowSubmitError} when the reply is failure-shaped
|
|
404
|
+
* and carries no price. Wrap every call in `try/catch`; see that class for why
|
|
405
|
+
* resolving such a reply was the `submit` half of civitai/civitai#4159.
|
|
406
|
+
* **Check `err.code` before saying anything about money** — and note that
|
|
407
|
+
* NEITHER code guarantees nothing was spent. `'exception'` usually means
|
|
408
|
+
* nothing was queued or charged, but a lost response or an in-progress
|
|
409
|
+
* idempotency conflict reaches it too; `'workflow-failed'` means a workflow
|
|
410
|
+
* PROBABLY exists (the `'whatif'` sentinel lands here too and has nothing to
|
|
411
|
+
* poll — guard before polling) and its spend may already be committed. Do not
|
|
412
|
+
* tell the viewer it was free, and on either code prefer reusing the same
|
|
413
|
+
* {@link SubmitWorkflowOptions.idempotencyKey}.
|
|
414
|
+
*
|
|
415
|
+
* 🔴 A BUDGET / SPEND-CAP REJECTION STILL RESOLVES, and that is deliberate. It
|
|
416
|
+
* is a documented outcome, not an error: the server quotes what it refused to
|
|
417
|
+
* charge, so the resolved snapshot has `status === 'failed'` AND a numeric
|
|
418
|
+
* `cost.total`. THAT is the shape to branch on when offering a top-up —
|
|
419
|
+
* `useBuzzPurchase().openPurchaseModal()` — not a `catch`.
|
|
420
|
+
*
|
|
421
|
+
* 🔴 BUT A RESOLVED `'failed'` IS NOT ALWAYS AN AFFORDABILITY PROBLEM, so do not
|
|
422
|
+
* wire every one of them to a purchase modal. The per-app **velocity** limit,
|
|
423
|
+
* the per-app **aggregate daily** cap, a fail-closed "temporarily unavailable"
|
|
424
|
+
* deny and a **missing price quote** are all priced, resolving outcomes that
|
|
425
|
+
* buying Buzz cannot fix.
|
|
426
|
+
*
|
|
427
|
+
* 🔴 THIS ALSO INCLUDES MODERATOR REVIEW PREVIEW. While an app is under review
|
|
428
|
+
* the host short-circuits every workflow request with
|
|
429
|
+
* `failureSnapshot('not available in review preview')`, so `submit()` rejects
|
|
430
|
+
* there where it used to resolve — the correct reading (no workflow was
|
|
431
|
+
* queued), and why the catch is not optional.
|
|
432
|
+
*
|
|
433
|
+
* `result` is updated to the returned snapshot BEFORE any rejection, so a
|
|
434
|
+
* failed submit can never leave a previous submit's workflow in `result`.
|
|
435
|
+
*/
|
|
219
436
|
submit: (body: WorkflowBody, options?: SubmitWorkflowOptions) => Promise<BlockWorkflowSnapshot>;
|
|
220
437
|
/**
|
|
221
438
|
* ONE host round-trip. The low-level pull primitive — you almost certainly
|
|
@@ -264,10 +481,20 @@ interface UseBuzzWorkflowReturn {
|
|
|
264
481
|
* Orchestrates the estimate → confirm → submit → poll dance through the
|
|
265
482
|
* host-mediated `postMessage` path.
|
|
266
483
|
*
|
|
267
|
-
* The host enforces budget rules (`cost_estimate <= token.buzzBudget`)
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
484
|
+
* The host enforces budget rules (`cost_estimate <= token.buzzBudget`) before
|
|
485
|
+
* forwarding to the orchestrator.
|
|
486
|
+
*
|
|
487
|
+
* 🔴 A BUDGET REFUSAL DOES **NOT** REJECT — IT RESOLVES. It comes back as a
|
|
488
|
+
* snapshot with `status: 'failed'`, an `error` string and the `cost` the server
|
|
489
|
+
* declined to charge, and THAT resolved shape is the cue to call
|
|
490
|
+
* `useBuzzPurchase().openPurchaseModal()`. What DOES reject is a submit with no
|
|
491
|
+
* usable outcome — see {@link WorkflowSubmitError}. Routing a rejection into a
|
|
492
|
+
* top-up sells Buzz for a failure Buzz cannot fix.
|
|
493
|
+
*
|
|
494
|
+
* 🔴 NOR IS EVERY RESOLVED `'failed'` AN AFFORDABILITY PROBLEM. The per-app
|
|
495
|
+
* velocity limit, the per-app aggregate daily cap, a fail-closed "temporarily
|
|
496
|
+
* unavailable" deny and a missing price quote are all priced, resolving outcomes
|
|
497
|
+
* too. Branch on the message/your own policy before offering to sell anything.
|
|
271
498
|
*
|
|
272
499
|
* AFTER `submit` FLIPS `status` TO `'polling'`, USE `watch(workflowId)`. It owns
|
|
273
500
|
* the loop, resolves on the terminal snapshot, and pushes every intermediate
|
|
@@ -310,21 +537,58 @@ interface UseBuzzWorkflowReturn {
|
|
|
310
537
|
* modelVersionId,
|
|
311
538
|
* params: { prompt: 'a cat' },
|
|
312
539
|
* };
|
|
540
|
+
* // A viewer-facing string YOUR APP owns, chosen by `code`. Neither
|
|
541
|
+
* // `err.message` (developer-facing, wording not a contract) nor
|
|
542
|
+
* // `err.snapshot.error` (server-authored, unsanitised) may be rendered.
|
|
543
|
+
* const estimateFailureMessage = (err: WorkflowEstimateError) =>
|
|
544
|
+
* err.code === 'no-cost'
|
|
545
|
+
* ? 'We could not get a price for this configuration. Try adjusting it.'
|
|
546
|
+
* : 'Pricing is unavailable right now. Please try again shortly.';
|
|
547
|
+
*
|
|
313
548
|
* // 🔴 estimate() REJECTS when the reply carries no usable price — an errored
|
|
314
549
|
* // estimate, or one that came back with no numeric cost. ALWAYS catch it.
|
|
315
550
|
* try {
|
|
316
551
|
* await estimate(body); // status 'estimating' → 'confirming' (cost in result.cost.total)
|
|
317
552
|
* } catch (err) {
|
|
318
553
|
* // status is now 'error'; `result` holds the unusable snapshot, never a
|
|
319
|
-
* // STALE priced one.
|
|
320
|
-
* // reason is on err.snapshot.error (unsanitised — log it, don't render it as
|
|
321
|
-
* // trusted copy). See WorkflowEstimateError.
|
|
554
|
+
* // STALE priced one. See WorkflowEstimateError for the three-audience split.
|
|
322
555
|
* if (!(err instanceof WorkflowEstimateError)) throw err;
|
|
323
|
-
* logForDebugging(err.snapshot.error);
|
|
324
|
-
* showError(
|
|
556
|
+
* logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
|
|
557
|
+
* showError(estimateFailureMessage(err)); // viewer-facing: app-owned
|
|
558
|
+
* }
|
|
559
|
+
* // 🔴 submit() REJECTS when the reply carries no usable workflow outcome, so it
|
|
560
|
+
* // needs a catch too — and the two codes differ on whether MONEY MOVED.
|
|
561
|
+
* try {
|
|
562
|
+
* const snap = await submit(body); // status 'submitting' → 'polling'
|
|
563
|
+
* if (snap.status === 'failed') {
|
|
564
|
+
* // RESOLVED + priced = a server outcome (affordability, a cap, a velocity
|
|
565
|
+
* // limit, a transient deny). Only some of those are fixable by buying Buzz.
|
|
566
|
+
* showError(submitOutcomeMessage(snap));
|
|
567
|
+
* } else {
|
|
568
|
+
* const done = await watch(snap.workflowId, { onUpdate: render }); // → terminal
|
|
569
|
+
* if (done.status === 'succeeded') setImages(done.imageUrls ?? []);
|
|
570
|
+
* }
|
|
571
|
+
* } catch (err) {
|
|
572
|
+
* if (!(err instanceof WorkflowSubmitError)) throw err;
|
|
573
|
+
* logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
|
|
574
|
+
* // 🔴 TWO SEPARATE QUESTIONS: `code` decides what you may say about MONEY,
|
|
575
|
+
* // the id decides only whether there is anything to POLL. Conjoining them
|
|
576
|
+
* // sends a 'whatif' id into the reassuring arm.
|
|
577
|
+
* if (err.code === 'workflow-failed') {
|
|
578
|
+
* // Spend may already be committed. Do NOT retry blindly (that mints a new
|
|
579
|
+
* // idempotency key = a second reservation) and do NOT claim nothing was
|
|
580
|
+
* // charged.
|
|
581
|
+
* showError('The generation may have started but did not complete.');
|
|
582
|
+
* // 'whatif' is a non-workflow sentinel — nothing behind it to poll.
|
|
583
|
+
* if (err.snapshot.workflowId !== 'whatif') {
|
|
584
|
+
* await watch(err.snapshot.workflowId, { onUpdate: render });
|
|
585
|
+
* }
|
|
586
|
+
* } else {
|
|
587
|
+
* // 'exception' — usually nothing was queued. Retry, but reuse the SAME
|
|
588
|
+
* // idempotencyKey: a lost response can also land here.
|
|
589
|
+
* showError('Could not start the generation. Please try again.');
|
|
590
|
+
* }
|
|
325
591
|
* }
|
|
326
|
-
* const snap = await submit(body); // status 'submitting' → 'polling'; returns a workflowId
|
|
327
|
-
* const done = await watch(snap.workflowId, { onUpdate: render }); // → terminal
|
|
328
592
|
*/
|
|
329
593
|
export declare function useBuzzWorkflow(): UseBuzzWorkflowReturn;
|
|
330
594
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AA8BnG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAW7C,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACrD;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyBD
|
|
1
|
+
{"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
|