@civitai/sdk 0.17.1 → 0.19.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 +49 -0
- package/dist/app/index.d.ts +18 -1
- package/dist/app/index.js +23 -3
- package/dist/core/handshake.d.ts +10 -0
- package/dist/core/handshake.js +2 -0
- package/dist/core/transport.d.ts +6 -0
- package/dist/core/transport.js +4 -0
- package/dist/core/transports/host-replies.generated.d.ts +1 -1
- package/dist/core/transports/host-replies.generated.js +3 -3
- package/dist/doc-store/index.d.ts +23 -2
- package/dist/doc-store/index.js +18 -4
- package/dist/doc-store/local-preview.d.ts +49 -0
- package/dist/doc-store/local-preview.js +131 -0
- package/dist/index.d.ts +2 -2
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workflows/types.generated.d.ts +49 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -282,6 +282,55 @@ before moderation and before any write budget. An absent field is allowed;
|
|
|
282
282
|
ownership is checked only when the document is written. Only phase-one operations exist: no batch writes, change feed, TTL or
|
|
283
283
|
owner-written documents yet.
|
|
284
284
|
|
|
285
|
+
### In a dev-tunnel preview: local to the tab
|
|
286
|
+
|
|
287
|
+
A dev-tunnel preview (`civitai app dev-tunnel`, the App Assistant's live
|
|
288
|
+
preview) has **no server-side storage**: an app that is not approved has no storage
|
|
289
|
+
namespace, and unreviewed code must not write into a published app's data. So when
|
|
290
|
+
the host marks the frame `storageMode: 'local-preview'` in `BLOCK_INIT`,
|
|
291
|
+
`initialize()` serves `app.storage.collection(name)` and `app.storage.info()` from an
|
|
292
|
+
**in-memory store in this tab** — [`createFakeStorage`](#a-fake-document-store), the
|
|
293
|
+
real client over a seam that enforces the server's rules — and sends **no request**
|
|
294
|
+
to `blocks/storage/*`. Your code does not change; show a badge so nobody mistakes the
|
|
295
|
+
data for real:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
if (app.storage.mode === 'local-preview') {
|
|
299
|
+
showBadge('Preview data is local to this tab');
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
- **Your LOCAL manifest decides the collections**, not the server's copy:
|
|
304
|
+
`blockManifestPlugin()` from `@civitai/app-sdk/vite` hands its `scopes` and
|
|
305
|
+
`storage` to the page on the dev server (never in a build); that is the only way
|
|
306
|
+
the manifest reaches the SDK. It does so from the `@civitai/app-sdk` release that
|
|
307
|
+
includes this change (the one whose CHANGELOG entry reads "`blockManifestPlugin()`
|
|
308
|
+
exposes the manifest's `scopes` and `storage` to `@civitai/sdk` on the dev server
|
|
309
|
+
only"); a `^0.x` range never moves to a newer minor, so raise that pin by hand.
|
|
310
|
+
Not every scaffold registers the plugin (the CLI's `page-money` template does
|
|
311
|
+
not); add it there. Without it, every document-store call rejects
|
|
312
|
+
`local_preview_no_manifest` (a `StorageError`, status 404, sent nowhere); a
|
|
313
|
+
declaration the local store cannot serve rejects `local_preview_invalid_manifest`
|
|
314
|
+
(also 404), and a local store that failed to load rejects
|
|
315
|
+
`local_preview_unavailable` (also 404; reload the preview). None is a status this
|
|
316
|
+
SDK treats as retryable, and none falls back to the real routes.
|
|
317
|
+
- **Model-version ownership is not checked locally.** A collection's
|
|
318
|
+
`ownedModelVersions` fields accept any well-formed model version id in a preview;
|
|
319
|
+
the server checks that the writer owns it. A malformed value is still refused.
|
|
320
|
+
- **One viewer, one tab, gone on reload.** It cannot show other people's entries,
|
|
321
|
+
real vote totals, ranking across users, the minimum-trust gate on shared writes,
|
|
322
|
+
or moderation of real content.
|
|
323
|
+
- **Only the document store.** The deprecated `app.storage.get/set/…`,
|
|
324
|
+
`app.sharedStorage` and `@civitai/blocks-react`'s `useAppStorage` /
|
|
325
|
+
`useSharedStorage` keep calling the real routes, which refuse a preview.
|
|
326
|
+
- **Only on the host's signal.** `app.storage.mode` is latched when `initialize()`
|
|
327
|
+
resolves, and never inferred from the token: a published app whose manifest forgot
|
|
328
|
+
`apps:storage` holds a token that looks the same, and keeps getting the real
|
|
329
|
+
route's `insufficient_scope`. That refusal's message says what to do —
|
|
330
|
+
declare `apps:storage` under `scopes`; it is granted once the app is approved — and
|
|
331
|
+
that in a preview this SDK would have kept the data locally had the host marked the
|
|
332
|
+
frame.
|
|
333
|
+
|
|
285
334
|
The legacy stores below keep working, unchanged, while apps move; their methods are
|
|
286
335
|
`@deprecated`. Nothing is copied from them into collections.
|
|
287
336
|
|
package/dist/app/index.d.ts
CHANGED
|
@@ -9,6 +9,21 @@ import { type SiteClient } from '../site/index.js';
|
|
|
9
9
|
import { type StorageClient } from '../storage/index.js';
|
|
10
10
|
import { type SubListingsClient } from '../sub-listings/index.js';
|
|
11
11
|
import { type WorkflowsClient } from '../workflows/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Which store serves `app.storage.collection(name)` and `app.storage.info()`.
|
|
14
|
+
*
|
|
15
|
+
* - `'remote'` — the server's document store, `/api/v1/blocks/storage/…`.
|
|
16
|
+
* - `'local-preview'` — an in-memory store in this tab, because the host marked
|
|
17
|
+
* the frame a dev-tunnel preview (which has no server-side storage). It
|
|
18
|
+
* enforces the server's rules against the LOCAL manifest, models ONE viewer,
|
|
19
|
+
* and is gone on reload. Show the viewer a "preview data is local to this tab"
|
|
20
|
+
* badge.
|
|
21
|
+
*
|
|
22
|
+
* Latched when `initialize()` resolves. Only the document store switches: the
|
|
23
|
+
* deprecated key/value methods on `app.storage`, `app.sharedStorage` and the
|
|
24
|
+
* `@civitai/blocks-react` storage hooks keep calling the real routes.
|
|
25
|
+
*/
|
|
26
|
+
export type StorageMode = 'remote' | 'local-preview';
|
|
12
27
|
export interface AppClient {
|
|
13
28
|
/**
|
|
14
29
|
* The public Civitai REST API (`/api/v1`), as the viewer.
|
|
@@ -36,7 +51,9 @@ export interface AppClient {
|
|
|
36
51
|
* with an OAuth access token is refused. The legacy store refuses an anonymous
|
|
37
52
|
* viewer outright; the document store lets one read SHARED collections only.
|
|
38
53
|
*/
|
|
39
|
-
readonly storage: StorageClient & DocumentStorage
|
|
54
|
+
readonly storage: StorageClient & DocumentStorage & {
|
|
55
|
+
readonly mode: StorageMode;
|
|
56
|
+
};
|
|
40
57
|
/**
|
|
41
58
|
* @deprecated Declare a `shared` collection under `storage.collections` and use
|
|
42
59
|
* `app.storage.collection(name)`. This store keeps working, unchanged, until the
|
package/dist/app/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createOrchestrationClient, DEFAULT_ORCHESTRATION_URL, } from '../orches
|
|
|
6
6
|
import { createTokenSession, } from '../session/index.js';
|
|
7
7
|
import { createSharedStorageClient, } from '../shared-storage/index.js';
|
|
8
8
|
import { createDocumentStorage } from '../doc-store/index.js';
|
|
9
|
+
import { createLocalPreviewStorage, readInjectedManifest } from '../doc-store/local-preview.js';
|
|
9
10
|
import { createSiteClient, DEFAULT_SITE_URL } from '../site/index.js';
|
|
10
11
|
import { createStorageClient } from '../storage/index.js';
|
|
11
12
|
import { createSubListingsClient } from '../sub-listings/index.js';
|
|
@@ -21,8 +22,17 @@ export async function initialize(options = {}) {
|
|
|
21
22
|
const holdsBlockTokenNow = holdsBlockToken(snapshot);
|
|
22
23
|
// Read live, like the predicate above: the host rotates the token.
|
|
23
24
|
const holdsOAuthTokenNow = () => snapshot().token.kind === 'oauth';
|
|
25
|
+
// 🔴 Latched here, ONCE, and only on the host's exact signal — never inferred
|
|
26
|
+
// from `token.scopes`. A published app whose manifest forgot `apps:storage`
|
|
27
|
+
// holds a token that looks the same as a preview's, and must keep getting the
|
|
28
|
+
// real route's `insufficient_scope` rather than a silent local store.
|
|
29
|
+
const local = snapshot().storageMode === 'local-preview'
|
|
30
|
+
? // The manifest arrives only from `blockManifestPlugin()`'s dev-server
|
|
31
|
+
// global: there is deliberately no option to pass one.
|
|
32
|
+
await createLocalPreviewStorage(readInjectedManifest(), snapshot().viewer)
|
|
33
|
+
: undefined;
|
|
24
34
|
return {
|
|
25
|
-
...createAppClient(createHostSession(transport), options, holdsBlockTokenNow, holdsOAuthTokenNow),
|
|
35
|
+
...createAppClient(createHostSession(transport), options, holdsBlockTokenNow, holdsOAuthTokenNow, local),
|
|
26
36
|
host: createHost(transport),
|
|
27
37
|
get viewer() {
|
|
28
38
|
return snapshot().viewer;
|
|
@@ -81,7 +91,9 @@ const OAUTH_OPT_IN = 'declare `auth: "oauth"` in block.manifest.json';
|
|
|
81
91
|
* refuses, with nothing in this repo able to notice it going stale.
|
|
82
92
|
*/
|
|
83
93
|
const BLOCK_NAMESPACE = /^\/*blocks\//;
|
|
84
|
-
function createAppClient(session, options, holdsBlockTokenNow = () => false, holdsOAuthTokenNow = () => false
|
|
94
|
+
function createAppClient(session, options, holdsBlockTokenNow = () => false, holdsOAuthTokenNow = () => false,
|
|
95
|
+
/** A local preview's document store; when present, the site HTTP client never reaches the document store. */
|
|
96
|
+
localDocuments) {
|
|
85
97
|
const http = (baseUrl) => createHttp({ session, baseUrl, fetch: options.fetch });
|
|
86
98
|
// One instance for every site-hosted surface: the storage, sub-listings and
|
|
87
99
|
// workflows clients' routes live under the same base URL, so a `siteUrl`
|
|
@@ -100,7 +112,15 @@ function createAppClient(session, options, holdsBlockTokenNow = () => false, hol
|
|
|
100
112
|
// One object, two stores, no shared names: the legacy five and the document
|
|
101
113
|
// store's `collection`/`info`. Spread rather than chained so neither keeps a
|
|
102
114
|
// reference to the other.
|
|
103
|
-
|
|
115
|
+
//
|
|
116
|
+
// In a local preview the document store is the in-memory one and is never
|
|
117
|
+
// handed `siteHttp`: zero `blocks/storage/*` requests by construction. The
|
|
118
|
+
// legacy key/value methods are out of scope and keep the real routes.
|
|
119
|
+
storage: {
|
|
120
|
+
...createStorageClient(siteHttp),
|
|
121
|
+
...(localDocuments ?? createDocumentStorage(siteHttp, { explainInsufficientScope: true })),
|
|
122
|
+
mode: localDocuments ? 'local-preview' : 'remote',
|
|
123
|
+
},
|
|
104
124
|
sharedStorage: createSharedStorageClient(siteHttp),
|
|
105
125
|
subListings: createSubListingsClient(siteHttp),
|
|
106
126
|
workflows: createWorkflowsClient(explainWorkflowRefusal(siteHttp, holdsOAuthTokenNow)),
|
package/dist/core/handshake.d.ts
CHANGED
|
@@ -98,7 +98,17 @@ export interface BlockInitPayload {
|
|
|
98
98
|
maxBrowsingLevel?: number;
|
|
99
99
|
/** The domain ceiling narrowed to this viewer. Always a subset of the above. */
|
|
100
100
|
effectiveBrowsingLevel?: number;
|
|
101
|
+
/**
|
|
102
|
+
* `'local-preview'` when the host mounted this frame as a dev-tunnel preview,
|
|
103
|
+
* which has no server-side storage. The host sets it from the surface it
|
|
104
|
+
* mounted, never from the token or anything the block sent. Absent everywhere
|
|
105
|
+
* else, and from a host that predates it. Typed `unknown` on the wire: only
|
|
106
|
+
* the exact string switches anything — see {@link LOCAL_PREVIEW_STORAGE}.
|
|
107
|
+
*/
|
|
108
|
+
storageMode?: unknown;
|
|
101
109
|
}
|
|
110
|
+
/** The one value of `BLOCK_INIT.storageMode` the SDK acts on. Compared exactly. */
|
|
111
|
+
export declare const LOCAL_PREVIEW_STORAGE = "local-preview";
|
|
102
112
|
/**
|
|
103
113
|
* Host messages the transport itself acts on. Domains own everything else.
|
|
104
114
|
*
|
package/dist/core/handshake.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* The host's side of the handshake, as it arrives on the wire. The shapes are
|
|
3
3
|
* the host's and must track it.
|
|
4
4
|
*/
|
|
5
|
+
/** The one value of `BLOCK_INIT.storageMode` the SDK acts on. Compared exactly. */
|
|
6
|
+
export const LOCAL_PREVIEW_STORAGE = 'local-preview';
|
|
5
7
|
/** Discriminator only — the payload is still unvalidated wire data. */
|
|
6
8
|
export function isHostMessage(data, type) {
|
|
7
9
|
return (typeof data === 'object' &&
|
package/dist/core/transport.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface BlockSnapshot {
|
|
|
29
29
|
maxBrowsingLevel?: number;
|
|
30
30
|
/** The domain ceiling narrowed to this viewer. Always a subset of `maxBrowsingLevel`. */
|
|
31
31
|
effectiveBrowsingLevel?: number;
|
|
32
|
+
/**
|
|
33
|
+
* `'local-preview'` when the host said this frame is a dev-tunnel preview with
|
|
34
|
+
* no server-side storage; absent otherwise. `initialize()` reads it ONCE, so a
|
|
35
|
+
* later change cannot move an app's storage between stores mid-session.
|
|
36
|
+
*/
|
|
37
|
+
storageMode?: 'local-preview';
|
|
32
38
|
}
|
|
33
39
|
export interface RequestOptions {
|
|
34
40
|
signal?: AbortSignal;
|
package/dist/core/transport.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LOCAL_PREVIEW_STORAGE } from './handshake.js';
|
|
1
2
|
export const EMPTY_SNAPSHOT = {
|
|
2
3
|
ready: false,
|
|
3
4
|
renderMode: 'iframe',
|
|
@@ -24,6 +25,9 @@ export function snapshotFromInit(payload, hostOrigin) {
|
|
|
24
25
|
domain: payload.domain,
|
|
25
26
|
maxBrowsingLevel: payload.maxBrowsingLevel,
|
|
26
27
|
effectiveBrowsingLevel: payload.effectiveBrowsingLevel,
|
|
28
|
+
// The exact string or nothing: a value this SDK does not know must read as
|
|
29
|
+
// "not a local preview", which keeps today's behaviour (the real routes).
|
|
30
|
+
...(payload.storageMode === LOCAL_PREVIEW_STORAGE ? { storageMode: LOCAL_PREVIEW_STORAGE } : {}),
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
/** Wire `WrappedToken` (ISO expiry) → runtime `BlockToken` (Date expiry). */
|
|
@@ -7,7 +7,7 @@ export interface HostReply {
|
|
|
7
7
|
readonly keys: readonly string[] | null;
|
|
8
8
|
}
|
|
9
9
|
/** The civitai commit this ledger was read from. A request the host added after it is refused too. */
|
|
10
|
-
export declare const HOST_SNAPSHOT = "
|
|
10
|
+
export declare const HOST_SNAPSHOT = "05f1d1fa8f";
|
|
11
11
|
/**
|
|
12
12
|
* Every request the host answered at {@link HOST_SNAPSHOT}. A type absent here is refused.
|
|
13
13
|
* Reply names and framing are fixed at publish time: if the host renames a reply or moves a
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by scripts/gen-host-replies.mjs from snapshots/host-messages.json
|
|
2
|
-
// (civitai/civitai
|
|
2
|
+
// (civitai/civitai 05f1d1fa8f, 2026-10-20). Do not edit.
|
|
3
3
|
/** The civitai commit this ledger was read from. A request the host added after it is refused too. */
|
|
4
|
-
export const HOST_SNAPSHOT = "
|
|
4
|
+
export const HOST_SNAPSHOT = "05f1d1fa8f";
|
|
5
5
|
/**
|
|
6
6
|
* Every request the host answered at {@link HOST_SNAPSHOT}. A type absent here is refused.
|
|
7
7
|
* Reply names and framing are fixed at publish time: if the host renames a reply or moves a
|
|
@@ -33,7 +33,7 @@ export const HOST_REPLIES = {
|
|
|
33
33
|
"OPEN_MEDIA_UPLOAD": { "reply": "MEDIA_UPLOAD_RESULT", "framing": "legacy", "keys": ["selected"] },
|
|
34
34
|
"OPEN_RESOURCE_PICKER": { "reply": "RESOURCE_PICKER_RESULT", "framing": "legacy", "keys": ["selected", "selectedResources"] },
|
|
35
35
|
"POLL_WORKFLOW": { "reply": "WORKFLOW_STATUS", "framing": "legacy", "keys": ["snapshot"] },
|
|
36
|
-
"PREPARE_TRAINING_DATASET": { "reply": "TRAINING_DATASET_RESULT", "framing": "legacy", "keys": [
|
|
36
|
+
"PREPARE_TRAINING_DATASET": { "reply": "TRAINING_DATASET_RESULT", "framing": "legacy", "keys": [] },
|
|
37
37
|
"PUBLISH_GENERATION_OUTPUTS": { "reply": "PUBLISH_RESULT", "framing": "legacy", "keys": ["result"] },
|
|
38
38
|
"QUERY_APP_WORKFLOWS": { "reply": "APP_WORKFLOWS_RESULT", "framing": "legacy", "keys": ["result"] },
|
|
39
39
|
"REQUEST_TOKEN": { "reply": "TOKEN_REFRESH_RESPONSE", "framing": "legacy", "keys": ["token"] },
|
|
@@ -171,7 +171,16 @@ export interface DocumentStorage {
|
|
|
171
171
|
* Every code the server documents for these routes. The type stays open
|
|
172
172
|
* (`string & {}`) so a code a later server adds still reaches the caller.
|
|
173
173
|
*/
|
|
174
|
-
export type StorageErrorCode = 'invalid_request' | 'index_not_declared' | 'unauthenticated' | 'sign_in_required' | 'insufficient_scope' | 'consent_required' | 'not_eligible' | 'not_author' | 'write_policy' | 'resource_not_owned' | 'not_found' | 'collection_not_declared' | 'method_not_allowed' | 'version_conflict' | 'index_building' | 'cursor_expired' | 'too_large' | 'quota_exceeded' | 'content_rejected' | 'rate_limited' | 'internal_error' | 'storage_disabled' | 'moderation_unavailable' | 'consent_unavailable' | 'service_unavailable'
|
|
174
|
+
export type StorageErrorCode = 'invalid_request' | 'index_not_declared' | 'unauthenticated' | 'sign_in_required' | 'insufficient_scope' | 'consent_required' | 'not_eligible' | 'not_author' | 'write_policy' | 'resource_not_owned' | 'not_found' | 'collection_not_declared' | 'method_not_allowed' | 'version_conflict' | 'index_building' | 'cursor_expired' | 'too_large' | 'quota_exceeded' | 'content_rejected' | 'rate_limited' | 'internal_error' | 'storage_disabled' | 'moderation_unavailable' | 'consent_unavailable' | 'service_unavailable'
|
|
175
|
+
/**
|
|
176
|
+
* LOCAL, never sent by the server: the host marked this frame a dev-tunnel
|
|
177
|
+
* local preview, and no manifest reached the SDK to say which collections exist.
|
|
178
|
+
*/
|
|
179
|
+
| 'local_preview_no_manifest'
|
|
180
|
+
/** LOCAL, never sent by the server: the local preview's manifest declares storage the local store cannot serve. */
|
|
181
|
+
| 'local_preview_invalid_manifest'
|
|
182
|
+
/** LOCAL, never sent by the server: the local preview's in-memory store could not be loaded. */
|
|
183
|
+
| 'local_preview_unavailable' | (string & {});
|
|
175
184
|
/**
|
|
176
185
|
* A refusal from the document store, in its one envelope `{ code, message, error, details? }`.
|
|
177
186
|
* Branch on `code`; `status` and `body` are {@link ApiError}'s, as sent.
|
|
@@ -184,4 +193,16 @@ export declare class StorageError extends ApiError {
|
|
|
184
193
|
/** Seconds to wait before retrying, when the server named one (`rate_limited`, a retryable 503). */
|
|
185
194
|
get retryAfter(): number | undefined;
|
|
186
195
|
}
|
|
187
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Appended to a REAL route's `insufficient_scope`, after the server's own sentence.
|
|
198
|
+
* The server cannot tell a published app whose manifest forgot the scope from a
|
|
199
|
+
* dev-tunnel preview (which is never granted it), so the message covers both —
|
|
200
|
+
* and says that, in a preview, this SDK keeps the data locally once the host
|
|
201
|
+
* marks the frame, so seeing this refusal there means the host did not.
|
|
202
|
+
*/
|
|
203
|
+
export declare const INSUFFICIENT_SCOPE_HINT: string;
|
|
204
|
+
export interface DocumentStorageOptions {
|
|
205
|
+
/** Append {@link INSUFFICIENT_SCOPE_HINT} to an `insufficient_scope` refusal. The server's `code` is unchanged. */
|
|
206
|
+
explainInsufficientScope?: boolean;
|
|
207
|
+
}
|
|
208
|
+
export declare function createDocumentStorage(http: Http, options?: DocumentStorageOptions): DocumentStorage;
|
package/dist/doc-store/index.js
CHANGED
|
@@ -42,8 +42,19 @@ export class StorageError extends ApiError {
|
|
|
42
42
|
return typeof value === 'number' ? value : undefined;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Appended to a REAL route's `insufficient_scope`, after the server's own sentence.
|
|
47
|
+
* The server cannot tell a published app whose manifest forgot the scope from a
|
|
48
|
+
* dev-tunnel preview (which is never granted it), so the message covers both —
|
|
49
|
+
* and says that, in a preview, this SDK keeps the data locally once the host
|
|
50
|
+
* marks the frame, so seeing this refusal there means the host did not.
|
|
51
|
+
*/
|
|
52
|
+
export const INSUFFICIENT_SCOPE_HINT = 'Declare `apps:storage` under `scopes` in block.manifest.json; it is granted once the app is approved. ' +
|
|
53
|
+
'In a dev-tunnel preview, this version of @civitai/sdk keeps preview data in a local in-memory store ' +
|
|
54
|
+
"instead, when the host marks the frame `storageMode: 'local-preview'` — this refusal came from the real " +
|
|
55
|
+
'route, so this frame was not marked.';
|
|
45
56
|
/** An {@link ApiError} whose body is the store's envelope becomes a {@link StorageError}; anything else passes through. */
|
|
46
|
-
function toStorageError(error) {
|
|
57
|
+
function toStorageError(error, options = {}) {
|
|
47
58
|
if (!(error instanceof ApiError) || error instanceof StorageError)
|
|
48
59
|
return error;
|
|
49
60
|
const body = error.body;
|
|
@@ -52,7 +63,10 @@ function toStorageError(error) {
|
|
|
52
63
|
const { code, message, details } = body;
|
|
53
64
|
if (typeof code !== 'string')
|
|
54
65
|
return error;
|
|
55
|
-
|
|
66
|
+
const said = typeof message === 'string' ? message : error.message;
|
|
67
|
+
return new StorageError(error.status, code,
|
|
68
|
+
// `body` stays as the server sent it; only the human-readable sentence grows.
|
|
69
|
+
options.explainInsufficientScope && code === 'insufficient_scope' ? `${said} — ${INSUFFICIENT_SCOPE_HINT}` : said, body, details != null && typeof details === 'object' && !Array.isArray(details)
|
|
56
70
|
? details
|
|
57
71
|
: undefined, { cause: error });
|
|
58
72
|
}
|
|
@@ -87,13 +101,13 @@ function segment(what, value) {
|
|
|
87
101
|
}
|
|
88
102
|
return encodeURIComponent(value);
|
|
89
103
|
}
|
|
90
|
-
export function createDocumentStorage(http) {
|
|
104
|
+
export function createDocumentStorage(http, options = {}) {
|
|
91
105
|
const call = async (method, path, opts = {}) => {
|
|
92
106
|
try {
|
|
93
107
|
return await http(method, path, opts);
|
|
94
108
|
}
|
|
95
109
|
catch (error) {
|
|
96
|
-
throw toStorageError(error);
|
|
110
|
+
throw toStorageError(error, options);
|
|
97
111
|
}
|
|
98
112
|
};
|
|
99
113
|
return {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ViewerInfo } from '../core/handshake.js';
|
|
2
|
+
import { type DocumentStorage } from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The document store for a DEV-TUNNEL PREVIEW: the host marked the frame
|
|
5
|
+
* `storageMode: 'local-preview'` (a preview has no server-side storage namespace,
|
|
6
|
+
* by design), so `app.storage.collection(name)` is served by an in-memory store
|
|
7
|
+
* in this tab instead of `/api/v1/blocks/storage/…`.
|
|
8
|
+
*
|
|
9
|
+
* That store is `createFakeStorage` — the real client over a seam that enforces
|
|
10
|
+
* the server's rules — loaded with a dynamic `import()`, so it is a separate
|
|
11
|
+
* chunk that an app outside a preview never downloads. It never receives the
|
|
12
|
+
* site HTTP client, so no request can reach a real route by construction.
|
|
13
|
+
*
|
|
14
|
+
* The collections come from the app's LOCAL manifest, the one being previewed,
|
|
15
|
+
* never from the server. With none available, every call fails with a local
|
|
16
|
+
* error instead of falling through to the real routes or to a permissive store.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Where `@civitai/app-sdk/vite` puts the local manifest's `scopes` and `storage`
|
|
20
|
+
* in a DEV SERVER page (never in a build). Spelled again, on purpose, in
|
|
21
|
+
* `packages/civitai-app-sdk/src/vite/index.ts`; the two copies are pinned equal by
|
|
22
|
+
* `tests/guards/local-preview-manifest-global.test.mjs`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const LOCAL_PREVIEW_MANIFEST_GLOBAL = "__CIVITAI_LOCAL_PREVIEW_MANIFEST__";
|
|
25
|
+
/** What a local preview reads from the manifest: the scopes it declares and its `storage` block. */
|
|
26
|
+
export interface LocalPreviewManifest {
|
|
27
|
+
scopes?: readonly string[];
|
|
28
|
+
storage?: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** The manifest the dev server injected, if any. */
|
|
31
|
+
export declare function readInjectedManifest(): LocalPreviewManifest | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The status of every local error (`local_preview_no_manifest`,
|
|
34
|
+
* `local_preview_invalid_manifest`, `local_preview_unavailable`). A missing or
|
|
35
|
+
* unservable manifest is a SETUP mistake that no wait fixes, so it must not be a status `isTransient`
|
|
36
|
+
* (`src/http/retry.ts`) reads as retryable — that is every 5xx and 429. 404 is
|
|
37
|
+
* the status the server gives the nearest case it has, `collection_not_declared`:
|
|
38
|
+
* with no usable manifest, no collection is declared. `get` still rejects (it
|
|
39
|
+
* reads only the code `not_found` as "no document"), and the `code` tells them
|
|
40
|
+
* apart. A store that failed to load is not retried either: a reload is the fix,
|
|
41
|
+
* and the message says so.
|
|
42
|
+
*/
|
|
43
|
+
export declare const LOCAL_PREVIEW_ERROR_STATUS = 404;
|
|
44
|
+
/**
|
|
45
|
+
* Builds the preview's document store. Never throws: a missing or unusable
|
|
46
|
+
* manifest, or a local store that fails to load, yields a store whose every call
|
|
47
|
+
* rejects, so storage cannot stop the rest of the app from starting.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createLocalPreviewStorage(manifest: LocalPreviewManifest | undefined, viewer: ViewerInfo | null): Promise<DocumentStorage>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createDocumentStorage, StorageError } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The document store for a DEV-TUNNEL PREVIEW: the host marked the frame
|
|
4
|
+
* `storageMode: 'local-preview'` (a preview has no server-side storage namespace,
|
|
5
|
+
* by design), so `app.storage.collection(name)` is served by an in-memory store
|
|
6
|
+
* in this tab instead of `/api/v1/blocks/storage/…`.
|
|
7
|
+
*
|
|
8
|
+
* That store is `createFakeStorage` — the real client over a seam that enforces
|
|
9
|
+
* the server's rules — loaded with a dynamic `import()`, so it is a separate
|
|
10
|
+
* chunk that an app outside a preview never downloads. It never receives the
|
|
11
|
+
* site HTTP client, so no request can reach a real route by construction.
|
|
12
|
+
*
|
|
13
|
+
* The collections come from the app's LOCAL manifest, the one being previewed,
|
|
14
|
+
* never from the server. With none available, every call fails with a local
|
|
15
|
+
* error instead of falling through to the real routes or to a permissive store.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Where `@civitai/app-sdk/vite` puts the local manifest's `scopes` and `storage`
|
|
19
|
+
* in a DEV SERVER page (never in a build). Spelled again, on purpose, in
|
|
20
|
+
* `packages/civitai-app-sdk/src/vite/index.ts`; the two copies are pinned equal by
|
|
21
|
+
* `tests/guards/local-preview-manifest-global.test.mjs`.
|
|
22
|
+
*/
|
|
23
|
+
export const LOCAL_PREVIEW_MANIFEST_GLOBAL = '__CIVITAI_LOCAL_PREVIEW_MANIFEST__';
|
|
24
|
+
/** The manifest the dev server injected, if any. */
|
|
25
|
+
export function readInjectedManifest() {
|
|
26
|
+
const value = globalThis[LOCAL_PREVIEW_MANIFEST_GLOBAL];
|
|
27
|
+
return value != null && typeof value === 'object' && !Array.isArray(value)
|
|
28
|
+
? value
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
const NO_MANIFEST = "local preview storage: the host marked this frame `storageMode: 'local-preview'` (a dev-tunnel preview, " +
|
|
32
|
+
'which has no server-side storage), and no block.manifest.json reached @civitai/sdk to say which ' +
|
|
33
|
+
'collections exist. Add `blockManifestPlugin()` from `@civitai/app-sdk/vite` to vite.config and run the ' +
|
|
34
|
+
'Vite dev server, with the @civitai/app-sdk release that includes the local-preview manifest injection or ' +
|
|
35
|
+
'later — the plugin does not inject it before that release, and a `^0.x` range never moves to a newer ' +
|
|
36
|
+
'minor, so raise the pin by hand. Nothing was sent to the server.';
|
|
37
|
+
/**
|
|
38
|
+
* The status of every local error (`local_preview_no_manifest`,
|
|
39
|
+
* `local_preview_invalid_manifest`, `local_preview_unavailable`). A missing or
|
|
40
|
+
* unservable manifest is a SETUP mistake that no wait fixes, so it must not be a status `isTransient`
|
|
41
|
+
* (`src/http/retry.ts`) reads as retryable — that is every 5xx and 429. 404 is
|
|
42
|
+
* the status the server gives the nearest case it has, `collection_not_declared`:
|
|
43
|
+
* with no usable manifest, no collection is declared. `get` still rejects (it
|
|
44
|
+
* reads only the code `not_found` as "no document"), and the `code` tells them
|
|
45
|
+
* apart. A store that failed to load is not retried either: a reload is the fix,
|
|
46
|
+
* and the message says so.
|
|
47
|
+
*/
|
|
48
|
+
export const LOCAL_PREVIEW_ERROR_STATUS = 404;
|
|
49
|
+
/**
|
|
50
|
+
* Builds the preview's document store. Never throws: a missing or unusable
|
|
51
|
+
* manifest, or a local store that fails to load, yields a store whose every call
|
|
52
|
+
* rejects, so storage cannot stop the rest of the app from starting.
|
|
53
|
+
*/
|
|
54
|
+
export async function createLocalPreviewStorage(manifest, viewer) {
|
|
55
|
+
if (manifest === undefined)
|
|
56
|
+
return refusingStorage('local_preview_no_manifest', NO_MANIFEST);
|
|
57
|
+
let createFakeStorage;
|
|
58
|
+
try {
|
|
59
|
+
({ createFakeStorage } = await import('./fake.js'));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return refusingStorage('local_preview_unavailable', `local preview storage: the local store could not be loaded — ${error?.message ?? String(error)}. ` +
|
|
63
|
+
'Reload the preview. Nothing was sent to the server.');
|
|
64
|
+
}
|
|
65
|
+
// MODEL-VERSION OWNERSHIP IS NOT CHECKED LOCALLY. The server checks a write's
|
|
66
|
+
// `ownedModelVersions` fields against the model versions the writer really
|
|
67
|
+
// owns; the local store knows of none, and would refuse every such write the
|
|
68
|
+
// server accepts. So every well-formed id a write carries is added to the
|
|
69
|
+
// viewer's `modelVersions` — the fake's own seam, read per call — before the
|
|
70
|
+
// write reaches it. Malformed values are still refused, as the server refuses them.
|
|
71
|
+
const owned = [];
|
|
72
|
+
const localViewer = viewer === null
|
|
73
|
+
? null
|
|
74
|
+
: {
|
|
75
|
+
username: viewer.username ?? `user-${viewer.id}`,
|
|
76
|
+
get modelVersions() {
|
|
77
|
+
return owned;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
const fake = createFakeStorage({
|
|
82
|
+
manifest: { scopes: manifest.scopes, storage: manifest.storage },
|
|
83
|
+
// Latched at init, like the mode: a sign-in remounts the frame.
|
|
84
|
+
viewer: localViewer,
|
|
85
|
+
});
|
|
86
|
+
const http = (method, path, opts) => {
|
|
87
|
+
collectIds(opts?.body, owned);
|
|
88
|
+
return fake.http(method, path, opts);
|
|
89
|
+
};
|
|
90
|
+
return createDocumentStorage(http);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return refusingStorage('local_preview_invalid_manifest', `local preview storage: block.manifest.json declares storage the local preview store cannot serve — ` +
|
|
94
|
+
`${error.message}. Nothing was sent to the server.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Every integer a write body carries that could be a model version id. */
|
|
98
|
+
function collectIds(value, into) {
|
|
99
|
+
if (typeof value === 'number') {
|
|
100
|
+
if (Number.isInteger(value) && value >= 1 && !into.includes(value))
|
|
101
|
+
into.push(value);
|
|
102
|
+
}
|
|
103
|
+
else if (Array.isArray(value)) {
|
|
104
|
+
for (const item of value)
|
|
105
|
+
collectIds(item, into);
|
|
106
|
+
}
|
|
107
|
+
else if (value !== null && typeof value === 'object') {
|
|
108
|
+
for (const item of Object.values(value))
|
|
109
|
+
collectIds(item, into);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** A store that refuses every call with one LOCAL, non-retryable error. */
|
|
113
|
+
function refusingStorage(code, message) {
|
|
114
|
+
const refuse = () => Promise.reject(new StorageError(LOCAL_PREVIEW_ERROR_STATUS, code, message, { code, message, error: message }));
|
|
115
|
+
// Every method of `StorageCollection`, so `satisfies` fails the build if one is added.
|
|
116
|
+
const collection = {
|
|
117
|
+
get: refuse,
|
|
118
|
+
getMany: refuse,
|
|
119
|
+
put: refuse,
|
|
120
|
+
create: refuse,
|
|
121
|
+
delete: refuse,
|
|
122
|
+
list: refuse,
|
|
123
|
+
increment: refuse,
|
|
124
|
+
mark: refuse,
|
|
125
|
+
unmark: refuse,
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
collection: () => collection,
|
|
129
|
+
info: refuse,
|
|
130
|
+
};
|
|
131
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* inside a civitai.com page, `app.host`. Outside one, `createSignIn()` gets the token.
|
|
4
4
|
*/
|
|
5
5
|
export { initialize } from './app/index.js';
|
|
6
|
-
export type { AppClient, BlockAppClient, BlockInitializeOptions, TokenInitializeOptions, } from './app/index.js';
|
|
6
|
+
export type { AppClient, BlockAppClient, BlockInitializeOptions, StorageMode, TokenInitializeOptions, } from './app/index.js';
|
|
7
7
|
export { createSignIn, SignInError } from './sign-in/index.js';
|
|
8
8
|
export type { SignIn, SignInOptions } from './sign-in/index.js';
|
|
9
9
|
export { SCOPES } from './session/index.js';
|
|
@@ -17,7 +17,7 @@ export { StorageError } from './doc-store/index.js';
|
|
|
17
17
|
export type { DocumentAuthor, DocumentCallOptions, DocumentListQuery, DocumentPage, DocumentStorage, DocumentWhereValue, StorageCollection, StorageCollectionInfo, StorageDocument, StorageErrorCode, StorageIndexType, StorageInfo, StorageLimits, StorageUsage, WriteOptions, } from './doc-store/index.js';
|
|
18
18
|
export { SubmissionUnconfirmedError } from './workflows/index.js';
|
|
19
19
|
export type { WorkflowCallOptions, WorkflowGetOptions, WorkflowListQuery, WorkflowListResult, WorkflowSubmitOptions, WorkflowWatchOptions, WorkflowsClient, } from './workflows/index.js';
|
|
20
|
-
export type { AiToolkitLrScheduler, AiToolkitOptimizerType, AiToolkitPlainEcosystem, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AppWorkflow, AppWorkflowImage, BlockModel3dOutput, BlockModelSubstitution, BlockPublishedModel, BlockSourceImage, BlockStepQuote, BlockStepToolCall, BlockTextToImageParams, BlockTrainedEpoch, BlockTrainingQuote, BlockWorkflowSnapshot, BlockWorkflowStepSnapshot, BuzzAccountType, InlineComfyNode, KnownBlockWorkflowErrorCode, ModelSubstitutionReason, WithWorkflowStepRefs, WorkflowBody, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyInline, WorkflowBodyCustomComfyRecipe, WorkflowBodyPassThroughStep, WorkflowBodyStep, WorkflowBodyTextToImage, WorkflowBodyTraining, WorkflowBodyWorkflow, WorkflowBodyWorkflowStep, WorkflowStepRef, } from './workflows/types.generated.js';
|
|
20
|
+
export type { AiToolkitLrScheduler, AiToolkitOptimizerType, AiToolkitPlainEcosystem, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AppWorkflow, AppWorkflowImage, BlockModel3dOutput, BlockModelSubstitution, BlockPublishedModel, BlockSourceImage, BlockStepQuote, BlockStepToolCall, BlockTextToImageParams, BlockTrainedEpoch, BlockTrainingDatasetItem, BlockTrainingQuote, BlockWorkflowSnapshot, BlockWorkflowStepSnapshot, BuzzAccountType, InlineComfyNode, KnownBlockWorkflowErrorCode, ModelSubstitutionReason, WithWorkflowStepRefs, WorkflowBody, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyInline, WorkflowBodyCustomComfyRecipe, WorkflowBodyPassThroughStep, WorkflowBodyStep, WorkflowBodyTextToImage, WorkflowBodyTraining, WorkflowBodyWorkflow, WorkflowBodyWorkflowStep, WorkflowStepRef, } from './workflows/types.generated.js';
|
|
21
21
|
export { isTerminal, isTerminalStatus } from './orchestration/index.js';
|
|
22
22
|
export type { OrchestrationClient, Step, StepTemplate, WaitOptions, Workflow, WorkflowPage, WorkflowQuery, WorkflowStatus, WorkflowTemplate, } from './orchestration/index.js';
|
|
23
23
|
export { createHost } from './host/index.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, reported to the host as `sdkVersion` in `BLOCK_READY`. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.19.0";
|
|
@@ -938,16 +938,16 @@ export type AiToolkitTrainingParams = AiToolkitTrainingParamsBase & ({
|
|
|
938
938
|
modelVariant: 'base' | 'sft';
|
|
939
939
|
});
|
|
940
940
|
/**
|
|
941
|
-
* Train a LoRA with the ai-toolkit engine on
|
|
942
|
-
*
|
|
941
|
+
* Train a LoRA with the ai-toolkit engine on images the VIEWER uploaded through
|
|
942
|
+
* the host — the App Blocks `kind: 'training'` workflow body.
|
|
943
943
|
*
|
|
944
944
|
* ## Availability — read before building on it
|
|
945
945
|
*
|
|
946
946
|
* - 🔴 Behind the host flag **`app-blocks-training-kind`**, which ships OFF and is
|
|
947
947
|
* evaluated per viewer. Where it is off, every training call is refused
|
|
948
948
|
* (`training from apps is not enabled`).
|
|
949
|
-
* - **Page apps only** (`/apps/run/<slug>`). The model slot answers
|
|
950
|
-
*
|
|
949
|
+
* - **Page apps only** (`/apps/run/<slug>`). The model slot answers
|
|
950
|
+
* `RUN_TRAINING` with an error, and the server refuses a non-page token.
|
|
951
951
|
* - The manifest must declare **`ai:write:budgeted`** and the viewer must have
|
|
952
952
|
* granted it; the viewer must be signed in.
|
|
953
953
|
* - **Not from a development or review session**: dev tokens (`dev:live`, dev
|
|
@@ -956,17 +956,22 @@ export type AiToolkitTrainingParams = AiToolkitTrainingParamsBase & ({
|
|
|
956
956
|
*
|
|
957
957
|
* ## The flow
|
|
958
958
|
*
|
|
959
|
-
* 1. **
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
959
|
+
* 1. **Upload** — `useImageUpload({ purpose: 'trainingData' }).open()`
|
|
960
|
+
* (`OPEN_IMAGE_UPLOAD { purpose: 'trainingData' }`). The viewer picks up to
|
|
961
|
+
* {@link BLOCK_TRAINING_DATASET_MAX_ITEMS} images in a host modal; you get
|
|
962
|
+
* `[{ url, air }]` ({@link BlockTrainingDataImage}), or `null` on cancel.
|
|
963
|
+
* Pair each `air` with your caption to build {@link WorkflowBodyTraining.items}.
|
|
963
964
|
* 2. **Quote** — `useBuzzWorkflow().estimate(body)` with this body, `quoteId`
|
|
964
965
|
* ABSENT. The reply's {@link BlockWorkflowSnapshot.trainingQuote} is the
|
|
965
966
|
* orchestrator's own price for exactly this run, stored server-side.
|
|
966
967
|
* 3. **Run** — `useRunTraining().runTraining({ ...body, quoteId })`
|
|
967
968
|
* (`RUN_TRAINING`). Civitai shows the viewer a consent dialog in ITS chrome —
|
|
968
969
|
* price, base model, length, dataset size, all read back from the server,
|
|
969
|
-
* never from your body
|
|
970
|
+
* never from your body, with the host's own copies of the uploads as
|
|
971
|
+
* thumbnails — and submits only on their click. A quote naming an image this
|
|
972
|
+
* host did not upload for the block is refused with no dialog
|
|
973
|
+
* (`training data not uploaded here`) — upload in the same page session as the
|
|
974
|
+
* run; a reload forgets the uploads.
|
|
970
975
|
* 4. **Follow** — `useBuzzWorkflow().watch(snapshot.workflowId)`.
|
|
971
976
|
*
|
|
972
977
|
* 🔴 `useBuzzWorkflow().submit()` REFUSES THIS BODY before sending it. The server
|
|
@@ -982,8 +987,12 @@ export type AiToolkitTrainingParams = AiToolkitTrainingParamsBase & ({
|
|
|
982
987
|
*/
|
|
983
988
|
export interface WorkflowBodyTraining {
|
|
984
989
|
kind: 'training';
|
|
985
|
-
/**
|
|
986
|
-
|
|
990
|
+
/**
|
|
991
|
+
* The dataset: 1…{@link BLOCK_TRAINING_DATASET_MAX_ITEMS} images this host
|
|
992
|
+
* uploaded for the viewer, each with its caption — see
|
|
993
|
+
* {@link BlockTrainingDatasetItem}. One blob may appear once.
|
|
994
|
+
*/
|
|
995
|
+
items: BlockTrainingDatasetItem[];
|
|
987
996
|
/** The only engine accepted. */
|
|
988
997
|
engine: 'ai-toolkit';
|
|
989
998
|
/**
|
|
@@ -992,12 +1001,17 @@ export interface WorkflowBodyTraining {
|
|
|
992
1001
|
*/
|
|
993
1002
|
model: string;
|
|
994
1003
|
params: AiToolkitTrainingParams;
|
|
995
|
-
/**
|
|
1004
|
+
/**
|
|
1005
|
+
* At most {@link BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS} characters. The host runs
|
|
1006
|
+
* it through the same trigger-word check Civitai's trainer and Training Studio
|
|
1007
|
+
* use, at the estimate and again at the submit, before anything is reserved; a
|
|
1008
|
+
* refused trigger word fails the estimate.
|
|
1009
|
+
*/
|
|
996
1010
|
triggerWord: string;
|
|
997
1011
|
/**
|
|
998
1012
|
* At most {@link BLOCK_TRAINING_SAMPLE_PROMPTS_MAX} prompts of at most
|
|
999
|
-
* {@link BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS} characters each.
|
|
1000
|
-
*
|
|
1013
|
+
* {@link BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS} characters each. Checked for
|
|
1014
|
+
* those bounds only, as in the Training Studio.
|
|
1001
1015
|
*/
|
|
1002
1016
|
samplePrompts: string[];
|
|
1003
1017
|
/**
|
|
@@ -1027,7 +1041,7 @@ export interface WorkflowBodyTraining {
|
|
|
1027
1041
|
* unmodified. Bounded by a platform-internal denylist and by `maxBuzz`, not
|
|
1028
1042
|
* by a registry.
|
|
1029
1043
|
* - {@link WorkflowBodyTraining} (`kind: 'training'`) — an ai-toolkit LoRA run
|
|
1030
|
-
* on
|
|
1044
|
+
* on images the viewer uploaded through the host. ESTIMATE ONLY
|
|
1031
1045
|
* through `useBuzzWorkflow`; it runs through `useRunTraining()`, whose
|
|
1032
1046
|
* host-chrome consent dialog is the only way to confirm its quote.
|
|
1033
1047
|
* - {@link WorkflowBodyWorkflow} (`kind: 'workflow'`) — up to
|
|
@@ -1395,8 +1409,8 @@ export interface BlockWorkflowSnapshot {
|
|
|
1395
1409
|
*
|
|
1396
1410
|
* `cost.total` on the same snapshot equals `trainingQuote.total`.
|
|
1397
1411
|
* `expiresAt` is an ISO-8601 timestamp: after it the run is refused and you
|
|
1398
|
-
* estimate again. `imageCount` is the
|
|
1399
|
-
*
|
|
1412
|
+
* estimate again. `imageCount` is the size of the dataset the server quoted
|
|
1413
|
+
* (`items.length`).
|
|
1400
1414
|
*
|
|
1401
1415
|
* Present ONLY on the reply to a `kind: 'training'` estimate. Absent on hosts
|
|
1402
1416
|
* that predate civitai/civitai#5434.
|
|
@@ -1529,7 +1543,7 @@ export interface BlockTrainingQuote {
|
|
|
1529
1543
|
quoteId: string;
|
|
1530
1544
|
/** The quoted price, in Buzz. */
|
|
1531
1545
|
total: number;
|
|
1532
|
-
/** How many images the
|
|
1546
|
+
/** How many images the quoted dataset holds. */
|
|
1533
1547
|
imageCount: number;
|
|
1534
1548
|
/** ISO-8601. The quote cannot be run after this. */
|
|
1535
1549
|
expiresAt: string;
|
|
@@ -1710,3 +1724,20 @@ export interface AppWorkflow {
|
|
|
1710
1724
|
*/
|
|
1711
1725
|
model3dOutputs?: BlockModel3dOutput[];
|
|
1712
1726
|
}
|
|
1727
|
+
/**
|
|
1728
|
+
* One image of a training dataset ({@link WorkflowBodyTraining.items}) — a blob
|
|
1729
|
+
* this host uploaded for the viewer, and the caption to train it with. The
|
|
1730
|
+
* Training Studio's own `{ air, caption }` shape.
|
|
1731
|
+
*
|
|
1732
|
+
* Host bounds (`blockTrainingDatasetItemsSchema`, civitai/civitai#6461): `air`
|
|
1733
|
+
* is the blob's key ({@link BlockTrainingDataImage.air}) or its Civitai-hosted
|
|
1734
|
+
* blob url — the server keeps only the key; `caption` at most
|
|
1735
|
+
* {@link BLOCK_TRAINING_CAPTION_MAX_CHARS} characters; no other keys;
|
|
1736
|
+
* 1…{@link BLOCK_TRAINING_DATASET_MAX_ITEMS} items per body, and one blob at
|
|
1737
|
+
* most once (keys compare case-insensitively). A body outside them is refused at
|
|
1738
|
+
* the estimate.
|
|
1739
|
+
*/
|
|
1740
|
+
export interface BlockTrainingDatasetItem {
|
|
1741
|
+
air: string;
|
|
1742
|
+
caption: string;
|
|
1743
|
+
}
|
package/package.json
CHANGED