@civitai/blocks-react 0.71.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.
- package/README.md +262 -15
- package/dist/hooks/useBatchEstimate.d.ts +4 -0
- package/dist/hooks/useBatchEstimate.js +4 -0
- package/dist/hooks/useBuzzWorkflow.d.ts +35 -3
- package/dist/hooks/useBuzzWorkflow.js +30 -3
- package/dist/hooks/useCheckpointPicker.d.ts +2 -0
- package/dist/hooks/useCheckpointPicker.js +2 -0
- package/dist/hooks/useRunConfirmedStep.d.ts +209 -0
- package/dist/hooks/useRunConfirmedStep.js +287 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal/hostSurface.d.ts +37 -0
- package/dist/internal/hostSurface.js +38 -0
- package/dist/internal/liveHost.d.ts +15 -6
- package/dist/internal/liveHost.js +151 -69
- package/dist/internal/mockHost.d.ts +123 -10
- package/dist/internal/mockHost.js +377 -14
- package/dist/internal/mockWorkflowGraph.d.ts +23 -10
- package/dist/internal/mockWorkflowGraph.js +34 -11
- package/dist/testing.d.ts +1 -1
- package/dist/transport/requestTimeouts.js +6 -0
- package/dist/transport/validate.d.ts +13 -0
- package/dist/transport/validate.js +76 -0
- package/package.json +4 -4
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { BlockRunConfirmedStepHostError, BlockStepQuote, BlockWorkflowSnapshot, WorkflowBodyPassThroughStep } from '@civitai/app-sdk/blocks';
|
|
2
|
+
export type { BlockRunConfirmedStepHostError, BlockStepQuote, WorkflowBodyPassThroughStep };
|
|
3
|
+
/**
|
|
4
|
+
* A pass-through step body ready to RUN as a confirmed job:
|
|
5
|
+
* {@link WorkflowBodyPassThroughStep} with the `quoteId` its estimate returned
|
|
6
|
+
* (`snapshot.stepQuote.quoteId`). Required here so a body without one is a
|
|
7
|
+
* compile error rather than a refusal.
|
|
8
|
+
*/
|
|
9
|
+
export type QuotedStepBody = WorkflowBodyPassThroughStep & {
|
|
10
|
+
quoteId: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The host's CLOSED refusal codes for `RUN_CONFIRMED_STEP`, as a runtime list.
|
|
14
|
+
* Mirrors civitai/civitai's `RUN_CONFIRMED_STEP_HOST_ERRORS`.
|
|
15
|
+
*
|
|
16
|
+
* 🔴 THE ERROR CHANNEL IS NOT AN ENUM: server messages (an expired or used quote,
|
|
17
|
+
* a body or price that differs from the quoted one) arrive in the same field —
|
|
18
|
+
* see {@link isRunConfirmedStepErrorCode}.
|
|
19
|
+
*/
|
|
20
|
+
export declare const RUN_CONFIRMED_STEP_ERROR_CODES: readonly ["review-mode", "block is not ready", "sign in to run", "invalid confirmed step request", "no block token", "declined", "daily-limit-exceeded", "submission-unconfirmed"];
|
|
21
|
+
/**
|
|
22
|
+
* The shape of a confirmed-step quote id: `sq_` + 32 lowercase hex characters.
|
|
23
|
+
*
|
|
24
|
+
* A COPY of `@civitai/app-sdk/blocks`'s `BLOCK_STEP_QUOTE_ID_REGEX`, deliberately
|
|
25
|
+
* not value-imported: a value import would raise this package's
|
|
26
|
+
* `@civitai/app-sdk` peer floor for one regular expression (see
|
|
27
|
+
* `PEER_FLOOR.md`). Pinned to the SDK's by `test/confirmedStepFlow.test.tsx`,
|
|
28
|
+
* which imports both.
|
|
29
|
+
*/
|
|
30
|
+
export declare const STEP_QUOTE_ID_PATTERN: RegExp;
|
|
31
|
+
/** `true` when `value` is a string shaped like a confirmed-step quote id. */
|
|
32
|
+
export declare function isStepQuoteId(value: unknown): value is string;
|
|
33
|
+
/**
|
|
34
|
+
* The message of the {@link RunConfirmedStepError} `runConfirmedStep` rejects
|
|
35
|
+
* with when the body carries no `sq_`-shaped `quoteId`.
|
|
36
|
+
*
|
|
37
|
+
* 🔴 REFUSED BEFORE THE SEND. The host refuses the same body with `invalid
|
|
38
|
+
* confirmed step request`, so nothing is lost by refusing here — and one thing is
|
|
39
|
+
* gained: a `tq_` training quote, an `undefined` from an estimate that returned
|
|
40
|
+
* no `stepQuote`, or a stale id from storage is named at the call site instead of
|
|
41
|
+
* surfacing as a host refusal. Nothing was sent, so nothing was charged.
|
|
42
|
+
*/
|
|
43
|
+
export declare const CONFIRMED_STEP_QUOTE_ID_REFUSAL = "runConfirmedStep() needs the quoteId its estimate returned (snapshot.stepQuote.quoteId, \"sq_\" + 32 hex characters) \u2014 estimate the body with useBuzzWorkflow().estimate() first. Nothing was sent.";
|
|
44
|
+
/**
|
|
45
|
+
* The `code` a {@link RunConfirmedStepError} can carry: one of the host's closed
|
|
46
|
+
* refusal codes, or `'refused'` — assigned by THIS SDK, never sent by the host,
|
|
47
|
+
* for a submit the server refused at a spend cap or as temporarily unavailable
|
|
48
|
+
* (see {@link RunConfirmedStepError.refused}).
|
|
49
|
+
*/
|
|
50
|
+
export type RunConfirmedStepErrorCode = BlockRunConfirmedStepHostError | 'refused';
|
|
51
|
+
/** `true` when `error` is one of the host's closed refusal codes rather than a server message. */
|
|
52
|
+
export declare function isRunConfirmedStepErrorCode(error: string): error is BlockRunConfirmedStepHostError;
|
|
53
|
+
/**
|
|
54
|
+
* A confirmed step that did not come back with a snapshot.
|
|
55
|
+
*
|
|
56
|
+
* 🔴 READ THE FLAGS BEFORE SAYING ANYTHING ABOUT MONEY — they are the only part of
|
|
57
|
+
* this error with a money meaning, and they point in opposite directions:
|
|
58
|
+
*
|
|
59
|
+
* - `.declined` — the viewer dismissed the confirm. NO JOB WAS SUBMITTED,
|
|
60
|
+
* guaranteed by the host's consent latch, and the quote is still runnable
|
|
61
|
+
* until it expires. Render nothing.
|
|
62
|
+
* - `.dailyLimitExceeded` — the job would exceed what remains today of the
|
|
63
|
+
* daily Buzz limit the VIEWER set for this app. NO JOB, nothing charged. The
|
|
64
|
+
* host has already explained it to the viewer and pointed at where the limit
|
|
65
|
+
* is raised; a confirm cannot override it. Do not retry, and do not offer a
|
|
66
|
+
* Buzz purchase — more Buzz does not lift a limit.
|
|
67
|
+
* - `.unconfirmed` — the job MAY BE RUNNING AND CHARGED: the host sent the
|
|
68
|
+
* submit and could not learn its outcome (`submission-unconfirmed`), or no
|
|
69
|
+
* reply arrived at all (`.timedOut` — the viewer may have confirmed a dialog
|
|
70
|
+
* this block had stopped waiting for). 🔴 NEVER RETRY AUTOMATICALLY and do not
|
|
71
|
+
* tell the viewer it failed. Every confirmation is its own job, so a retry —
|
|
72
|
+
* which needs a new estimate and a new confirmation — is a SECOND, separately
|
|
73
|
+
* charged job if the first one did start. Check the viewer's generations
|
|
74
|
+
* first (`useAppWorkflows()` lists this app's runs).
|
|
75
|
+
* - `.refused` (`code: 'refused'`) — the server REFUSED the submit at a spend
|
|
76
|
+
* cap after the viewer confirmed (the viewer's daily or private-run Buzz cap,
|
|
77
|
+
* the app's daily spend or rate limit). Its reservation was refunded: NO JOB,
|
|
78
|
+
* nothing charged. `.message` is the server's reason and `.snapshot` the
|
|
79
|
+
* refusal it arrived in. Retrying needs a new estimate and usually a later
|
|
80
|
+
* time.
|
|
81
|
+
* - `.signInRequired` — no session. Route into `useRequestSignIn()`.
|
|
82
|
+
* - anything else — a refusal before any submit (an expired or used quote, a
|
|
83
|
+
* body that no longer matches its quote, a price that changed, or the
|
|
84
|
+
* client-side {@link CONFIRMED_STEP_QUOTE_ID_REFUSAL}). Estimate again before
|
|
85
|
+
* retrying; `.message` is the host's or server's text.
|
|
86
|
+
*/
|
|
87
|
+
export declare class RunConfirmedStepError extends Error {
|
|
88
|
+
/**
|
|
89
|
+
* The closed host refusal code, `'refused'` for a cap refusal, or `undefined`
|
|
90
|
+
* for a server/transport error.
|
|
91
|
+
*/
|
|
92
|
+
readonly code?: RunConfirmedStepErrorCode;
|
|
93
|
+
/**
|
|
94
|
+
* The server refused the submit (a spend cap, or a temporary-availability deny)
|
|
95
|
+
* and refunded it — no job exists. The quote is used up: estimate again.
|
|
96
|
+
* See the class docs; `.message` is the server's reason.
|
|
97
|
+
*/
|
|
98
|
+
readonly refused: boolean;
|
|
99
|
+
/** The refusal snapshot the host replied with, when {@link refused}. */
|
|
100
|
+
readonly snapshot?: BlockWorkflowSnapshot;
|
|
101
|
+
/** The viewer dismissed the confirm — no job was submitted. */
|
|
102
|
+
readonly declined: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The job would exceed the viewer's own daily Buzz limit for this app — no job
|
|
105
|
+
* was submitted and nothing was charged. Set for the host code
|
|
106
|
+
* `daily-limit-exceeded` and for a refusal snapshot whose `errorCode` is that
|
|
107
|
+
* value.
|
|
108
|
+
*/
|
|
109
|
+
readonly dailyLimitExceeded: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The job may exist and may have been charged — `submission-unconfirmed`, or a
|
|
112
|
+
* transport timeout. Check the viewer's generations before retrying.
|
|
113
|
+
*/
|
|
114
|
+
readonly unconfirmed: boolean;
|
|
115
|
+
/** No reply arrived within the 10-minute consent bound. Implies {@link unconfirmed}. */
|
|
116
|
+
readonly timedOut: boolean;
|
|
117
|
+
/** There is no session (`sign in to run`, or a bare `UNAUTHORIZED`). */
|
|
118
|
+
readonly signInRequired: boolean;
|
|
119
|
+
constructor(error: string, opts?: {
|
|
120
|
+
timedOut?: boolean;
|
|
121
|
+
refusedSnapshot?: BlockWorkflowSnapshot;
|
|
122
|
+
code?: BlockRunConfirmedStepHostError;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** What {@link useRunConfirmedStep} returns. */
|
|
126
|
+
export interface UseRunConfirmedStep {
|
|
127
|
+
/**
|
|
128
|
+
* Ask the host to run a quoted pass-through step. Civitai shows the viewer a
|
|
129
|
+
* confirm with the server's price; on their click it submits, and this
|
|
130
|
+
* resolves with the submitted workflow's snapshot — poll it with
|
|
131
|
+
* `useBuzzWorkflow().watch(snapshot.workflowId)`.
|
|
132
|
+
*
|
|
133
|
+
* REJECTS with a {@link RunConfirmedStepError} — including `declined` and
|
|
134
|
+
* `dailyLimitExceeded` (no job), `refused` (a spend-cap refusal; no job) and
|
|
135
|
+
* `unconfirmed` (a job may exist). Read the flags before rendering anything. A
|
|
136
|
+
* resolved snapshot is always a submitted job with a pollable `workflowId`.
|
|
137
|
+
*
|
|
138
|
+
* A body whose `quoteId` is missing or not `sq_`-shaped is refused HERE,
|
|
139
|
+
* before anything is sent — see {@link CONFIRMED_STEP_QUOTE_ID_REFUSAL}.
|
|
140
|
+
*/
|
|
141
|
+
runConfirmedStep: (body: QuotedStepBody) => Promise<BlockWorkflowSnapshot>;
|
|
142
|
+
/** `true` while a request is in flight (including the viewer's dialog). */
|
|
143
|
+
pending: boolean;
|
|
144
|
+
/** The last request's failure, or `null`. Cleared at the start of the next call. */
|
|
145
|
+
error: RunConfirmedStepError | null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Run ONE pass-through `kind: 'step'` job whose price is above what the app may
|
|
149
|
+
* spend without asking — through the host-mediated `RUN_CONFIRMED_STEP` →
|
|
150
|
+
* `CONFIRMED_STEP_RESULT` bridge, after the viewer confirms the exact price in
|
|
151
|
+
* Civitai's own dialog.
|
|
152
|
+
*
|
|
153
|
+
* 🔴 AVAILABILITY: page apps only, behind the host flag
|
|
154
|
+
* `app-blocks-confirmed-step` (ships off), with `ai:write:budgeted` declared and
|
|
155
|
+
* granted by a signed-in viewer, on a host carrying civitai/civitai#5786. Refused
|
|
156
|
+
* from `dev:live` and from review sessions — build the flow against the mock
|
|
157
|
+
* host. Where it is unavailable the estimate simply carries no `stepQuote`.
|
|
158
|
+
*
|
|
159
|
+
* The flow: `useBuzzWorkflow().estimate(body)` (its `stepQuote.quoteId`) →
|
|
160
|
+
* `runConfirmedStep({ ...body, quoteId })` → `useBuzzWorkflow().watch(workflowId)`.
|
|
161
|
+
* Change nothing in the body between the estimate and the run: the server checks
|
|
162
|
+
* it against the quoted one.
|
|
163
|
+
*
|
|
164
|
+
* 🔴 ONLY A FIXED PRICE IS CONFIRMABLE. `stepQuote` is present when the
|
|
165
|
+
* orchestrator quoted a fixed price above the token's per-call budget and at
|
|
166
|
+
* most `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB` (5,000 Buzz). A variable or
|
|
167
|
+
* metered price, or no price at all, gets no quote and cannot be run this way.
|
|
168
|
+
*
|
|
169
|
+
* 🔴 ESTIMATE THE BODY ON ITS OWN. A batch estimate (`useBatchEstimate()`) never
|
|
170
|
+
* carries a `stepQuote`. And where a quote would be granted, the estimate
|
|
171
|
+
* REJECTS instead for a training `$type` (`training` / `imageResourceTraining`,
|
|
172
|
+
* any casing — use `kind: 'training'`) and for an `input` that is not plain JSON
|
|
173
|
+
* (a `Date`, `Map`, `Set`, `BigInt`, `NaN`, an empty slot in a sparse array, …). Both are server messages with no code; see
|
|
174
|
+
* `BlockWorkflowSnapshot.stepQuote` for the exact text.
|
|
175
|
+
*
|
|
176
|
+
* 🔴 THE CONFIRM IS CIVITAI'S, AND IT IS THE CHARGE. The price on it is read back
|
|
177
|
+
* from the server's stored quote — never from your body. A quote is single-use
|
|
178
|
+
* and expires 15 minutes after the estimate; the server prices the job again at
|
|
179
|
+
* submit and refuses a higher price.
|
|
180
|
+
*
|
|
181
|
+
* 🔴 NO AUTOMATIC RETRY OF ANY KIND, on purpose. Every confirmation is its own
|
|
182
|
+
* job — there is no idempotency key, the orchestrator id comes from the confirmed
|
|
183
|
+
* quote — and one outcome (`submission-unconfirmed`) means a job may already be
|
|
184
|
+
* running. So this hook never re-sends, and neither should you: a retry is a
|
|
185
|
+
* NEW, separately charged job.
|
|
186
|
+
*
|
|
187
|
+
* Sent under the 10-minute human-interaction bound: the host replies only when
|
|
188
|
+
* the viewer clicks or dismisses its dialog.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* const { estimate, watch } = useBuzzWorkflow();
|
|
192
|
+
* const { runConfirmedStep } = useRunConfirmedStep();
|
|
193
|
+
* const quote = (await estimate(body)).stepQuote;
|
|
194
|
+
* if (!quote) return submit(body); // within budget — the ordinary path
|
|
195
|
+
* try {
|
|
196
|
+
* const started = await runConfirmedStep({ ...body, quoteId: quote.quoteId });
|
|
197
|
+
* await watch(started.workflowId, { onUpdate: render });
|
|
198
|
+
* } catch (e) {
|
|
199
|
+
* if (!(e instanceof RunConfirmedStepError)) throw e;
|
|
200
|
+
* if (e.declined) return; // no job — say nothing
|
|
201
|
+
* if (e.dailyLimitExceeded) return; // no job — Civitai already told the viewer
|
|
202
|
+
* if (e.signInRequired) return requestSignIn();
|
|
203
|
+
* if (e.unconfirmed) return showCheckYourGenerations(); // may be running — never auto-retry
|
|
204
|
+
* if (e.refused) return showError(e.message); // a cap refusal; no job, nothing charged
|
|
205
|
+
* showError('Could not start this job. Get a new price and try again.');
|
|
206
|
+
* }
|
|
207
|
+
*/
|
|
208
|
+
export declare function useRunConfirmedStep(): UseRunConfirmedStep;
|
|
209
|
+
//# sourceMappingURL=useRunConfirmedStep.d.ts.map
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { HUMAN_INTERACTION_TIMEOUT_MS } from '../transport/requestTimeouts.js';
|
|
3
|
+
import { getTransport } from '../transport/singleton.js';
|
|
4
|
+
import { RequestTimeoutError, sendTypedRequest } from '../transport/transport.js';
|
|
5
|
+
/**
|
|
6
|
+
* The host's CLOSED refusal codes for `RUN_CONFIRMED_STEP`, as a runtime list.
|
|
7
|
+
* Mirrors civitai/civitai's `RUN_CONFIRMED_STEP_HOST_ERRORS`.
|
|
8
|
+
*
|
|
9
|
+
* 🔴 THE ERROR CHANNEL IS NOT AN ENUM: server messages (an expired or used quote,
|
|
10
|
+
* a body or price that differs from the quoted one) arrive in the same field —
|
|
11
|
+
* see {@link isRunConfirmedStepErrorCode}.
|
|
12
|
+
*/
|
|
13
|
+
export const RUN_CONFIRMED_STEP_ERROR_CODES = [
|
|
14
|
+
'review-mode',
|
|
15
|
+
'block is not ready',
|
|
16
|
+
'sign in to run',
|
|
17
|
+
'invalid confirmed step request',
|
|
18
|
+
'no block token',
|
|
19
|
+
'declined',
|
|
20
|
+
'daily-limit-exceeded',
|
|
21
|
+
'submission-unconfirmed',
|
|
22
|
+
];
|
|
23
|
+
const CODE_SET = new Set(RUN_CONFIRMED_STEP_ERROR_CODES);
|
|
24
|
+
/**
|
|
25
|
+
* The shape of a confirmed-step quote id: `sq_` + 32 lowercase hex characters.
|
|
26
|
+
*
|
|
27
|
+
* A COPY of `@civitai/app-sdk/blocks`'s `BLOCK_STEP_QUOTE_ID_REGEX`, deliberately
|
|
28
|
+
* not value-imported: a value import would raise this package's
|
|
29
|
+
* `@civitai/app-sdk` peer floor for one regular expression (see
|
|
30
|
+
* `PEER_FLOOR.md`). Pinned to the SDK's by `test/confirmedStepFlow.test.tsx`,
|
|
31
|
+
* which imports both.
|
|
32
|
+
*/
|
|
33
|
+
export const STEP_QUOTE_ID_PATTERN = /^sq_[a-f0-9]{32}$/;
|
|
34
|
+
/** `true` when `value` is a string shaped like a confirmed-step quote id. */
|
|
35
|
+
export function isStepQuoteId(value) {
|
|
36
|
+
return typeof value === 'string' && STEP_QUOTE_ID_PATTERN.test(value);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The message of the {@link RunConfirmedStepError} `runConfirmedStep` rejects
|
|
40
|
+
* with when the body carries no `sq_`-shaped `quoteId`.
|
|
41
|
+
*
|
|
42
|
+
* 🔴 REFUSED BEFORE THE SEND. The host refuses the same body with `invalid
|
|
43
|
+
* confirmed step request`, so nothing is lost by refusing here — and one thing is
|
|
44
|
+
* gained: a `tq_` training quote, an `undefined` from an estimate that returned
|
|
45
|
+
* no `stepQuote`, or a stale id from storage is named at the call site instead of
|
|
46
|
+
* surfacing as a host refusal. Nothing was sent, so nothing was charged.
|
|
47
|
+
*/
|
|
48
|
+
export const CONFIRMED_STEP_QUOTE_ID_REFUSAL = 'runConfirmedStep() needs the quoteId its estimate returned (snapshot.stepQuote.quoteId, "sq_" + 32 hex characters) — estimate the body with useBuzzWorkflow().estimate() first. Nothing was sent.';
|
|
49
|
+
/**
|
|
50
|
+
* The `workflowId` the host's confirmed submit stamps on a snapshot that is NOT
|
|
51
|
+
* a run — its cap refusals resolve `{ workflowId: 'failed', status: 'failed',
|
|
52
|
+
* cost, error }`, the same shape the training submit uses. Compared with `===`:
|
|
53
|
+
* a real orchestrator id is a workflow that may exist.
|
|
54
|
+
*/
|
|
55
|
+
const HOST_REFUSAL_WORKFLOW_ID = 'failed';
|
|
56
|
+
/** The viewer's own daily-limit refusal, as a host code and as a snapshot `errorCode`. */
|
|
57
|
+
const DAILY_LIMIT_CODE = 'daily-limit-exceeded';
|
|
58
|
+
/** `true` when `error` is one of the host's closed refusal codes rather than a server message. */
|
|
59
|
+
export function isRunConfirmedStepErrorCode(error) {
|
|
60
|
+
return CODE_SET.has(error);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A confirmed step that did not come back with a snapshot.
|
|
64
|
+
*
|
|
65
|
+
* 🔴 READ THE FLAGS BEFORE SAYING ANYTHING ABOUT MONEY — they are the only part of
|
|
66
|
+
* this error with a money meaning, and they point in opposite directions:
|
|
67
|
+
*
|
|
68
|
+
* - `.declined` — the viewer dismissed the confirm. NO JOB WAS SUBMITTED,
|
|
69
|
+
* guaranteed by the host's consent latch, and the quote is still runnable
|
|
70
|
+
* until it expires. Render nothing.
|
|
71
|
+
* - `.dailyLimitExceeded` — the job would exceed what remains today of the
|
|
72
|
+
* daily Buzz limit the VIEWER set for this app. NO JOB, nothing charged. The
|
|
73
|
+
* host has already explained it to the viewer and pointed at where the limit
|
|
74
|
+
* is raised; a confirm cannot override it. Do not retry, and do not offer a
|
|
75
|
+
* Buzz purchase — more Buzz does not lift a limit.
|
|
76
|
+
* - `.unconfirmed` — the job MAY BE RUNNING AND CHARGED: the host sent the
|
|
77
|
+
* submit and could not learn its outcome (`submission-unconfirmed`), or no
|
|
78
|
+
* reply arrived at all (`.timedOut` — the viewer may have confirmed a dialog
|
|
79
|
+
* this block had stopped waiting for). 🔴 NEVER RETRY AUTOMATICALLY and do not
|
|
80
|
+
* tell the viewer it failed. Every confirmation is its own job, so a retry —
|
|
81
|
+
* which needs a new estimate and a new confirmation — is a SECOND, separately
|
|
82
|
+
* charged job if the first one did start. Check the viewer's generations
|
|
83
|
+
* first (`useAppWorkflows()` lists this app's runs).
|
|
84
|
+
* - `.refused` (`code: 'refused'`) — the server REFUSED the submit at a spend
|
|
85
|
+
* cap after the viewer confirmed (the viewer's daily or private-run Buzz cap,
|
|
86
|
+
* the app's daily spend or rate limit). Its reservation was refunded: NO JOB,
|
|
87
|
+
* nothing charged. `.message` is the server's reason and `.snapshot` the
|
|
88
|
+
* refusal it arrived in. Retrying needs a new estimate and usually a later
|
|
89
|
+
* time.
|
|
90
|
+
* - `.signInRequired` — no session. Route into `useRequestSignIn()`.
|
|
91
|
+
* - anything else — a refusal before any submit (an expired or used quote, a
|
|
92
|
+
* body that no longer matches its quote, a price that changed, or the
|
|
93
|
+
* client-side {@link CONFIRMED_STEP_QUOTE_ID_REFUSAL}). Estimate again before
|
|
94
|
+
* retrying; `.message` is the host's or server's text.
|
|
95
|
+
*/
|
|
96
|
+
export class RunConfirmedStepError extends Error {
|
|
97
|
+
/**
|
|
98
|
+
* The closed host refusal code, `'refused'` for a cap refusal, or `undefined`
|
|
99
|
+
* for a server/transport error.
|
|
100
|
+
*/
|
|
101
|
+
code;
|
|
102
|
+
/**
|
|
103
|
+
* The server refused the submit (a spend cap, or a temporary-availability deny)
|
|
104
|
+
* and refunded it — no job exists. The quote is used up: estimate again.
|
|
105
|
+
* See the class docs; `.message` is the server's reason.
|
|
106
|
+
*/
|
|
107
|
+
refused;
|
|
108
|
+
/** The refusal snapshot the host replied with, when {@link refused}. */
|
|
109
|
+
snapshot;
|
|
110
|
+
/** The viewer dismissed the confirm — no job was submitted. */
|
|
111
|
+
declined;
|
|
112
|
+
/**
|
|
113
|
+
* The job would exceed the viewer's own daily Buzz limit for this app — no job
|
|
114
|
+
* was submitted and nothing was charged. Set for the host code
|
|
115
|
+
* `daily-limit-exceeded` and for a refusal snapshot whose `errorCode` is that
|
|
116
|
+
* value.
|
|
117
|
+
*/
|
|
118
|
+
dailyLimitExceeded;
|
|
119
|
+
/**
|
|
120
|
+
* The job may exist and may have been charged — `submission-unconfirmed`, or a
|
|
121
|
+
* transport timeout. Check the viewer's generations before retrying.
|
|
122
|
+
*/
|
|
123
|
+
unconfirmed;
|
|
124
|
+
/** No reply arrived within the 10-minute consent bound. Implies {@link unconfirmed}. */
|
|
125
|
+
timedOut;
|
|
126
|
+
/** There is no session (`sign in to run`, or a bare `UNAUTHORIZED`). */
|
|
127
|
+
signInRequired;
|
|
128
|
+
constructor(error, opts) {
|
|
129
|
+
super(error);
|
|
130
|
+
this.name = 'RunConfirmedStepError';
|
|
131
|
+
this.timedOut = opts?.timedOut === true;
|
|
132
|
+
this.refused = opts?.refusedSnapshot !== undefined;
|
|
133
|
+
if (opts?.refusedSnapshot !== undefined) {
|
|
134
|
+
this.code = 'refused';
|
|
135
|
+
this.snapshot = opts.refusedSnapshot;
|
|
136
|
+
}
|
|
137
|
+
else if (opts?.code !== undefined) {
|
|
138
|
+
this.code = opts.code;
|
|
139
|
+
}
|
|
140
|
+
else if (isRunConfirmedStepErrorCode(error)) {
|
|
141
|
+
this.code = error;
|
|
142
|
+
}
|
|
143
|
+
this.declined = error === 'declined';
|
|
144
|
+
this.dailyLimitExceeded =
|
|
145
|
+
error === DAILY_LIMIT_CODE || opts?.refusedSnapshot?.errorCode === DAILY_LIMIT_CODE;
|
|
146
|
+
this.unconfirmed = this.timedOut || error === 'submission-unconfirmed';
|
|
147
|
+
this.signInRequired = error === 'sign in to run' || error === 'UNAUTHORIZED';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Run ONE pass-through `kind: 'step'` job whose price is above what the app may
|
|
152
|
+
* spend without asking — through the host-mediated `RUN_CONFIRMED_STEP` →
|
|
153
|
+
* `CONFIRMED_STEP_RESULT` bridge, after the viewer confirms the exact price in
|
|
154
|
+
* Civitai's own dialog.
|
|
155
|
+
*
|
|
156
|
+
* 🔴 AVAILABILITY: page apps only, behind the host flag
|
|
157
|
+
* `app-blocks-confirmed-step` (ships off), with `ai:write:budgeted` declared and
|
|
158
|
+
* granted by a signed-in viewer, on a host carrying civitai/civitai#5786. Refused
|
|
159
|
+
* from `dev:live` and from review sessions — build the flow against the mock
|
|
160
|
+
* host. Where it is unavailable the estimate simply carries no `stepQuote`.
|
|
161
|
+
*
|
|
162
|
+
* The flow: `useBuzzWorkflow().estimate(body)` (its `stepQuote.quoteId`) →
|
|
163
|
+
* `runConfirmedStep({ ...body, quoteId })` → `useBuzzWorkflow().watch(workflowId)`.
|
|
164
|
+
* Change nothing in the body between the estimate and the run: the server checks
|
|
165
|
+
* it against the quoted one.
|
|
166
|
+
*
|
|
167
|
+
* 🔴 ONLY A FIXED PRICE IS CONFIRMABLE. `stepQuote` is present when the
|
|
168
|
+
* orchestrator quoted a fixed price above the token's per-call budget and at
|
|
169
|
+
* most `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB` (5,000 Buzz). A variable or
|
|
170
|
+
* metered price, or no price at all, gets no quote and cannot be run this way.
|
|
171
|
+
*
|
|
172
|
+
* 🔴 ESTIMATE THE BODY ON ITS OWN. A batch estimate (`useBatchEstimate()`) never
|
|
173
|
+
* carries a `stepQuote`. And where a quote would be granted, the estimate
|
|
174
|
+
* REJECTS instead for a training `$type` (`training` / `imageResourceTraining`,
|
|
175
|
+
* any casing — use `kind: 'training'`) and for an `input` that is not plain JSON
|
|
176
|
+
* (a `Date`, `Map`, `Set`, `BigInt`, `NaN`, an empty slot in a sparse array, …). Both are server messages with no code; see
|
|
177
|
+
* `BlockWorkflowSnapshot.stepQuote` for the exact text.
|
|
178
|
+
*
|
|
179
|
+
* 🔴 THE CONFIRM IS CIVITAI'S, AND IT IS THE CHARGE. The price on it is read back
|
|
180
|
+
* from the server's stored quote — never from your body. A quote is single-use
|
|
181
|
+
* and expires 15 minutes after the estimate; the server prices the job again at
|
|
182
|
+
* submit and refuses a higher price.
|
|
183
|
+
*
|
|
184
|
+
* 🔴 NO AUTOMATIC RETRY OF ANY KIND, on purpose. Every confirmation is its own
|
|
185
|
+
* job — there is no idempotency key, the orchestrator id comes from the confirmed
|
|
186
|
+
* quote — and one outcome (`submission-unconfirmed`) means a job may already be
|
|
187
|
+
* running. So this hook never re-sends, and neither should you: a retry is a
|
|
188
|
+
* NEW, separately charged job.
|
|
189
|
+
*
|
|
190
|
+
* Sent under the 10-minute human-interaction bound: the host replies only when
|
|
191
|
+
* the viewer clicks or dismisses its dialog.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* const { estimate, watch } = useBuzzWorkflow();
|
|
195
|
+
* const { runConfirmedStep } = useRunConfirmedStep();
|
|
196
|
+
* const quote = (await estimate(body)).stepQuote;
|
|
197
|
+
* if (!quote) return submit(body); // within budget — the ordinary path
|
|
198
|
+
* try {
|
|
199
|
+
* const started = await runConfirmedStep({ ...body, quoteId: quote.quoteId });
|
|
200
|
+
* await watch(started.workflowId, { onUpdate: render });
|
|
201
|
+
* } catch (e) {
|
|
202
|
+
* if (!(e instanceof RunConfirmedStepError)) throw e;
|
|
203
|
+
* if (e.declined) return; // no job — say nothing
|
|
204
|
+
* if (e.dailyLimitExceeded) return; // no job — Civitai already told the viewer
|
|
205
|
+
* if (e.signInRequired) return requestSignIn();
|
|
206
|
+
* if (e.unconfirmed) return showCheckYourGenerations(); // may be running — never auto-retry
|
|
207
|
+
* if (e.refused) return showError(e.message); // a cap refusal; no job, nothing charged
|
|
208
|
+
* showError('Could not start this job. Get a new price and try again.');
|
|
209
|
+
* }
|
|
210
|
+
*/
|
|
211
|
+
export function useRunConfirmedStep() {
|
|
212
|
+
const [pending, setPending] = useState(false);
|
|
213
|
+
const [error, setError] = useState(null);
|
|
214
|
+
// This request can outlive the component by up to ten minutes (the dialog may
|
|
215
|
+
// stay open), so a `pending` toggled on an unmounted control is state nobody
|
|
216
|
+
// can clear.
|
|
217
|
+
const mountedRef = useRef(true);
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
mountedRef.current = true;
|
|
220
|
+
return () => {
|
|
221
|
+
mountedRef.current = false;
|
|
222
|
+
};
|
|
223
|
+
}, []);
|
|
224
|
+
const runConfirmedStep = useCallback(async (body) => {
|
|
225
|
+
// See `CONFIRMED_STEP_QUOTE_ID_REFUSAL`. Before the `pending` flip and the
|
|
226
|
+
// send: nothing about this call may reach the wire. `body` is read through
|
|
227
|
+
// `unknown` because a JavaScript caller, or one that cast its way past the
|
|
228
|
+
// type, is exactly who this is for.
|
|
229
|
+
if (!isStepQuoteId(body?.quoteId)) {
|
|
230
|
+
const refused = new RunConfirmedStepError(CONFIRMED_STEP_QUOTE_ID_REFUSAL, {
|
|
231
|
+
code: 'invalid confirmed step request',
|
|
232
|
+
});
|
|
233
|
+
if (mountedRef.current)
|
|
234
|
+
setError(refused);
|
|
235
|
+
throw refused;
|
|
236
|
+
}
|
|
237
|
+
if (mountedRef.current) {
|
|
238
|
+
setPending(true);
|
|
239
|
+
setError(null);
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const reply = await sendTypedRequest(getTransport(), { type: 'RUN_CONFIRMED_STEP', payload: { body } }, 'CONFIRMED_STEP_RESULT', { timeoutMs: HUMAN_INTERACTION_TIMEOUT_MS }).catch((err) => {
|
|
243
|
+
if (err instanceof RequestTimeoutError) {
|
|
244
|
+
throw new RunConfirmedStepError(err.message, { timedOut: true });
|
|
245
|
+
}
|
|
246
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
247
|
+
});
|
|
248
|
+
if (reply.error || !reply.snapshot) {
|
|
249
|
+
// `||`, not `??`: `error: ''` is a shape-valid reply. Its fallback is the
|
|
250
|
+
// CAUTIOUS code — with neither a snapshot nor a reason, nothing says a
|
|
251
|
+
// job was not started, so the block must not be told it was safe to
|
|
252
|
+
// retry.
|
|
253
|
+
throw new RunConfirmedStepError(reply.error || 'submission-unconfirmed');
|
|
254
|
+
}
|
|
255
|
+
// 🔴 A RESOLVED SNAPSHOT IS NOT ALWAYS A RUN. The host's confirmed submit
|
|
256
|
+
// answers its spend-cap refusals (refunded, nothing started) with a
|
|
257
|
+
// resolved `{ workflowId: 'failed', status: 'failed', cost, error }`.
|
|
258
|
+
// Resolving that would hand the caller a workflow id to `watch()` that
|
|
259
|
+
// names nothing, and drop the reason. Exact match on the host's
|
|
260
|
+
// sentinel: a failed snapshot with a REAL id is a workflow that may
|
|
261
|
+
// exist, and keeps resolving so it can be watched.
|
|
262
|
+
if (reply.snapshot.workflowId === HOST_REFUSAL_WORKFLOW_ID &&
|
|
263
|
+
reply.snapshot.status === 'failed') {
|
|
264
|
+
throw new RunConfirmedStepError(reply.snapshot.error || 'confirmed step refused', {
|
|
265
|
+
refusedSnapshot: reply.snapshot,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return reply.snapshot;
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
const wrapped = err instanceof RunConfirmedStepError
|
|
272
|
+
? err
|
|
273
|
+
: new RunConfirmedStepError(err instanceof Error ? err.message : String(err));
|
|
274
|
+
if (mountedRef.current)
|
|
275
|
+
setError(wrapped);
|
|
276
|
+
// Thrown even when unmounted: the caller's `await` must still learn the
|
|
277
|
+
// outcome of a possibly-charged job.
|
|
278
|
+
throw wrapped;
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
if (mountedRef.current)
|
|
282
|
+
setPending(false);
|
|
283
|
+
}
|
|
284
|
+
}, []);
|
|
285
|
+
return { runConfirmedStep, pending, error };
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=useRunConfirmedStep.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export { usePrepareTrainingDataset, PrepareTrainingDatasetError, PREPARE_TRAININ
|
|
|
75
75
|
export type { UsePrepareTrainingDataset, BlockPrepareTrainingDatasetHostError, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, } from './hooks/usePrepareTrainingDataset.js';
|
|
76
76
|
export { useRunTraining, RunTrainingError, RUN_TRAINING_ERROR_CODES, isRunTrainingErrorCode, } from './hooks/useRunTraining.js';
|
|
77
77
|
export type { UseRunTraining, QuotedTrainingBody, RunTrainingErrorCode, BlockRunTrainingHostError, BlockTrainingQuote, WorkflowBodyTraining, } from './hooks/useRunTraining.js';
|
|
78
|
+
export { useRunConfirmedStep, RunConfirmedStepError, RUN_CONFIRMED_STEP_ERROR_CODES, isRunConfirmedStepErrorCode, isStepQuoteId, } from './hooks/useRunConfirmedStep.js';
|
|
79
|
+
export type { UseRunConfirmedStep, QuotedStepBody, RunConfirmedStepErrorCode, BlockRunConfirmedStepHostError, BlockStepQuote, WorkflowBodyPassThroughStep, } from './hooks/useRunConfirmedStep.js';
|
|
78
80
|
export type { BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, AppWorkflowsParams, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, } from '@civitai/app-sdk/blocks';
|
|
79
81
|
export { useCheckpointPicker } from './hooks/useCheckpointPicker.js';
|
|
80
82
|
export type { UseCheckpointPicker } from './hooks/useCheckpointPicker.js';
|
package/dist/index.js
CHANGED
|
@@ -63,6 +63,7 @@ export { useCollectionFollow, CollectionFollowError, COLLECTION_FOLLOW_ERROR_COD
|
|
|
63
63
|
export { useCreatePostFromApp, CreatePostError, CREATE_POST_ERROR_CODES, isCreatePostErrorCode, } from './hooks/useCreatePostFromApp.js';
|
|
64
64
|
export { usePrepareTrainingDataset, PrepareTrainingDatasetError, PREPARE_TRAINING_DATASET_ERROR_CODES, isPrepareTrainingDatasetErrorCode, } from './hooks/usePrepareTrainingDataset.js';
|
|
65
65
|
export { useRunTraining, RunTrainingError, RUN_TRAINING_ERROR_CODES, isRunTrainingErrorCode, } from './hooks/useRunTraining.js';
|
|
66
|
+
export { useRunConfirmedStep, RunConfirmedStepError, RUN_CONFIRMED_STEP_ERROR_CODES, isRunConfirmedStepErrorCode, isStepQuoteId, } from './hooks/useRunConfirmedStep.js';
|
|
66
67
|
export { useCheckpointPicker } from './hooks/useCheckpointPicker.js';
|
|
67
68
|
export { useResourcePicker, RESOURCE_PICKER_MULTIPLE_MAX } from './hooks/useResourcePicker.js';
|
|
68
69
|
export { useImageUpload } from './hooks/useImageUpload.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hostSurface.ts — which production host a dev host stands in for.
|
|
3
|
+
*
|
|
4
|
+
* civitai.com has two hosts. A full-page app (`app.page`) is mounted by the page
|
|
5
|
+
* host; a block in a `model.*` slot is mounted by the model-slot host. They
|
|
6
|
+
* answer a few messages differently, so a dev host has to know which of the two
|
|
7
|
+
* it is imitating. Both dev hosts (`createMockHost`, `createLiveHost`) read it
|
|
8
|
+
* from the one place they already model it: the `BLOCK_INIT` context.
|
|
9
|
+
*/
|
|
10
|
+
import type { BlockContext } from '@civitai/app-sdk/blocks';
|
|
11
|
+
/** The slot id of a full-page app. */
|
|
12
|
+
export declare const PAGE_SLOT_ID = "app.page";
|
|
13
|
+
/**
|
|
14
|
+
* Is this dev host standing in for the PAGE host?
|
|
15
|
+
*
|
|
16
|
+
* `context` is the `options.context` the harness passed. `undefined` is a page:
|
|
17
|
+
* both dev hosts default to a complete page context when none is given.
|
|
18
|
+
*
|
|
19
|
+
* Keyed on the slot id alone, not on `isPageSlotContext`. Production picks the
|
|
20
|
+
* host by the slot the app is mounted in, so an incomplete `{ slotId:
|
|
21
|
+
* 'app.page' }` context is still a page.
|
|
22
|
+
*/
|
|
23
|
+
export declare function emulatesPageHost(context: BlockContext | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The `error` the page host answers `SET_USER_CHECKPOINT` with, verbatim.
|
|
26
|
+
*
|
|
27
|
+
* A page token is bound to no model, and the server writes a checkpoint override
|
|
28
|
+
* into the settings row of a model-bound install. The page host therefore
|
|
29
|
+
* refuses every such request with `USER_CHECKPOINT_SET { requestId, ok: false,
|
|
30
|
+
* error }`, whatever `versionId` was sent, and makes no server call.
|
|
31
|
+
*
|
|
32
|
+
* Read from `civitai/civitai` `main` at `c1fffa4d1f`
|
|
33
|
+
* (`src/components/AppBlocks/PageBlockHost.tsx`). It is host-authored text: show
|
|
34
|
+
* it or log it, do not branch on it.
|
|
35
|
+
*/
|
|
36
|
+
export declare const PAGE_CHECKPOINT_PERSIST_ERROR = "page blocks cannot persist a checkpoint override (no model binding)";
|
|
37
|
+
//# sourceMappingURL=hostSurface.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hostSurface.ts — which production host a dev host stands in for.
|
|
3
|
+
*
|
|
4
|
+
* civitai.com has two hosts. A full-page app (`app.page`) is mounted by the page
|
|
5
|
+
* host; a block in a `model.*` slot is mounted by the model-slot host. They
|
|
6
|
+
* answer a few messages differently, so a dev host has to know which of the two
|
|
7
|
+
* it is imitating. Both dev hosts (`createMockHost`, `createLiveHost`) read it
|
|
8
|
+
* from the one place they already model it: the `BLOCK_INIT` context.
|
|
9
|
+
*/
|
|
10
|
+
/** The slot id of a full-page app. */
|
|
11
|
+
export const PAGE_SLOT_ID = 'app.page';
|
|
12
|
+
/**
|
|
13
|
+
* Is this dev host standing in for the PAGE host?
|
|
14
|
+
*
|
|
15
|
+
* `context` is the `options.context` the harness passed. `undefined` is a page:
|
|
16
|
+
* both dev hosts default to a complete page context when none is given.
|
|
17
|
+
*
|
|
18
|
+
* Keyed on the slot id alone, not on `isPageSlotContext`. Production picks the
|
|
19
|
+
* host by the slot the app is mounted in, so an incomplete `{ slotId:
|
|
20
|
+
* 'app.page' }` context is still a page.
|
|
21
|
+
*/
|
|
22
|
+
export function emulatesPageHost(context) {
|
|
23
|
+
return (context?.slotId ?? PAGE_SLOT_ID) === PAGE_SLOT_ID;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The `error` the page host answers `SET_USER_CHECKPOINT` with, verbatim.
|
|
27
|
+
*
|
|
28
|
+
* A page token is bound to no model, and the server writes a checkpoint override
|
|
29
|
+
* into the settings row of a model-bound install. The page host therefore
|
|
30
|
+
* refuses every such request with `USER_CHECKPOINT_SET { requestId, ok: false,
|
|
31
|
+
* error }`, whatever `versionId` was sent, and makes no server call.
|
|
32
|
+
*
|
|
33
|
+
* Read from `civitai/civitai` `main` at `c1fffa4d1f`
|
|
34
|
+
* (`src/components/AppBlocks/PageBlockHost.tsx`). It is host-authored text: show
|
|
35
|
+
* it or log it, do not branch on it.
|
|
36
|
+
*/
|
|
37
|
+
export const PAGE_CHECKPOINT_PERSIST_ERROR = 'page blocks cannot persist a checkpoint override (no model binding)';
|
|
38
|
+
//# sourceMappingURL=hostSurface.js.map
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* fetch chokepoints ..... 2
|
|
48
48
|
* REST endpoints ........ GET /api/v1/blocks/me
|
|
49
49
|
* tRPC procedures ....... 29 = blocks.* (14) + apps.shared.* (10) + apps.storage.* (5)
|
|
50
|
-
* switch case labels ....
|
|
50
|
+
* switch case labels .... 49, of which 11 REFUSE (enumerated under SCOPE below)
|
|
51
51
|
* --- END DERIVED ---
|
|
52
52
|
*
|
|
53
53
|
* PICKERS (Phase 1 of "make dev:live a faithful local host"): the live host
|
|
@@ -70,11 +70,12 @@
|
|
|
70
70
|
* and row budgets enforced per (app, user) — with the REAL ceilings, so a
|
|
71
71
|
* write this host accepts is one production would accept.
|
|
72
72
|
*
|
|
73
|
-
* SET_USER_CHECKPOINT (Phase 4):
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
73
|
+
* SET_USER_CHECKPOINT (Phase 4): answered as the production host for the slot
|
|
74
|
+
* would. On a PAGE context (the default; dev-token mints page tokens) it is
|
|
75
|
+
* refused locally with the page host's own reply — `ok: false` and the same
|
|
76
|
+
* error text — and no backend call, because the page host makes none. On a
|
|
77
|
+
* model-slot context it is FORWARDED to the block-token
|
|
78
|
+
* `blocks.updateUserSettings` mutation, never fabricated.
|
|
78
79
|
*
|
|
79
80
|
* SHARED STORAGE (#386): the live host SERVES all TEN `SHARED_*` bridges —
|
|
80
81
|
* `apps.shared.{list,get,getCount,getCounts,append,update,vote,unvote,withdraw,
|
|
@@ -113,6 +114,10 @@
|
|
|
113
114
|
* • RUN_TRAINING — the same dev-token refusal, plus the host-chrome consent
|
|
114
115
|
* dialog a run is confirmed in, which this harness does not have. Replies
|
|
115
116
|
* with a refusal.
|
|
117
|
+
* • RUN_CONFIRMED_STEP — the server refuses a confirmed step from a dev token
|
|
118
|
+
* and the estimate never returns a `stepQuote` for one, and the price is
|
|
119
|
+
* confirmed in host chrome this harness does not have. Replies with a
|
|
120
|
+
* refusal.
|
|
116
121
|
* • PUBLISH_GENERATION_OUTPUTS — publishing requires the viewer's signed-in
|
|
117
122
|
* civitai.com session, which the local harness does not have; test
|
|
118
123
|
* publishing against the mock host — `createMockHost` or `Harness` from
|
|
@@ -217,6 +222,10 @@ export interface LiveHostOptions {
|
|
|
217
222
|
* string so the two cannot drift.
|
|
218
223
|
*/
|
|
219
224
|
export declare const TRAINING_UNSUPPORTED_ERROR = "training is not supported in dev:live \u2014 use dev:mock";
|
|
225
|
+
/**
|
|
226
|
+
* The reply `RUN_CONFIRMED_STEP` gets in dev:live — see its handler.
|
|
227
|
+
*/
|
|
228
|
+
export declare const CONFIRMED_STEP_UNSUPPORTED_ERROR = "confirmed steps are not supported in dev:live \u2014 use dev:mock";
|
|
220
229
|
/**
|
|
221
230
|
* Decoded payload of a block-token JWT (the claims `BlockTokenService.sign`
|
|
222
231
|
* stamps — see civitai `block-token.service.ts`). Everything is optional/unknown
|