@civitai/blocks-react 0.42.0 → 0.43.1
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 +59 -4
- package/dist/hooks/useBuzzPurchase.d.ts +6 -0
- package/dist/hooks/useBuzzPurchase.d.ts.map +1 -1
- package/dist/hooks/useBuzzPurchase.js +8 -1
- package/dist/hooks/useBuzzPurchase.js.map +1 -1
- package/dist/hooks/useBuzzWorkflow.d.ts +169 -1
- package/dist/hooks/useBuzzWorkflow.d.ts.map +1 -1
- package/dist/hooks/useBuzzWorkflow.js +201 -1
- package/dist/hooks/useBuzzWorkflow.js.map +1 -1
- package/dist/hooks/useCheckpointPicker.d.ts +0 -7
- package/dist/hooks/useCheckpointPicker.d.ts.map +1 -1
- package/dist/hooks/useCheckpointPicker.js +2 -8
- package/dist/hooks/useCheckpointPicker.js.map +1 -1
- package/dist/hooks/useImageUpload.d.ts +1 -1
- package/dist/hooks/useImageUpload.d.ts.map +1 -1
- package/dist/hooks/useImageUpload.js +11 -4
- package/dist/hooks/useImageUpload.js.map +1 -1
- package/dist/hooks/usePublishGenerationOutputs.d.ts +11 -3
- package/dist/hooks/usePublishGenerationOutputs.d.ts.map +1 -1
- package/dist/hooks/usePublishGenerationOutputs.js +13 -2
- package/dist/hooks/usePublishGenerationOutputs.js.map +1 -1
- package/dist/hooks/useResourcePicker.js +2 -2
- package/dist/hooks/useResourcePicker.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/iframeTransport.js +1 -1
- package/dist/internal/iframeTransport.js.map +1 -1
- package/dist/internal/mockHost.d.ts +31 -0
- package/dist/internal/mockHost.d.ts.map +1 -1
- package/dist/internal/mockHost.js +31 -0
- package/dist/internal/mockHost.js.map +1 -1
- package/dist/internal/requestTimeouts.d.ts +62 -0
- package/dist/internal/requestTimeouts.d.ts.map +1 -0
- package/dist/internal/requestTimeouts.js +168 -0
- package/dist/internal/requestTimeouts.js.map +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -216,9 +216,30 @@ const body: WorkflowBody = {
|
|
|
216
216
|
modelVersionId,
|
|
217
217
|
params: { prompt: userPrompt },
|
|
218
218
|
};
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
// The viewer-facing copy is a string YOUR APP owns, chosen by `err.code`.
|
|
220
|
+
// Nothing on the error may be rendered: `err.message` is developer-facing and
|
|
221
|
+
// its wording is not a contract; `err.snapshot.error` is server-authored and
|
|
222
|
+
// unsanitised.
|
|
223
|
+
const estimateFailureMessage = (err: WorkflowEstimateError) =>
|
|
224
|
+
err.code === 'no-cost'
|
|
225
|
+
? 'We could not get a price for this configuration. Try adjusting it.'
|
|
226
|
+
: 'Pricing is unavailable right now. Please try again shortly.';
|
|
227
|
+
|
|
228
|
+
// 🔴 estimate() REJECTS when the reply carries no usable price. ALWAYS catch it.
|
|
229
|
+
let priced = false;
|
|
230
|
+
try {
|
|
231
|
+
await estimate(body); // status 'estimating' → 'confirming' (cost in result.cost.total)
|
|
232
|
+
priced = true;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
if (!(err instanceof WorkflowEstimateError)) throw err;
|
|
235
|
+
// status is now 'error'. Log both for the developer; render neither.
|
|
236
|
+
logForDebugging(err.message, err.snapshot.error);
|
|
237
|
+
showError(estimateFailureMessage(err));
|
|
238
|
+
}
|
|
239
|
+
if (priced) {
|
|
240
|
+
const snap = await submit(body); // status 'submitting' → 'polling'; returns a workflowId
|
|
241
|
+
await poll(snap.workflowId); // you loop this on a backoff until terminal
|
|
242
|
+
}
|
|
222
243
|
```
|
|
223
244
|
|
|
224
245
|
**Status semantics** (gotcha #8/#9/#10):
|
|
@@ -231,9 +252,43 @@ await poll(snap.workflowId); // you loop this on a backoff until terminal
|
|
|
231
252
|
- The hook does **not** auto-poll. After `submit` flips status to `'polling'`,
|
|
232
253
|
the **caller** runs a `useEffect` that calls `poll(workflowId)` on a backoff
|
|
233
254
|
until the snapshot is terminal (`succeeded | failed | canceled | expired`).
|
|
234
|
-
- An over-budget / rejected submit comes back as a **resolved** snapshot with
|
|
255
|
+
- An over-budget / rejected **submit** comes back as a **resolved** snapshot with
|
|
235
256
|
`status: 'failed'` + an `error` string — the transport resolves the reply, it
|
|
236
257
|
doesn't throw. Check `snap.status`, not just `try/catch`.
|
|
258
|
+
- **`estimate` is the exception to that rule** (`@civitai/blocks-react@0.43.0+`).
|
|
259
|
+
It **rejects** with a `WorkflowEstimateError` when the reply carries no usable
|
|
260
|
+
price, rather than resolving a snapshot with no `cost`. Two things produce
|
|
261
|
+
that, and `err.code` tells them apart: `'failed'` (the estimate errored
|
|
262
|
+
server-side) and `'no-cost'` (an otherwise-successful reply that simply has no
|
|
263
|
+
price).
|
|
264
|
+
Before that version both cases resolved, so a block that correctly gates
|
|
265
|
+
Confirm on `typeof cost === 'number'` rendered a dialog it could never confirm
|
|
266
|
+
("Cost unavailable") and the server's reason was discarded — civitai/civitai#4159.
|
|
267
|
+
Note this also fires in **moderator review preview**, where the host answers
|
|
268
|
+
every workflow request with `'not available in review preview'`: without a
|
|
269
|
+
`catch`, a reviewer's first click becomes an unhandled rejection.
|
|
270
|
+
To exercise your `catch` locally, set the mock host's
|
|
271
|
+
`generation.failEstimate: 'failed' | 'no-cost'`.
|
|
272
|
+
- **Three fields, three audiences — and none of them is viewer-facing copy.**
|
|
273
|
+
The string a viewer sees is one **your app owns**; nothing on this error may
|
|
274
|
+
be rendered as-is.
|
|
275
|
+
- `err.code` (`'failed' | 'no-cost'`) — **the branch target**, and the only
|
|
276
|
+
stable one. Switch on it to pick your own localised message.
|
|
277
|
+
- `err.snapshot.error` — **the diagnostic read**, and recovering it is the
|
|
278
|
+
whole point of the fix. **Server-authored and unsanitised** (raw upstream
|
|
279
|
+
text, including database constraint names, can reach it): log it or show it
|
|
280
|
+
in a developer-facing surface, and **never render it verbatim into markup**.
|
|
281
|
+
- `err.message` — **developer-facing**. A generic constant naming only the code
|
|
282
|
+
(`estimate did not return a usable price (no-cost) — reason on .snapshot.error`),
|
|
283
|
+
because `message` is what an uncaught rejection prints and what an error
|
|
284
|
+
reporter ships by default. Safe to log and to let a stack trace print;
|
|
285
|
+
**not intended for display to viewers** — it names an internal field path,
|
|
286
|
+
it is not localised, and **its exact wording is not a contract**, so a UI
|
|
287
|
+
built on it silently rots. Two apps migrating to `0.43.0` piped it into
|
|
288
|
+
rendered UI and would have shipped that sentence to end users.
|
|
289
|
+
- A cost of **`0` is a real price**, not a missing one (the orchestrator whatif
|
|
290
|
+
prices a cache hit at 0). `estimate` resolves it; only a non-numeric
|
|
291
|
+
`cost.total` rejects.
|
|
237
292
|
|
|
238
293
|
> **Estimate must mirror submit** (gotcha #59): build the params for `estimate`
|
|
239
294
|
> with the *exact* same logic as `submit` — same seed decision especially. The
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* balance increased; the new balance is included if the host reports it.
|
|
5
5
|
* The insufficient-budget recovery path for {@link useBuzzWorkflow}.
|
|
6
6
|
*
|
|
7
|
+
* 🔴 HUMAN-GATED: the reply comes when the viewer closes the modal, and a
|
|
8
|
+
* payment flow is nowhere near a ~30s round-trip — so this passes
|
|
9
|
+
* {@link HUMAN_INTERACTION_TIMEOUT_MS}. On the default the promise rejected
|
|
10
|
+
* mid-checkout, which reads to the block as "purchase failed" for a purchase
|
|
11
|
+
* that may well have SUCCEEDED (same defect class as civitai/civitai#4158).
|
|
12
|
+
*
|
|
7
13
|
* @example
|
|
8
14
|
* const { openPurchaseModal } = useBuzzPurchase();
|
|
9
15
|
* const { purchased, newBalance } = await openPurchaseModal(suggestedAmount);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuzzPurchase.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzPurchase.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useBuzzPurchase.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzPurchase.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,IAAI;IACjC,iBAAiB,EAAE,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvG,CAWA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
+
import { HUMAN_INTERACTION_TIMEOUT_MS } from '../internal/requestTimeouts.js';
|
|
2
3
|
import { getTransport } from '../internal/singleton.js';
|
|
3
4
|
import { sendTypedRequest } from '../internal/transport.js';
|
|
4
5
|
/**
|
|
@@ -7,6 +8,12 @@ import { sendTypedRequest } from '../internal/transport.js';
|
|
|
7
8
|
* balance increased; the new balance is included if the host reports it.
|
|
8
9
|
* The insufficient-budget recovery path for {@link useBuzzWorkflow}.
|
|
9
10
|
*
|
|
11
|
+
* 🔴 HUMAN-GATED: the reply comes when the viewer closes the modal, and a
|
|
12
|
+
* payment flow is nowhere near a ~30s round-trip — so this passes
|
|
13
|
+
* {@link HUMAN_INTERACTION_TIMEOUT_MS}. On the default the promise rejected
|
|
14
|
+
* mid-checkout, which reads to the block as "purchase failed" for a purchase
|
|
15
|
+
* that may well have SUCCEEDED (same defect class as civitai/civitai#4158).
|
|
16
|
+
*
|
|
10
17
|
* @example
|
|
11
18
|
* const { openPurchaseModal } = useBuzzPurchase();
|
|
12
19
|
* const { purchased, newBalance } = await openPurchaseModal(suggestedAmount);
|
|
@@ -14,7 +21,7 @@ import { sendTypedRequest } from '../internal/transport.js';
|
|
|
14
21
|
*/
|
|
15
22
|
export function useBuzzPurchase() {
|
|
16
23
|
const openPurchaseModal = useCallback(async (suggestedAmount) => {
|
|
17
|
-
const { purchased, newBalance } = await sendTypedRequest(getTransport(), { type: 'OPEN_BUZZ_PURCHASE', payload: { suggestedAmount } }, 'BUZZ_PURCHASE_RESULT');
|
|
24
|
+
const { purchased, newBalance } = await sendTypedRequest(getTransport(), { type: 'OPEN_BUZZ_PURCHASE', payload: { suggestedAmount } }, 'BUZZ_PURCHASE_RESULT', { timeoutMs: HUMAN_INTERACTION_TIMEOUT_MS });
|
|
18
25
|
return { purchased, newBalance };
|
|
19
26
|
}, []);
|
|
20
27
|
return { openPurchaseModal };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuzzPurchase.js","sourceRoot":"","sources":["../../src/hooks/useBuzzPurchase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D
|
|
1
|
+
{"version":3,"file":"useBuzzPurchase.js","sourceRoot":"","sources":["../../src/hooks/useBuzzPurchase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,eAAe;IAG7B,MAAM,iBAAiB,GAAG,WAAW,CAAC,KAAK,EAAE,eAAwB,EAAE,EAAE;QACvE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,gBAAgB,CACtD,YAAY,EAAE,EACd,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,EAAE,EAC5D,sBAAsB,EACtB,EAAE,SAAS,EAAE,4BAA4B,EAAE,CAC5C,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IACnC,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -71,6 +71,134 @@ export interface WatchWorkflowOptions {
|
|
|
71
71
|
*/
|
|
72
72
|
maxRetries?: number;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Thrown by {@link UseBuzzWorkflowReturn.estimate} when the host's reply does not
|
|
76
|
+
* carry a usable price — either because the estimate ERRORED, or because it came
|
|
77
|
+
* back without a numeric `cost.total`.
|
|
78
|
+
*
|
|
79
|
+
* 🔴 WHY THIS EXISTS — civitai/civitai#4159. A host-side `blocks.estimateWorkflow`
|
|
80
|
+
* throw is not propagated to the block as a rejection; it CANNOT be, because the
|
|
81
|
+
* reply crosses `postMessage`. The host instead posts a well-formed
|
|
82
|
+
* `ESTIMATE_RESULT` carrying its `failureSnapshot(err)` —
|
|
83
|
+
* `{ workflowId: 'failed', status: 'failed', error: '<server message>' }`, with
|
|
84
|
+
* **no `cost`** (see the host's `failureSnapshot.ts`, whose own comment states
|
|
85
|
+
* the block is expected to read `status`/`error`).
|
|
86
|
+
*
|
|
87
|
+
* Until this guard, `estimate()` resolved that snapshot as a SUCCESS and moved
|
|
88
|
+
* the hook to `'confirming'`. A correctly fail-closed block — one that gates
|
|
89
|
+
* Confirm on `typeof snapshot.cost?.total === 'number'` — then rendered a
|
|
90
|
+
* confirm dialog reading "Cost unavailable" with Confirm permanently disabled,
|
|
91
|
+
* and the server's own explanation (already present on `snapshot.error`) was
|
|
92
|
+
* discarded on the floor. The control was dead AND undiagnosable: a caller
|
|
93
|
+
* could not tell "priced at 0" from "the estimate errored".
|
|
94
|
+
*
|
|
95
|
+
* 🔴 TWO PRODUCERS, ONE OBSERVABLE — which is why {@link WorkflowEstimateError.code}
|
|
96
|
+
* exists and why the guard is not keyed on `status` alone. The server's
|
|
97
|
+
* `snapshotFromWorkflow` omits `cost` from an otherwise-SUCCESSFUL snapshot
|
|
98
|
+
* whenever the whatIf reply has no numeric total, so a `{status:'pending'}` reply
|
|
99
|
+
* with no `error` at all produces the identical dead control. A `status`-only
|
|
100
|
+
* guard would be INERT for that half. The two are distinguishable only by
|
|
101
|
+
* `snapshot.error` / `workflowId === 'failed'` — exactly the fields the original
|
|
102
|
+
* incident discarded, which is why it is still unknown which producer caused it.
|
|
103
|
+
*
|
|
104
|
+
* So an unusable estimate now REJECTS, which is what every caller's existing
|
|
105
|
+
* `try/catch` around `estimate()` is already shaped for. The snapshot is carried
|
|
106
|
+
* on the error rather than dropped, so nothing that was reachable before is lost
|
|
107
|
+
* — a caller that wants the raw reply reads `err.snapshot`.
|
|
108
|
+
*
|
|
109
|
+
* 🔴 WHERE THE REASON IS — READ `.snapshot.error`, NOT `message`. Recovering that
|
|
110
|
+
* string is the entire point of this class, so getting this the wrong way round
|
|
111
|
+
* reproduces the bug one layer down: a caller who logs `message` expecting the
|
|
112
|
+
* server's words gets a constant and is exactly as stuck as before.
|
|
113
|
+
*
|
|
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
|
|
132
|
+
* rejection prints and what a third-party block's error reporter ships
|
|
133
|
+
* upstream by default, and this is a package consumed by third-party code.
|
|
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}).
|
|
140
|
+
*
|
|
141
|
+
* 🔴 DO NOT "SIMPLIFY" THIS BY PUTTING `snapshot.error` BACK ON `message`. The
|
|
142
|
+
* two fields are split on purpose and the split is what keeps database internals
|
|
143
|
+
* off a third party's default-printed surface. `test/useBuzzWorkflow.test.tsx`
|
|
144
|
+
* pins it with a realistic `Unique constraint failed: Key (email)=(…)` fixture.
|
|
145
|
+
*
|
|
146
|
+
* 🔴 NOT APPLIED TO `submit`, and the reason is narrower than it looks. On
|
|
147
|
+
* `WORKFLOW_SUBMITTED` there are likewise TWO producers, indistinguishable by
|
|
148
|
+
* `status`: budget/cap REJECTIONS, which are a documented outcome the block
|
|
149
|
+
* recovers from (open a top-up flow) and which carry a `cost`; and caught server
|
|
150
|
+
* exceptions posted via the SAME `failureSnapshot(err)` used above, which do not.
|
|
151
|
+
* The `#4159` defect is therefore live on `submit` too, discriminated by `cost`
|
|
152
|
+
* presence rather than by `status`. Fixing it there is a separate change with its
|
|
153
|
+
* own blast radius on the recovery path (a fix MUST keep the budget-rejection arm
|
|
154
|
+
* RESOLVING, or the top-up flow breaks) and is deliberately NOT attempted here —
|
|
155
|
+
* so `submit` is left resolving BOTH, and the accompanying test pins only the
|
|
156
|
+
* budget-rejection producer, not a claim that submit is correct.
|
|
157
|
+
*
|
|
158
|
+
* 🔴 TRACKED, NOT FORGOTTEN: civitai/civitai-app-starters#251. Read it before
|
|
159
|
+
* touching `submit`'s failure handling.
|
|
160
|
+
*/
|
|
161
|
+
export declare class WorkflowEstimateError extends Error {
|
|
162
|
+
/**
|
|
163
|
+
* WHICH PRODUCER this was, structurally — so a caller branches on an enum
|
|
164
|
+
* rather than on prose. It is the ONLY stable branch target here:
|
|
165
|
+
* `snapshot.error` is server-authored and can change without notice, and
|
|
166
|
+
* {@link WorkflowEstimateError.message} is a generic developer-facing summary
|
|
167
|
+
* whose exact wording is not a contract either.
|
|
168
|
+
*
|
|
169
|
+
* 🔴 THIS IS WHAT A VIEWER-FACING MESSAGE MUST BE DERIVED FROM. Branch on
|
|
170
|
+
* `code` and return a string your app owns and localises; never render
|
|
171
|
+
* `message` or `snapshot.error` to a viewer.
|
|
172
|
+
*
|
|
173
|
+
* - `'failed'` — the reply's `status` is `'failed'`. USUALLY that means
|
|
174
|
+
* `blocks.estimateWorkflow` threw server-side and the host posted its
|
|
175
|
+
* `failureSnapshot(err)` (so `snapshot.error` carries the server's reason and
|
|
176
|
+
* there is no `cost`) — but it is NOT only that: a whatIf the orchestrator
|
|
177
|
+
* itself reports as failed maps to `'failed'` through the server's own
|
|
178
|
+
* `ORCH_STATUS_MAP`, and such a reply CAN carry a numeric `cost`. Both are
|
|
179
|
+
* rejected: a failed estimate is not a quote you may spend against, whether
|
|
180
|
+
* or not a number came back with it. Do not read this code as "the host
|
|
181
|
+
* threw" — read it as "the estimate did not succeed".
|
|
182
|
+
* - `'no-cost'` — a NON-failed reply with no numeric `cost.total`. There is
|
|
183
|
+
* usually no `snapshot.error` to explain it, which makes it the harder of the
|
|
184
|
+
* two to diagnose from the block side.
|
|
185
|
+
*/
|
|
186
|
+
readonly code: 'failed' | 'no-cost';
|
|
187
|
+
/**
|
|
188
|
+
* The snapshot the host replied with, VERBATIM — including `snapshot.error`,
|
|
189
|
+
* the server's own words, when there are any.
|
|
190
|
+
*
|
|
191
|
+
* 🔴 `snapshot.error` IS SERVER-AUTHORED AND UNSANITISED. It is where the
|
|
192
|
+
* reason lives (reading it is the documented way to diagnose an estimate that
|
|
193
|
+
* will not price — the whole point of civitai/civitai#4159), but civitai's
|
|
194
|
+
* `errorHandling.ts` documents that raw upstream text — Prisma/`pg` column and
|
|
195
|
+
* constraint names among it — can reach that field. Log it, show it in a
|
|
196
|
+
* developer-facing error surface, **NEVER render it verbatim into markup**,
|
|
197
|
+
* and do not ship it to a third-party error tracker without thinking about it.
|
|
198
|
+
*/
|
|
199
|
+
readonly snapshot: BlockWorkflowSnapshot;
|
|
200
|
+
constructor(snapshot: BlockWorkflowSnapshot, code: 'failed' | 'no-cost');
|
|
201
|
+
}
|
|
74
202
|
/** Optional per-submit controls. */
|
|
75
203
|
export interface SubmitWorkflowOptions {
|
|
76
204
|
/**
|
|
@@ -83,6 +211,28 @@ export interface SubmitWorkflowOptions {
|
|
|
83
211
|
idempotencyKey?: string;
|
|
84
212
|
}
|
|
85
213
|
interface UseBuzzWorkflowReturn {
|
|
214
|
+
/**
|
|
215
|
+
* Price a workflow without queueing it. Resolves ONLY with a snapshot that
|
|
216
|
+
* carries a numeric `cost.total`.
|
|
217
|
+
*
|
|
218
|
+
* 🔴 REJECTS with {@link WorkflowEstimateError} when the reply is unusable —
|
|
219
|
+
* the estimate errored server-side, or it came back with no numeric cost.
|
|
220
|
+
* Wrap every call in `try/catch`; see that class for why resolving such a
|
|
221
|
+
* reply was civitai/civitai#4159.
|
|
222
|
+
*
|
|
223
|
+
* 🔴 THIS INCLUDES MODERATOR REVIEW PREVIEW, a behaviour change worth knowing
|
|
224
|
+
* before you ship. While an app is under review the host short-circuits every
|
|
225
|
+
* workflow request with `failureSnapshot('not available in review preview')`,
|
|
226
|
+
* so `estimate()` now rejects there where it used to resolve. That is the
|
|
227
|
+
* correct reading — no estimate happened — and it is why the catch is not
|
|
228
|
+
* optional: a block without one turns the reviewer's first click into an
|
|
229
|
+
* unhandled rejection, at exactly the moment it is meant to look healthy. A
|
|
230
|
+
* block that catches shows the reviewer the reason instead.
|
|
231
|
+
*
|
|
232
|
+
* `result` is updated to the returned snapshot BEFORE any rejection, so a
|
|
233
|
+
* failed estimate can never leave a previous, differently-configured
|
|
234
|
+
* estimate's price sitting in `result` for a Confirm gate to read.
|
|
235
|
+
*/
|
|
86
236
|
estimate: (body: WorkflowBody) => Promise<BlockWorkflowSnapshot>;
|
|
87
237
|
submit: (body: WorkflowBody, options?: SubmitWorkflowOptions) => Promise<BlockWorkflowSnapshot>;
|
|
88
238
|
/**
|
|
@@ -178,7 +328,25 @@ interface UseBuzzWorkflowReturn {
|
|
|
178
328
|
* modelVersionId,
|
|
179
329
|
* params: { prompt: 'a cat' },
|
|
180
330
|
* };
|
|
181
|
-
*
|
|
331
|
+
* // A viewer-facing string YOUR APP owns, chosen by `code`. Neither
|
|
332
|
+
* // `err.message` (developer-facing, wording not a contract) nor
|
|
333
|
+
* // `err.snapshot.error` (server-authored, unsanitised) may be rendered.
|
|
334
|
+
* const estimateFailureMessage = (err: WorkflowEstimateError) =>
|
|
335
|
+
* err.code === 'no-cost'
|
|
336
|
+
* ? 'We could not get a price for this configuration. Try adjusting it.'
|
|
337
|
+
* : 'Pricing is unavailable right now. Please try again shortly.';
|
|
338
|
+
*
|
|
339
|
+
* // 🔴 estimate() REJECTS when the reply carries no usable price — an errored
|
|
340
|
+
* // estimate, or one that came back with no numeric cost. ALWAYS catch it.
|
|
341
|
+
* try {
|
|
342
|
+
* await estimate(body); // status 'estimating' → 'confirming' (cost in result.cost.total)
|
|
343
|
+
* } catch (err) {
|
|
344
|
+
* // status is now 'error'; `result` holds the unusable snapshot, never a
|
|
345
|
+
* // STALE priced one. See WorkflowEstimateError for the three-audience split.
|
|
346
|
+
* if (!(err instanceof WorkflowEstimateError)) throw err;
|
|
347
|
+
* logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
|
|
348
|
+
* showError(estimateFailureMessage(err)); // viewer-facing: app-owned
|
|
349
|
+
* }
|
|
182
350
|
* const snap = await submit(body); // status 'submitting' → 'polling'; returns a workflowId
|
|
183
351
|
* const done = await watch(snap.workflowId, { onUpdate: render }); // → terminal
|
|
184
352
|
*/
|
|
@@ -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,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,EAAE,CACN,IAAI,EAAE,YAAY,EAClB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;OAGG;IACH,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,EAAE,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACrC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAEpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;gBAE7B,QAAQ,EAAE,qBAAqB,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS;CAoBxE;AAED,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,qBAAqB;IAC7B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,EAAE,CACN,IAAI,EAAE,YAAY,EAClB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;OAGG;IACH,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,EAAE,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACrC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,wBAAgB,eAAe,IAAI,qBAAqB,CAkPvD"}
|
|
@@ -72,6 +72,153 @@ function sleep(ms, signal) {
|
|
|
72
72
|
signal?.addEventListener('abort', done);
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Thrown by {@link UseBuzzWorkflowReturn.estimate} when the host's reply does not
|
|
77
|
+
* carry a usable price — either because the estimate ERRORED, or because it came
|
|
78
|
+
* back without a numeric `cost.total`.
|
|
79
|
+
*
|
|
80
|
+
* 🔴 WHY THIS EXISTS — civitai/civitai#4159. A host-side `blocks.estimateWorkflow`
|
|
81
|
+
* throw is not propagated to the block as a rejection; it CANNOT be, because the
|
|
82
|
+
* reply crosses `postMessage`. The host instead posts a well-formed
|
|
83
|
+
* `ESTIMATE_RESULT` carrying its `failureSnapshot(err)` —
|
|
84
|
+
* `{ workflowId: 'failed', status: 'failed', error: '<server message>' }`, with
|
|
85
|
+
* **no `cost`** (see the host's `failureSnapshot.ts`, whose own comment states
|
|
86
|
+
* the block is expected to read `status`/`error`).
|
|
87
|
+
*
|
|
88
|
+
* Until this guard, `estimate()` resolved that snapshot as a SUCCESS and moved
|
|
89
|
+
* the hook to `'confirming'`. A correctly fail-closed block — one that gates
|
|
90
|
+
* Confirm on `typeof snapshot.cost?.total === 'number'` — then rendered a
|
|
91
|
+
* confirm dialog reading "Cost unavailable" with Confirm permanently disabled,
|
|
92
|
+
* and the server's own explanation (already present on `snapshot.error`) was
|
|
93
|
+
* discarded on the floor. The control was dead AND undiagnosable: a caller
|
|
94
|
+
* could not tell "priced at 0" from "the estimate errored".
|
|
95
|
+
*
|
|
96
|
+
* 🔴 TWO PRODUCERS, ONE OBSERVABLE — which is why {@link WorkflowEstimateError.code}
|
|
97
|
+
* exists and why the guard is not keyed on `status` alone. The server's
|
|
98
|
+
* `snapshotFromWorkflow` omits `cost` from an otherwise-SUCCESSFUL snapshot
|
|
99
|
+
* whenever the whatIf reply has no numeric total, so a `{status:'pending'}` reply
|
|
100
|
+
* with no `error` at all produces the identical dead control. A `status`-only
|
|
101
|
+
* guard would be INERT for that half. The two are distinguishable only by
|
|
102
|
+
* `snapshot.error` / `workflowId === 'failed'` — exactly the fields the original
|
|
103
|
+
* incident discarded, which is why it is still unknown which producer caused it.
|
|
104
|
+
*
|
|
105
|
+
* So an unusable estimate now REJECTS, which is what every caller's existing
|
|
106
|
+
* `try/catch` around `estimate()` is already shaped for. The snapshot is carried
|
|
107
|
+
* on the error rather than dropped, so nothing that was reachable before is lost
|
|
108
|
+
* — a caller that wants the raw reply reads `err.snapshot`.
|
|
109
|
+
*
|
|
110
|
+
* 🔴 WHERE THE REASON IS — READ `.snapshot.error`, NOT `message`. Recovering that
|
|
111
|
+
* string is the entire point of this class, so getting this the wrong way round
|
|
112
|
+
* reproduces the bug one layer down: a caller who logs `message` expecting the
|
|
113
|
+
* server's words gets a constant and is exactly as stuck as before.
|
|
114
|
+
*
|
|
115
|
+
* 🔴 THREE FIELDS, THREE AUDIENCES — AND **NONE OF THEM IS VIEWER-FACING COPY**.
|
|
116
|
+
* The viewer-facing string is one the APP owns; this error carries nothing you
|
|
117
|
+
* may render as-is. Two apps migrating to `0.43.0` each had a `catch` that piped
|
|
118
|
+
* `err.message` straight into rendered UI, and each would have shipped
|
|
119
|
+
* `estimate did not return a usable price (failed) — reason on .snapshot.error`
|
|
120
|
+
* to end users; both were caught only by a test asserting that exact string.
|
|
121
|
+
*
|
|
122
|
+
* - {@link WorkflowEstimateError.snapshot}`.error` — **the DIAGNOSTIC read.**
|
|
123
|
+
* The server's own words, verbatim. **Server-authored and UNSANITISED**:
|
|
124
|
+
* civitai's `errorHandling.ts` documents that raw upstream text —
|
|
125
|
+
* Prisma/`pg` column and constraint names among it — can reach this field.
|
|
126
|
+
* Log it, or show it in a developer-facing error surface. **NEVER render it
|
|
127
|
+
* verbatim into markup**, and think before shipping it to a third-party
|
|
128
|
+
* error tracker.
|
|
129
|
+
* - {@link WorkflowEstimateError.message} — **DEVELOPER-FACING.** A CONSTANT
|
|
130
|
+
* template with only `code` interpolated. It contains no server text and
|
|
131
|
+
* nothing to sanitise, so it is safe to LOG and safe to let a stack trace
|
|
132
|
+
* print — that is deliberate, because `message` is what an uncaught
|
|
133
|
+
* rejection prints and what a third-party block's error reporter ships
|
|
134
|
+
* upstream by default, and this is a package consumed by third-party code.
|
|
135
|
+
* **It is NOT intended for display to viewers**: it names an internal field
|
|
136
|
+
* path, it is not localised, and **its exact wording is NOT a contract** —
|
|
137
|
+
* it can change in any release, so a UI built on it silently rots.
|
|
138
|
+
* - {@link WorkflowEstimateError.code} — **the BRANCH TARGET**, and the only
|
|
139
|
+
* stable one. Switch on it to pick a viewer-facing string YOUR APP owns
|
|
140
|
+
* (see the mapper in the `@example` on {@link useBuzzWorkflow}).
|
|
141
|
+
*
|
|
142
|
+
* 🔴 DO NOT "SIMPLIFY" THIS BY PUTTING `snapshot.error` BACK ON `message`. The
|
|
143
|
+
* two fields are split on purpose and the split is what keeps database internals
|
|
144
|
+
* off a third party's default-printed surface. `test/useBuzzWorkflow.test.tsx`
|
|
145
|
+
* pins it with a realistic `Unique constraint failed: Key (email)=(…)` fixture.
|
|
146
|
+
*
|
|
147
|
+
* 🔴 NOT APPLIED TO `submit`, and the reason is narrower than it looks. On
|
|
148
|
+
* `WORKFLOW_SUBMITTED` there are likewise TWO producers, indistinguishable by
|
|
149
|
+
* `status`: budget/cap REJECTIONS, which are a documented outcome the block
|
|
150
|
+
* recovers from (open a top-up flow) and which carry a `cost`; and caught server
|
|
151
|
+
* exceptions posted via the SAME `failureSnapshot(err)` used above, which do not.
|
|
152
|
+
* The `#4159` defect is therefore live on `submit` too, discriminated by `cost`
|
|
153
|
+
* presence rather than by `status`. Fixing it there is a separate change with its
|
|
154
|
+
* own blast radius on the recovery path (a fix MUST keep the budget-rejection arm
|
|
155
|
+
* RESOLVING, or the top-up flow breaks) and is deliberately NOT attempted here —
|
|
156
|
+
* so `submit` is left resolving BOTH, and the accompanying test pins only the
|
|
157
|
+
* budget-rejection producer, not a claim that submit is correct.
|
|
158
|
+
*
|
|
159
|
+
* 🔴 TRACKED, NOT FORGOTTEN: civitai/civitai-app-starters#251. Read it before
|
|
160
|
+
* touching `submit`'s failure handling.
|
|
161
|
+
*/
|
|
162
|
+
export class WorkflowEstimateError extends Error {
|
|
163
|
+
/**
|
|
164
|
+
* WHICH PRODUCER this was, structurally — so a caller branches on an enum
|
|
165
|
+
* rather than on prose. It is the ONLY stable branch target here:
|
|
166
|
+
* `snapshot.error` is server-authored and can change without notice, and
|
|
167
|
+
* {@link WorkflowEstimateError.message} is a generic developer-facing summary
|
|
168
|
+
* whose exact wording is not a contract either.
|
|
169
|
+
*
|
|
170
|
+
* 🔴 THIS IS WHAT A VIEWER-FACING MESSAGE MUST BE DERIVED FROM. Branch on
|
|
171
|
+
* `code` and return a string your app owns and localises; never render
|
|
172
|
+
* `message` or `snapshot.error` to a viewer.
|
|
173
|
+
*
|
|
174
|
+
* - `'failed'` — the reply's `status` is `'failed'`. USUALLY that means
|
|
175
|
+
* `blocks.estimateWorkflow` threw server-side and the host posted its
|
|
176
|
+
* `failureSnapshot(err)` (so `snapshot.error` carries the server's reason and
|
|
177
|
+
* there is no `cost`) — but it is NOT only that: a whatIf the orchestrator
|
|
178
|
+
* itself reports as failed maps to `'failed'` through the server's own
|
|
179
|
+
* `ORCH_STATUS_MAP`, and such a reply CAN carry a numeric `cost`. Both are
|
|
180
|
+
* rejected: a failed estimate is not a quote you may spend against, whether
|
|
181
|
+
* or not a number came back with it. Do not read this code as "the host
|
|
182
|
+
* threw" — read it as "the estimate did not succeed".
|
|
183
|
+
* - `'no-cost'` — a NON-failed reply with no numeric `cost.total`. There is
|
|
184
|
+
* usually no `snapshot.error` to explain it, which makes it the harder of the
|
|
185
|
+
* two to diagnose from the block side.
|
|
186
|
+
*/
|
|
187
|
+
code;
|
|
188
|
+
/**
|
|
189
|
+
* The snapshot the host replied with, VERBATIM — including `snapshot.error`,
|
|
190
|
+
* the server's own words, when there are any.
|
|
191
|
+
*
|
|
192
|
+
* 🔴 `snapshot.error` IS SERVER-AUTHORED AND UNSANITISED. It is where the
|
|
193
|
+
* reason lives (reading it is the documented way to diagnose an estimate that
|
|
194
|
+
* will not price — the whole point of civitai/civitai#4159), but civitai's
|
|
195
|
+
* `errorHandling.ts` documents that raw upstream text — Prisma/`pg` column and
|
|
196
|
+
* constraint names among it — can reach that field. Log it, show it in a
|
|
197
|
+
* developer-facing error surface, **NEVER render it verbatim into markup**,
|
|
198
|
+
* and do not ship it to a third-party error tracker without thinking about it.
|
|
199
|
+
*/
|
|
200
|
+
snapshot;
|
|
201
|
+
constructor(snapshot, code) {
|
|
202
|
+
// 🔴 GENERIC, AND DELIBERATELY NOT `snapshot.error`. `message` is the field an
|
|
203
|
+
// uncaught rejection PRINTS and the field a third-party block's error
|
|
204
|
+
// reporter ships upstream by default — so putting the raw server string here
|
|
205
|
+
// would put database internals (constraint names, `Key (email)=(…) already
|
|
206
|
+
// exists.`) on the default-printed surface of somebody else's code. Exposure
|
|
207
|
+
// to the block is identical either way (`snapshot.error` was always on the
|
|
208
|
+
// wire), so routing the raw text to `.snapshot.error` alone costs a
|
|
209
|
+
// deliberate debugger nothing and changes only the DISPOSITION. `code` is
|
|
210
|
+
// carried IN the message so an uncaught rejection is still self-describing.
|
|
211
|
+
//
|
|
212
|
+
// 🔴 IT IS DEVELOPER-FACING, NOT VIEWER-FACING — it names an internal field
|
|
213
|
+
// path and is not localised, so it must not reach rendered UI. Its exact
|
|
214
|
+
// wording is NOT a contract: it may change in any release. A viewer-facing
|
|
215
|
+
// string is derived from `code` by the app (see the class docs).
|
|
216
|
+
super(`estimate did not return a usable price (${code}) — reason on .snapshot.error`);
|
|
217
|
+
this.name = 'WorkflowEstimateError';
|
|
218
|
+
this.code = code;
|
|
219
|
+
this.snapshot = snapshot;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
75
222
|
/**
|
|
76
223
|
* Orchestrates the estimate → confirm → submit → poll dance through the
|
|
77
224
|
* host-mediated `postMessage` path.
|
|
@@ -122,7 +269,25 @@ function sleep(ms, signal) {
|
|
|
122
269
|
* modelVersionId,
|
|
123
270
|
* params: { prompt: 'a cat' },
|
|
124
271
|
* };
|
|
125
|
-
*
|
|
272
|
+
* // A viewer-facing string YOUR APP owns, chosen by `code`. Neither
|
|
273
|
+
* // `err.message` (developer-facing, wording not a contract) nor
|
|
274
|
+
* // `err.snapshot.error` (server-authored, unsanitised) may be rendered.
|
|
275
|
+
* const estimateFailureMessage = (err: WorkflowEstimateError) =>
|
|
276
|
+
* err.code === 'no-cost'
|
|
277
|
+
* ? 'We could not get a price for this configuration. Try adjusting it.'
|
|
278
|
+
* : 'Pricing is unavailable right now. Please try again shortly.';
|
|
279
|
+
*
|
|
280
|
+
* // 🔴 estimate() REJECTS when the reply carries no usable price — an errored
|
|
281
|
+
* // estimate, or one that came back with no numeric cost. ALWAYS catch it.
|
|
282
|
+
* try {
|
|
283
|
+
* await estimate(body); // status 'estimating' → 'confirming' (cost in result.cost.total)
|
|
284
|
+
* } catch (err) {
|
|
285
|
+
* // status is now 'error'; `result` holds the unusable snapshot, never a
|
|
286
|
+
* // STALE priced one. See WorkflowEstimateError for the three-audience split.
|
|
287
|
+
* if (!(err instanceof WorkflowEstimateError)) throw err;
|
|
288
|
+
* logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
|
|
289
|
+
* showError(estimateFailureMessage(err)); // viewer-facing: app-owned
|
|
290
|
+
* }
|
|
126
291
|
* const snap = await submit(body); // status 'submitting' → 'polling'; returns a workflowId
|
|
127
292
|
* const done = await watch(snap.workflowId, { onUpdate: render }); // → terminal
|
|
128
293
|
*/
|
|
@@ -135,7 +300,42 @@ export function useBuzzWorkflow() {
|
|
|
135
300
|
setStatus('estimating');
|
|
136
301
|
try {
|
|
137
302
|
const { snapshot } = await sendTypedRequest(getTransport(), { type: 'ESTIMATE_WORKFLOW', payload: { body } }, 'ESTIMATE_RESULT', { timeoutMs: WORKFLOW_REQUEST_TIMEOUT_MS });
|
|
303
|
+
// 🔴 PUBLISH THE SNAPSHOT BEFORE ANY REJECTION BELOW. `result` is what the
|
|
304
|
+
// README documents as where the cost lives, so a block may gate its Confirm
|
|
305
|
+
// on `typeof result.cost?.total === 'number'` rather than on the returned
|
|
306
|
+
// value. If the throw jumped over this line, a FAILED estimate would leave
|
|
307
|
+
// the PREVIOUS estimate's snapshot in place and that gate would read the
|
|
308
|
+
// OLD config's price — a live control quoting the wrong number on a money
|
|
309
|
+
// path, which is strictly worse than the dead control this PR exists to
|
|
310
|
+
// fix. Assigning first preserves the pre-fix fail-CLOSED property (the
|
|
311
|
+
// cost-less snapshot overwrites the priced one) and merely adds the
|
|
312
|
+
// rejection on top.
|
|
138
313
|
setResult(snapshot);
|
|
314
|
+
// 🔴 AN UNUSABLE ESTIMATE MUST REJECT — and there are TWO producers of the
|
|
315
|
+
// identical observable "resolved, but no `cost.total`", distinguishable
|
|
316
|
+
// only by fields the incident in civitai/civitai#4159 discarded:
|
|
317
|
+
//
|
|
318
|
+
// (a) status:'failed' — `blocks.estimateWorkflow` threw server-side. The
|
|
319
|
+
// host cannot reject across postMessage, so it posts
|
|
320
|
+
// `failureSnapshot(err)`: a VALID snapshot with `status:'failed'`,
|
|
321
|
+
// an `error` string, and no `cost`.
|
|
322
|
+
// (b) no numeric cost on an otherwise-successful snapshot — the server's
|
|
323
|
+
// `snapshotFromWorkflow` OMITS `cost` entirely when the whatIf reply
|
|
324
|
+
// has no numeric total (`...(typeof total === 'number' ? … : {})`),
|
|
325
|
+
// which yields e.g. `{status:'pending'}` with no `error` at all.
|
|
326
|
+
//
|
|
327
|
+
// Keying on `status` ALONE would leave (b) resolving — the same dead
|
|
328
|
+
// "Cost unavailable" control, still undiagnosable. So the rule is the one
|
|
329
|
+
// the caller actually needs: an estimate that did not yield a usable price
|
|
330
|
+
// does not resolve. `canceled`/`expired` are covered by the same clause
|
|
331
|
+
// (they carry no cost), which is why they need no arm of their own.
|
|
332
|
+
const usableCost = typeof snapshot.cost?.total === 'number';
|
|
333
|
+
if (snapshot.status === 'failed') {
|
|
334
|
+
throw new WorkflowEstimateError(snapshot, 'failed');
|
|
335
|
+
}
|
|
336
|
+
if (!usableCost) {
|
|
337
|
+
throw new WorkflowEstimateError(snapshot, 'no-cost');
|
|
338
|
+
}
|
|
139
339
|
setStatus('confirming');
|
|
140
340
|
return snapshot;
|
|
141
341
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAI9C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEpF;;;;GAIG;AACH,MAAM,iBAAiB,GAAiD,IAAI,GAAG,CAAC;IAC9E,WAAW;IACX,QAAQ;IACR,UAAU;IACV,SAAS;CACV,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,MAAM,2BAA2B,GAAG,OAAO,CAAC;AAE5C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C,qFAAqF;AACrF,MAAM,yBAAyB,GAAG,KAAK,CAAC;AAExC,6EAA6E;AAC7E,MAAM,wBAAwB,GAAG,EAAE,GAAG,MAAM,CAAC;AAE7C,gFAAgF;AAChF,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAwDpC;;;;;;;;;GASG;AACH,SAAS,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC7C,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC3C,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;
|
|
1
|
+
{"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import type { BlockCheckpointInfo } from '@civitai/app-sdk/blocks';
|
|
2
|
-
/**
|
|
3
|
-
* A resource picker is HUMAN-interactive — the user browses the catalog and may
|
|
4
|
-
* take a while to choose. The default request timeout (~30s) is for fast
|
|
5
|
-
* protocol round-trips and would reject a slow browse mid-pick. Use a generous
|
|
6
|
-
* bound instead; the host still resolves earlier on pick/dismiss/close.
|
|
7
|
-
*/
|
|
8
|
-
export declare const PICKER_REQUEST_TIMEOUT_MS: number;
|
|
9
2
|
/**
|
|
10
3
|
* Drives the platform-side Checkpoint picker and the persist-override flow.
|
|
11
4
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckpointPicker.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckpointPicker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"useCheckpointPicker.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckpointPicker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAMnE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,IAAI;IACrC,IAAI,EAAE,CAAC,IAAI,EAAE;QACX;;;;;WAKG;QACH,cAAc,EAAE,MAAM,CAAC;QACvB,uEAAuE;QACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,KAAK,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IAClD,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAkCA"}
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
+
import { HUMAN_INTERACTION_TIMEOUT_MS } from '../internal/requestTimeouts.js';
|
|
2
3
|
import { getTransport } from '../internal/singleton.js';
|
|
3
4
|
import { sendTypedRequest } from '../internal/transport.js';
|
|
4
|
-
/**
|
|
5
|
-
* A resource picker is HUMAN-interactive — the user browses the catalog and may
|
|
6
|
-
* take a while to choose. The default request timeout (~30s) is for fast
|
|
7
|
-
* protocol round-trips and would reject a slow browse mid-pick. Use a generous
|
|
8
|
-
* bound instead; the host still resolves earlier on pick/dismiss/close.
|
|
9
|
-
*/
|
|
10
|
-
export const PICKER_REQUEST_TIMEOUT_MS = 10 * 60_000;
|
|
11
5
|
/**
|
|
12
6
|
* Drives the platform-side Checkpoint picker and the persist-override flow.
|
|
13
7
|
*
|
|
@@ -39,7 +33,7 @@ export function useCheckpointPicker() {
|
|
|
39
33
|
? { currentVersionId: opts.currentVersionId }
|
|
40
34
|
: {}),
|
|
41
35
|
},
|
|
42
|
-
}, 'CHECKPOINT_PICKER_RESULT', { timeoutMs:
|
|
36
|
+
}, 'CHECKPOINT_PICKER_RESULT', { timeoutMs: HUMAN_INTERACTION_TIMEOUT_MS });
|
|
43
37
|
return { selected };
|
|
44
38
|
}, []);
|
|
45
39
|
const persist = useCallback(async (versionId) => {
|