@nodaro/shared 2.7.0 → 2.10.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/dist/index.cjs +281 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +503 -95
- package/dist/index.d.ts +503 -95
- package/dist/index.js +257 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/llm-models.test.ts +137 -10
- package/src/__tests__/organizations-types.test.ts +61 -0
- package/src/__tests__/producer-types.test.ts +11 -0
- package/src/__tests__/prompt-length-limits.test.ts +7 -3
- package/src/__tests__/seedance2-continuation-ref.test.ts +2 -3
- package/src/__tests__/video-analysis-catalog-sync.test.ts +1 -1
- package/src/__tests__/video-analysis-pricing.test.ts +2 -2
- package/src/__tests__/video-mode-for-inputs.test.ts +74 -0
- package/src/combine-transitions.ts +38 -0
- package/src/credit-estimators/video-utils.ts +1 -1
- package/src/featured-entities.ts +1 -1
- package/src/i18n/types.ts +12 -14
- package/src/index.ts +17 -1
- package/src/llm-models.ts +115 -2
- package/src/model-catalog.ts +19 -0
- package/src/model-constants.ts +52 -7
- package/src/organizations/index.ts +2 -0
- package/src/organizations/types.ts +152 -0
- package/src/organizations/views.ts +220 -0
- package/src/producer-types.ts +6 -0
- package/src/smart-cut-windows.ts +8 -17
- package/src/suno-track-sources.ts +23 -0
- package/src/surround.ts +10 -90
- package/src/video-analysis-pricing.ts +25 -36
- package/src/workflow-export.ts +41 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MemberStatus,
|
|
3
|
+
OrgKind,
|
|
4
|
+
OrgRole,
|
|
5
|
+
OrgSettings,
|
|
6
|
+
OrgStatus,
|
|
7
|
+
WorkspaceRole,
|
|
8
|
+
WorkspaceSettings,
|
|
9
|
+
} from "./types.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What the organization endpoints RETURN.
|
|
13
|
+
*
|
|
14
|
+
* `types.ts` carries what a client must send and the codes it must dispatch
|
|
15
|
+
* on; this carries the other half of the same wire contract — the shapes that
|
|
16
|
+
* come back. It lives here for the same reason: the SDK, the CLI, the app and
|
|
17
|
+
* any third-party integration all read these, and a shape described in three
|
|
18
|
+
* places is a shape that drifts in two of them.
|
|
19
|
+
*
|
|
20
|
+
* CONTRACT ONLY, like its sibling. There is no resolution logic here, no
|
|
21
|
+
* access rule, no vocabulary — a view names fields, it does not decide who
|
|
22
|
+
* may see them. Fields the server omits for a caller without the standing to
|
|
23
|
+
* see them are OPTIONAL here rather than nullable: absent means "not for
|
|
24
|
+
* you", `null` means "genuinely unset", and a client that cannot tell those
|
|
25
|
+
* apart will render the wrong thing.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export interface OrganizationView {
|
|
29
|
+
id: string
|
|
30
|
+
slug: string
|
|
31
|
+
name: string
|
|
32
|
+
kind: OrgKind
|
|
33
|
+
status: OrgStatus
|
|
34
|
+
ownerUserId: string
|
|
35
|
+
settings: OrgSettings
|
|
36
|
+
termsAcceptedAt: string | null
|
|
37
|
+
createdAt: string
|
|
38
|
+
updatedAt: string
|
|
39
|
+
/** The CALLER's role. Absent on a read that did not establish membership. */
|
|
40
|
+
role?: OrgRole
|
|
41
|
+
memberStatus?: MemberStatus
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface OrgMemberView {
|
|
45
|
+
userId: string
|
|
46
|
+
role: OrgRole
|
|
47
|
+
status: MemberStatus
|
|
48
|
+
joinedAt: string
|
|
49
|
+
email: string | null
|
|
50
|
+
displayName: string | null
|
|
51
|
+
avatarUrl: string | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WorkspaceView {
|
|
55
|
+
id: string
|
|
56
|
+
orgId: string
|
|
57
|
+
name: string
|
|
58
|
+
slug: string
|
|
59
|
+
description: string | null
|
|
60
|
+
settings: WorkspaceSettings
|
|
61
|
+
defaultProjectId: string | null
|
|
62
|
+
archived: boolean
|
|
63
|
+
archivedAt: string | null
|
|
64
|
+
createdAt: string
|
|
65
|
+
updatedAt: string
|
|
66
|
+
/** The CALLER's role. Absent on a read that did not establish membership. */
|
|
67
|
+
role?: WorkspaceRole
|
|
68
|
+
memberStatus?: MemberStatus
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface WorkspaceMemberView {
|
|
72
|
+
userId: string
|
|
73
|
+
role: WorkspaceRole
|
|
74
|
+
displayName: string | null
|
|
75
|
+
avatarUrl: string | null
|
|
76
|
+
addedAt: string
|
|
77
|
+
/** Workspace admins only — absent for a plain member's read. */
|
|
78
|
+
status?: MemberStatus
|
|
79
|
+
creditCap?: number | null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Where an invitation stands. `expired` is derived from `expiresAt`, not stored. */
|
|
83
|
+
export type InvitationState = "open" | "accepted" | "revoked" | "expired"
|
|
84
|
+
|
|
85
|
+
export interface InvitationView {
|
|
86
|
+
id: string
|
|
87
|
+
orgId: string
|
|
88
|
+
workspaceId: string | null
|
|
89
|
+
email: string
|
|
90
|
+
orgRole: OrgRole
|
|
91
|
+
workspaceRole: WorkspaceRole | null
|
|
92
|
+
invitedBy: string | null
|
|
93
|
+
state: InvitationState
|
|
94
|
+
expiresAt: string
|
|
95
|
+
acceptedAt: string | null
|
|
96
|
+
revokedAt: string | null
|
|
97
|
+
createdAt: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* One row per address a create/resend was asked for.
|
|
102
|
+
*
|
|
103
|
+
* `link` is present whenever the address was NOT emailed — an install with no
|
|
104
|
+
* mail provider, or a delivery that failed. A client MUST surface it: the
|
|
105
|
+
* invitation exists either way, and without the link nobody can reach it.
|
|
106
|
+
*/
|
|
107
|
+
export interface InvitationDelivery {
|
|
108
|
+
email: string
|
|
109
|
+
status: "sent" | "link_only" | "failed"
|
|
110
|
+
link?: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What an invitee sees BEFORE signing in — the one organization read that
|
|
115
|
+
* needs no token. `email` comes back masked, so the invitee can recognise
|
|
116
|
+
* their own address without the link disclosing it to whoever holds it.
|
|
117
|
+
*/
|
|
118
|
+
export interface InvitationPreview {
|
|
119
|
+
orgName: string
|
|
120
|
+
kind: OrgKind
|
|
121
|
+
vocabulary: Record<string, string>
|
|
122
|
+
inviterName: string | null
|
|
123
|
+
workspaceName: string | null
|
|
124
|
+
email: string
|
|
125
|
+
expiresAt: string
|
|
126
|
+
state: InvitationState
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface JoinCodeView {
|
|
130
|
+
code: string
|
|
131
|
+
enabled: boolean
|
|
132
|
+
rotatedAt: string
|
|
133
|
+
rotatedBy: string | null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* What `GET /v1/me` reports about the caller's memberships.
|
|
138
|
+
*
|
|
139
|
+
* Deliberately a SUMMARY, not the full views above: this is the payload every
|
|
140
|
+
* client loads on every session start, and it answers one question — what am
|
|
141
|
+
* I a member of, and what may I call each thing. Names, roles, and the
|
|
142
|
+
* resolved vocabulary are here because a switcher cannot render without them;
|
|
143
|
+
* descriptions, timestamps and default projects are not, because a switcher
|
|
144
|
+
* never shows them and `GET /v1/orgs/:id` exists.
|
|
145
|
+
*
|
|
146
|
+
* The settings block is narrowed to the three keys a CLIENT can act on. The
|
|
147
|
+
* rest of an organization's settings are enforced server-side, and shipping
|
|
148
|
+
* them here would invite a client to enforce them badly.
|
|
149
|
+
*/
|
|
150
|
+
export interface OrganizationSummary {
|
|
151
|
+
id: string
|
|
152
|
+
slug: string
|
|
153
|
+
name: string
|
|
154
|
+
kind: OrgKind
|
|
155
|
+
status: OrgStatus
|
|
156
|
+
/** The caller's own role and standing — always present in this payload. */
|
|
157
|
+
role: OrgRole
|
|
158
|
+
memberStatus: MemberStatus
|
|
159
|
+
settings: {
|
|
160
|
+
personal_space_enabled: boolean
|
|
161
|
+
allowed_email_domains: string[]
|
|
162
|
+
vocabulary_overrides: Record<string, string>
|
|
163
|
+
}
|
|
164
|
+
/** Resolved labels, so no client hard-codes "Class" or "Team". */
|
|
165
|
+
vocabulary: Record<string, string>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface WorkspaceSummary {
|
|
169
|
+
id: string
|
|
170
|
+
orgId: string
|
|
171
|
+
name: string
|
|
172
|
+
slug: string
|
|
173
|
+
role: WorkspaceRole
|
|
174
|
+
memberStatus: MemberStatus
|
|
175
|
+
archived: boolean
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The organizations block on `GET /v1/me`.
|
|
180
|
+
*
|
|
181
|
+
* THREE distinct states, and a client that collapses them is wrong in a way
|
|
182
|
+
* users feel: the fields ABSENT means this install has no organizations at
|
|
183
|
+
* all; present and empty means the account belongs to none; and
|
|
184
|
+
* `organizationsUnavailable` means the lookup FAILED — in which case a
|
|
185
|
+
* client must KEEP whatever selection it already had, because telling someone
|
|
186
|
+
* their school vanished during a cache blip is worse than a stale switcher.
|
|
187
|
+
*/
|
|
188
|
+
export interface MeOrganizations {
|
|
189
|
+
organizations?: OrganizationSummary[]
|
|
190
|
+
workspaces?: WorkspaceSummary[]
|
|
191
|
+
lastWorkspaceId?: string | null
|
|
192
|
+
organizationsUnavailable?: boolean
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* One recorded action in an organization's audit log.
|
|
197
|
+
*
|
|
198
|
+
* `action` is an OPEN vocabulary and a client must not exhaust it: new
|
|
199
|
+
* actions are added as the product grows, and a switch that throws on an
|
|
200
|
+
* unknown one turns a new feature into a broken page. Render what you
|
|
201
|
+
* recognise, fall back to the raw string for the rest.
|
|
202
|
+
*
|
|
203
|
+
* `actor` is null for anything the system did on nobody's behalf.
|
|
204
|
+
*/
|
|
205
|
+
export interface OrgAuditEntry {
|
|
206
|
+
id: string
|
|
207
|
+
workspaceId: string | null
|
|
208
|
+
action: string
|
|
209
|
+
targetType: string | null
|
|
210
|
+
targetId: string | null
|
|
211
|
+
details: Record<string, unknown>
|
|
212
|
+
createdAt: string
|
|
213
|
+
actor: { userId: string; displayName: string | null; email: string | null } | null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** A cursor-paged read. The cursor is part of the answer, not a side channel. */
|
|
217
|
+
export interface OrgPage<T> {
|
|
218
|
+
data: T[]
|
|
219
|
+
nextCursor: string | null
|
|
220
|
+
}
|
package/src/producer-types.ts
CHANGED
|
@@ -79,6 +79,12 @@ export const VIDEO_PRODUCER_TYPES: ReadonlySet<string> = new Set([
|
|
|
79
79
|
"cinematic-avatar",
|
|
80
80
|
// Assemble Narrated Video: fits N (clip, voice) blocks into one MP4 → video.
|
|
81
81
|
"assemble-narrated-video",
|
|
82
|
+
// Still to Video: one still image + one audio track → MP4 (local FFmpeg,
|
|
83
|
+
// no provider). Emits generatedVideoUrl like every other ffmpeg video node.
|
|
84
|
+
"still-to-video",
|
|
85
|
+
// Slideshow: 2-100 stills + one optional audio track → MP4 (local FFmpeg,
|
|
86
|
+
// no provider). Same contract; images arrive via the image-collage lane.
|
|
87
|
+
"slideshow",
|
|
82
88
|
])
|
|
83
89
|
|
|
84
90
|
/**
|
package/src/smart-cut-windows.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Smart-cut
|
|
2
|
+
* Smart-cut SEARCH WINDOWS — the shared bound + clamp for
|
|
3
3
|
* generate-video-pro's `smartCutFramesPrev` / `smartCutFramesNext`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* default, which is byte-identical to the behavior before they were
|
|
11
|
-
* exposed.
|
|
12
|
-
*
|
|
13
|
-
* Why wider helps: a continuation can re-enact a longer stretch of the
|
|
14
|
-
* previous tail than 8 frames covers, and a match outside the window is
|
|
15
|
-
* simply never found — the boundary silently falls back to the fixed
|
|
16
|
-
* freeze-trims. recast pins 24/24 for exactly this reason.
|
|
5
|
+
* They bound how much of each side of a boundary the engine considers when
|
|
6
|
+
* it places the cut: N frames from the end of a segment and M from the start
|
|
7
|
+
* of the next. Absent → the engine's own default, byte-identical to the
|
|
8
|
+
* behavior before they were exposed. A boundary the engine cannot resolve
|
|
9
|
+
* inside the window falls back to the fixed freeze-trims; recast pins 24/24.
|
|
17
10
|
*
|
|
18
11
|
* Why a shared clamp: the canvas node (single-node Run) and the orchestrator
|
|
19
12
|
* (workflow Run) are two independent send paths into the same engine route,
|
|
@@ -23,10 +16,8 @@
|
|
|
23
16
|
* and the two paths cannot drift apart.
|
|
24
17
|
*/
|
|
25
18
|
|
|
26
|
-
/** Widest window the UI offers
|
|
27
|
-
*
|
|
28
|
-
* at 24fps, and every frame added past the real overlap only costs match
|
|
29
|
-
* time and invites a spurious pairing. */
|
|
19
|
+
/** Widest window the UI offers (the engine route itself accepts up to 48).
|
|
20
|
+
* Past this, added frames only cost search time and invite a false match. */
|
|
30
21
|
export const SMART_CUT_WINDOW_MAX = 24
|
|
31
22
|
/** Narrowest meaningful window — one frame each side. */
|
|
32
23
|
export const SMART_CUT_WINDOW_MIN = 1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node types whose output carries Suno chaining ids (`sunoTrackId` /
|
|
3
|
+
* `sunoTaskId`) for a downstream Suno node (extend / separate / replace /
|
|
4
|
+
* add-vocals / …) to chain off.
|
|
5
|
+
*
|
|
6
|
+
* One set for the three readers — the canvas resolver, the orchestrator
|
|
7
|
+
* resolver, and the config panels' "Inherited" hint (#819). They used to keep
|
|
8
|
+
* their own copies and drifted: the canvas read ids off a `suno-separate`
|
|
9
|
+
* (whose output is stems, not a track) while the orchestrator ignored it, so
|
|
10
|
+
* the same graph resolved on one path and not the other. Structural
|
|
11
|
+
* vocabulary only — node type names, no prompt content.
|
|
12
|
+
*/
|
|
13
|
+
export const SUNO_TRACK_SOURCE_TYPES: ReadonlySet<string> = new Set([
|
|
14
|
+
"suno-generate",
|
|
15
|
+
"suno-cover",
|
|
16
|
+
"suno-extend",
|
|
17
|
+
"suno-mashup",
|
|
18
|
+
"suno-replace-section",
|
|
19
|
+
"suno-add-instrumental",
|
|
20
|
+
"suno-add-vocals",
|
|
21
|
+
"suno-convert-wav",
|
|
22
|
+
"suno-upload-extend",
|
|
23
|
+
])
|
package/src/surround.ts
CHANGED
|
@@ -1,48 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Surround continuation — shared
|
|
2
|
+
* Surround continuation — the shared WIRE CONTRACT.
|
|
3
3
|
*
|
|
4
4
|
* The Location 360° "look-around" builds each ring view (45°, 90°, …) as an
|
|
5
|
-
* image-to-image continuation of the previous
|
|
6
|
-
* geometric continuity by handing the model a half-done frame: one edge holds
|
|
7
|
-
* the previous view's carried pixels, the rest is flat gray, and the model is
|
|
8
|
-
* asked to paint the gray region.
|
|
5
|
+
* image-to-image continuation of the previous one.
|
|
9
6
|
*
|
|
10
|
-
* This module owns the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* This module owns only what the route Zod schema, the SDK input type, and the
|
|
8
|
+
* worker all need to agree on: the direction enum and the carried-fraction
|
|
9
|
+
* defaults. The fill prompt lives in `@nodaro/prompts` (never published) and
|
|
10
|
+
* the compositing/harmonization engine is private.
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
|
-
* The
|
|
18
|
-
*
|
|
19
|
-
* PAN (horizontal — half-carry continuation):
|
|
20
|
-
* - `right` — turning right: the new frame's LEFT edge continues the previous
|
|
21
|
-
* view's RIGHT edge, so the carried band sits on the LEFT, painted on the RIGHT.
|
|
22
|
-
* - `left` — turning left: the new frame's RIGHT edge continues the previous
|
|
23
|
-
* view's LEFT edge, so the carried band sits on the RIGHT, painted on the LEFT.
|
|
24
|
-
* (Mirror of `right`. Lets studio chain BOTH ways from a keyframe, capping
|
|
25
|
-
* chain depth so quality doesn't compound down a long one-way chain.)
|
|
26
|
-
*
|
|
27
|
-
* TILT (vertical — thin-strip, subject-driven re-render):
|
|
28
|
-
* - `up` — tilting straight up: render the open SKY overhead. A thin strip of
|
|
29
|
-
* the establishing shot's TOP edge is carried into the new frame's BOTTOM for
|
|
30
|
-
* a soft horizon transition; the rest is painted as sky (NOT a mirrored
|
|
31
|
-
* landscape).
|
|
32
|
-
* - `down` — tilting straight down: render the GROUND below. A thin strip of the
|
|
33
|
-
* BOTTOM edge is carried into the new frame's TOP.
|
|
14
|
+
* The camera move a continuation represents: `right` / `left` pan the view
|
|
15
|
+
* horizontally, `up` / `down` tilt it vertically.
|
|
34
16
|
*/
|
|
35
17
|
export const SURROUND_DIRECTIONS = ["right", "left", "up", "down"] as const
|
|
36
18
|
export type SurroundDirection = (typeof SURROUND_DIRECTIONS)[number]
|
|
37
19
|
|
|
38
|
-
/**
|
|
20
|
+
/** Default carried fraction for a horizontal pan. */
|
|
39
21
|
export const DEFAULT_CARRIED_FRACTION = 0.5
|
|
40
|
-
/**
|
|
41
|
-
* Tilts carry only a thin horizon strip. Carrying half of a horizontal frame is
|
|
42
|
-
* exactly what makes the model echo/mirror the landscape vertically instead of
|
|
43
|
-
* rendering what's actually overhead/underfoot — so tilts keep the carry small
|
|
44
|
-
* and let the tilt prompt drive the subject.
|
|
45
|
-
*/
|
|
22
|
+
/** Default carried fraction for a vertical tilt. */
|
|
46
23
|
export const TILT_CARRIED_FRACTION = 0.12
|
|
47
24
|
|
|
48
25
|
/** True for the vertical tilt directions (up/down), false for the pans. */
|
|
@@ -54,60 +31,3 @@ export function isTiltDirection(direction: SurroundDirection): boolean {
|
|
|
54
31
|
export function defaultCarriedFraction(direction: SurroundDirection): number {
|
|
55
32
|
return isTiltDirection(direction) ? TILT_CARRIED_FRACTION : DEFAULT_CARRIED_FRACTION
|
|
56
33
|
}
|
|
57
|
-
|
|
58
|
-
/** Which edge of the NEW frame holds the carried pixels vs the painted region. */
|
|
59
|
-
const EDGE: Record<SurroundDirection, { carried: string; painted: string }> = {
|
|
60
|
-
right: { carried: "left", painted: "right" },
|
|
61
|
-
left: { carried: "right", painted: "left" },
|
|
62
|
-
up: { carried: "bottom", painted: "top" },
|
|
63
|
-
down: { carried: "top", painted: "bottom" },
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** What a tilt must actually render (NOT a continuation of the landscape). */
|
|
67
|
-
const TILT_SUBJECT: Record<"up" | "down", { word: string; subject: string; where: string }> = {
|
|
68
|
-
up: {
|
|
69
|
-
word: "up",
|
|
70
|
-
subject: "the open sky directly overhead — sky, clouds, or (for an interior) the canopy or ceiling",
|
|
71
|
-
where: "overhead",
|
|
72
|
-
},
|
|
73
|
-
down: {
|
|
74
|
-
word: "down",
|
|
75
|
-
subject: "the ground directly below — terrain, floor, or water surface",
|
|
76
|
-
where: "below",
|
|
77
|
-
},
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Build the fill prompt the model receives alongside the half-carry composite.
|
|
82
|
-
*
|
|
83
|
-
* `userPrompt` (an optional scene hint from the caller) is woven in front. PAN
|
|
84
|
-
* directions get the seamless-continuation prompt (with the anti-golden-hour
|
|
85
|
-
* negative that fights the documented warm-regrade drift). TILT directions get a
|
|
86
|
-
* subject-forcing prompt — render the sky / ground overhead / below, explicitly
|
|
87
|
-
* NOT a mirrored landscape — which is what stops the vertical echo.
|
|
88
|
-
*/
|
|
89
|
-
export function buildSurroundFillPrompt(direction: SurroundDirection, userPrompt?: string): string {
|
|
90
|
-
const scene = userPrompt && userPrompt.trim() ? `${userPrompt.trim()}. ` : ""
|
|
91
|
-
const { carried, painted } = EDGE[direction]
|
|
92
|
-
|
|
93
|
-
if (direction === "up" || direction === "down") {
|
|
94
|
-
const t = TILT_SUBJECT[direction]
|
|
95
|
-
return (
|
|
96
|
-
`${scene}` +
|
|
97
|
-
`This is a camera tilted straight ${t.word} from the same scene. The ${carried} strip holds real, finished pixels from the edge of the horizon view; the ${painted} region is flat gray and MUST be painted as ${t.subject}. ` +
|
|
98
|
-
`Render what is genuinely ${t.where} — do NOT repeat, mirror, or continue the landscape, and do NOT draw a horizon line or distant scenery in the painted region. ` +
|
|
99
|
-
`CRITICAL: keep the ${carried} strip unchanged and match the scene's EXACT lighting, time of day, white balance, and color grade — the same light as the ${carried} strip; no golden hour, no sunset, no warm relight, no cinematic regrade. ` +
|
|
100
|
-
`Blend smoothly into the ${carried} strip with no visible seam. No people, no text, no labels, no watermarks.`
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// pan (right / left)
|
|
105
|
-
return (
|
|
106
|
-
`${scene}` +
|
|
107
|
-
`This is a partial frame: the ${carried} portion contains real, finished pixels and the ${painted} portion is flat gray that MUST be painted in. ` +
|
|
108
|
-
`Paint ONLY the ${painted} gray region as a natural, seamless continuation of the ${carried} portion — same scene, same perspective, continuing the horizon, geometry, and content across the boundary with no break. ` +
|
|
109
|
-
`Keep the ${carried} portion completely unchanged. ` +
|
|
110
|
-
`CRITICAL: do NOT change the lighting, exposure, white balance, or time of day. Match the ${carried} portion's EXACT light, color temperature, and contrast across the whole frame — if it is flat overcast daylight, keep flat overcast daylight. No golden hour, no sunset, no warm relight, no cinematic regrade. ` +
|
|
111
|
-
`The seam between the ${carried} and ${painted} portions must be invisible. No people, no text, no labels, no watermarks.`
|
|
112
|
-
)
|
|
113
|
-
}
|
|
@@ -9,14 +9,12 @@
|
|
|
9
9
|
* `buildVideoAnalysisCreditId` + `/v1/credits/model-cost`) all derive from
|
|
10
10
|
* these.
|
|
11
11
|
*
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* node. A cross-check test in that private package guards this table so the
|
|
19
|
-
* public numbers can't silently drift from the formula.
|
|
12
|
+
* The rate constants and the formula that GENERATE these numbers live
|
|
13
|
+
* PRIVATELY in the `@nodaroai/cloud-plugins` package — never in this public
|
|
14
|
+
* repo. They were first moved out of this package (published Apache-2.0 on
|
|
15
|
+
* npm), then out of the app repo entirely alongside the rest of the
|
|
16
|
+
* video-analysis node. A cross-check test in that private package guards
|
|
17
|
+
* this table so the public numbers can't silently drift from the formula.
|
|
20
18
|
*
|
|
21
19
|
* `VIDEO_ANALYSIS_BUCKET_CREDITS` below is the precomputed OUTPUT of that
|
|
22
20
|
* private formula for every (model × bucket) combination — a plain credit
|
|
@@ -24,9 +22,8 @@
|
|
|
24
22
|
* `VIDEO_CLIP_CREDITS` uses in `film-pricing.ts`. It is what the frontend's
|
|
25
23
|
* client-side cost preview (`estimateNodeCredits` in
|
|
26
24
|
* workflow-editor/types.ts) reads instead of calling the formula directly.
|
|
27
|
-
* The formula's own test in `@nodaroai/cloud-plugins`
|
|
28
|
-
*
|
|
29
|
-
* against it and fails on drift. There is deliberately NO app-side formula to
|
|
25
|
+
* The formula's own test in `@nodaroai/cloud-plugins` cross-checks this
|
|
26
|
+
* table against it and fails on drift. There is deliberately NO app-side formula to
|
|
30
27
|
* check against — it was moved private in 2026-07 and the old backend test
|
|
31
28
|
* went with it.
|
|
32
29
|
*
|
|
@@ -65,27 +62,20 @@ export const VIDEO_ANALYSIS_WINDOW = { LEN: WINDOW_LEN, STRIDE: WINDOW_STRIDE, O
|
|
|
65
62
|
// not 110). The plugin's cost test now covers sentinels as well, so this class
|
|
66
63
|
// of drift fails CI instead of shipping.
|
|
67
64
|
// REGENERATED 2026-07-31 with the smart-tier re-base (formula inputs moved in
|
|
68
|
-
// the plugin: sampling 24→6 fps
|
|
69
|
-
//
|
|
70
|
-
// per
|
|
71
|
-
// Net effect: smart drops 27–47% per bucket — the 24 fps token spend was also
|
|
72
|
-
// partly paying for media tokens the provider clamped and never counted — and
|
|
73
|
-
// the economy rows tick up 3–6% from the prompt-token true-up.
|
|
65
|
+
// the plugin: sampling 24→6 fps, which proved equal on content and better on
|
|
66
|
+
// cast stability; system-prompt and per-window output-token constants updated).
|
|
67
|
+
// Net effect: smart drops 27–47% per bucket, and the economy rows tick up 3–6%.
|
|
74
68
|
//
|
|
75
|
-
// REGENERATED 2026-08-03 — V1 hybrid-smart reprice
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// judge/refine terms instead of an implicit share of a single-pass budget.
|
|
86
|
-
// This is the full, honest reprice Tal approved, including the economy tiers
|
|
87
|
-
// (fast 33->185 @180s ends a below-cost combine exposure that existed at the
|
|
88
|
-
// old price). Net effect, per bucket (every row rises):
|
|
69
|
+
// REGENERATED 2026-08-03 — V1 hybrid-smart reprice, from the plugin's own
|
|
70
|
+
// generator. This is the V1 true-up of the earlier provisional
|
|
71
|
+
// judge/refine/frame-judge constants (the constants themselves, like the rest
|
|
72
|
+
// of the formula, stay private in the plugin repo — never in this public
|
|
73
|
+
// package). `smart` is now a multi-roll plan that always refines its merged
|
|
74
|
+
// result (`selectionMode` does not apply to it), and every multi-roll tier
|
|
75
|
+
// now carries its own explicit judge/refine terms instead of an implicit
|
|
76
|
+
// share of a single-pass budget.
|
|
77
|
+
// The economy tiers rise too (fast 33->185 @180s).
|
|
78
|
+
// Net effect, per bucket (every row rises):
|
|
89
79
|
//
|
|
90
80
|
// gemini-3-flash 60s 24->180 180s 33->185 360s 86-> 514 600s 143-> 846
|
|
91
81
|
// gemini-3.6-flash 60s 65->203 180s 92->218 360s 237-> 598 600s 395-> 986
|
|
@@ -119,9 +109,8 @@ export const VIDEO_ANALYSIS_BUCKET_CREDITS: Record<string, number> = {
|
|
|
119
109
|
"video-analysis:mixed:180s": 289,
|
|
120
110
|
"video-analysis:mixed:360s": 724,
|
|
121
111
|
"video-analysis:mixed:600s": 1169,
|
|
122
|
-
// SMART — the accuracy tier, and since the 2026-08-03
|
|
123
|
-
//
|
|
124
|
-
// fast + 2 pro donor rolls, always refined (`selectionMode` does not apply
|
|
112
|
+
// SMART — the accuracy tier, and since the 2026-08-03 re-plan a multi-roll
|
|
113
|
+
// plan like the others, always refined (`selectionMode` does not apply
|
|
125
114
|
// here — smart always refines; it never offers a cheaper "choose" path).
|
|
126
115
|
// Priced above the economy tiers because it genuinely costs more to run;
|
|
127
116
|
// the only tier whose accuracy is validated against a hand-counted edit
|
|
@@ -168,7 +157,7 @@ export function videoAnalysisNumWindows(bucketSec: number): number {
|
|
|
168
157
|
* Precomputed credit cost for the `video-audit` node ("AI Audit") — the same
|
|
169
158
|
* pattern as `VIDEO_ANALYSIS_BUCKET_CREDITS` above: the OUTPUT of the private
|
|
170
159
|
* `videoAuditBucketCredits` formula in `@nodaroai/cloud-plugins`
|
|
171
|
-
* (
|
|
160
|
+
* (in the plugin repo), a plain lookup table never a
|
|
172
161
|
* formula, cross-checked against that package's own cost test. Shares the
|
|
173
162
|
* SAME duration-bucket ladder as video-analysis (`VIDEO_ANALYSIS_DURATION_BUCKETS`
|
|
174
163
|
* / `pickVideoAnalysisBucket`) — the audit re-watches the same clip, so it
|
|
@@ -195,7 +184,7 @@ export function videoAnalysisNumWindows(bucketSec: number): number {
|
|
|
195
184
|
* `buildVideoAnalysisCreditId`.
|
|
196
185
|
*
|
|
197
186
|
* Values pasted verbatim from the plugin generator's output
|
|
198
|
-
* (
|
|
187
|
+
* (the plugin repo's bucket generator) —
|
|
199
188
|
* never hand computed. The plugin's cost test cross-checks every row.
|
|
200
189
|
*/
|
|
201
190
|
export const VIDEO_AUDIT_BUCKET_CREDITS: Record<string, number> = {
|
package/src/workflow-export.ts
CHANGED
|
@@ -74,6 +74,45 @@ export interface WorkflowExportLocation {
|
|
|
74
74
|
styleLock?: boolean | null
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** A media URL referenced from a node's data, located by node + field path. */
|
|
78
|
+
export interface WorkflowMediaRef {
|
|
79
|
+
nodeId: string
|
|
80
|
+
nodeLabel?: string
|
|
81
|
+
/** Dot/bracket path inside `node.data`, e.g. `imageUrl` or `referenceImageUrls[1]`. */
|
|
82
|
+
field: string
|
|
83
|
+
url: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Export-time portability analysis (#866). A bundle exported from a private
|
|
88
|
+
* host carries media URLs only that host can serve (`http://localhost:3000/
|
|
89
|
+
* storage/…`, a LAN address, a `.internal` name); imported anywhere else, the
|
|
90
|
+
* nodes fail at Run time with an opaque provider fetch error. The exporter
|
|
91
|
+
* lists those URLs here so the person exporting is told BEFORE sharing, and
|
|
92
|
+
* an importer can explain what will not load. Absent when every media URL is
|
|
93
|
+
* publicly routable.
|
|
94
|
+
*/
|
|
95
|
+
export interface WorkflowPortability {
|
|
96
|
+
unreachableMedia: WorkflowMediaRef[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* What the importer did about the bundle's media (#866). Publicly reachable
|
|
101
|
+
* media that is not already on the importing instance's own storage is
|
|
102
|
+
* copied there (`rehosted`) so the workflow runs from local copies; media on
|
|
103
|
+
* a host the importer cannot reach is left as-is and listed (`unreachable`);
|
|
104
|
+
* anything declined for another reason (too large, not a media type, over
|
|
105
|
+
* the per-import cap, upload failed) is listed with the reason (`skipped`).
|
|
106
|
+
*/
|
|
107
|
+
export interface WorkflowImportReport {
|
|
108
|
+
rehosted: number
|
|
109
|
+
unreachable: WorkflowMediaRef[]
|
|
110
|
+
skipped: Array<WorkflowMediaRef & { reason: string }>
|
|
111
|
+
/** Anything else the importer should know, e.g. copies were made but the
|
|
112
|
+
* workflow could not be updated to use them. */
|
|
113
|
+
notes?: string[]
|
|
114
|
+
}
|
|
115
|
+
|
|
77
116
|
export interface WorkflowExport {
|
|
78
117
|
version: 1
|
|
79
118
|
exportedAt: string
|
|
@@ -87,6 +126,8 @@ export interface WorkflowExport {
|
|
|
87
126
|
creatures?: WorkflowExportCreature[]
|
|
88
127
|
locations: WorkflowExportLocation[]
|
|
89
128
|
}
|
|
129
|
+
/** Present only when the bundle references media another instance cannot fetch. */
|
|
130
|
+
portability?: WorkflowPortability
|
|
90
131
|
}
|
|
91
132
|
|
|
92
133
|
/**
|