videobook 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -2
  3. package/dist/archive-b2.js +1 -1
  4. package/dist/asset-media-projection.js +161 -0
  5. package/dist/cli/args.js +42 -3
  6. package/dist/cli/config-command.js +17 -1
  7. package/dist/cli/doctor-command.js +15 -0
  8. package/dist/cli/har-command.js +86 -0
  9. package/dist/cli/main.js +13 -1
  10. package/dist/command-reference.js +3 -1
  11. package/dist/content-store.js +2 -1
  12. package/dist/diagnostics.js +2 -2
  13. package/dist/env-secrets.js +1 -0
  14. package/dist/generation-har.js +106 -0
  15. package/dist/generation-reference-mentions.js +19 -0
  16. package/dist/goal-plan-v3.js +11 -1
  17. package/dist/goal-plan.js +1 -1
  18. package/dist/goal-store.js +2 -2
  19. package/dist/har-capture.js +175 -0
  20. package/dist/har-export.js +238 -0
  21. package/dist/har-index.js +98 -0
  22. package/dist/har-retention.js +73 -0
  23. package/dist/har-settings.js +27 -0
  24. package/dist/jobs/_alibaba-upload.js +2 -4
  25. package/dist/jobs/_fal-upload.js +2 -4
  26. package/dist/jobs/_image-edit-adapter.js +3 -1
  27. package/dist/jobs/_kie-upload.js +2 -4
  28. package/dist/jobs/assess-media-probes.js +94 -0
  29. package/dist/jobs/generate-media.js +10 -1
  30. package/dist/jobs/image-subjects.js +3 -1
  31. package/dist/jobs/kie-paygate.js +10 -3
  32. package/dist/jobs/register-all.js +2 -0
  33. package/dist/jobs/run-notebook.js +20 -2
  34. package/dist/jobs/xai-paygate.js +5 -2
  35. package/dist/library-asset-projection.js +141 -0
  36. package/dist/lightning-defaults.js +4 -4
  37. package/dist/media/adapters/kie.js +2 -1
  38. package/dist/media/catalog.js +8 -2
  39. package/dist/media/cost.js +1 -1
  40. package/dist/media/descriptor.js +7 -4
  41. package/dist/media/frontier-gaps.js +0 -1
  42. package/dist/media/har-endpoint.js +25 -0
  43. package/dist/media/json-schema.js +3 -0
  44. package/dist/media/preferred-defaults.js +161 -0
  45. package/dist/media/prompt-dialects.js +9 -1
  46. package/dist/media-library.js +158 -153
  47. package/dist/notebook-sticky-settings.js +60 -0
  48. package/dist/package-version.js +18 -0
  49. package/dist/posts/dropbox-auth.js +1 -1
  50. package/dist/posts/post-metrics.js +2 -1
  51. package/dist/provider-usage.js +1 -18
  52. package/dist/queue.js +12 -1
  53. package/dist/serve.js +2 -0
  54. package/dist/tools/api-status.js +1 -1
  55. package/dist/tools/generate-media.js +3 -0
  56. package/dist/tools/generation-payload.js +17 -2
  57. package/dist/tools/get-generation-har.js +43 -0
  58. package/dist/tools/index.js +4 -0
  59. package/dist/tools/list-cell-generations.js +19 -0
  60. package/dist/tools/reconcile-project-work.js +52 -193
  61. package/dist/tools/set-har-capture.js +30 -0
  62. package/dist/tools/videobook-domain.js +16 -1
  63. package/dist/upload.js +2 -1
  64. package/dist/usage-providers.js +48 -10
  65. package/dist/workspace-action-groups.js +2 -2
  66. package/dist/x/account.js +1 -1
  67. package/dist/x/authenticated-handle.js +1 -1
  68. package/dist/x/oauth-flow.js +1 -1
  69. package/dist/x/post-to-x.js +2 -1
  70. package/dist/x/x-tokens.js +2 -1
  71. package/package.json +15 -14
  72. package/ui/assets/{alive-captions-duTKsyll.js → alive-captions-CnLK22ED.js} +1 -1
  73. package/ui/assets/confirm-bodies-BaalBxp1.js +1 -0
  74. package/ui/assets/{fullscreen-COrmiHPn.js → fullscreen-DTJQJ6dX.js} +1 -1
  75. package/ui/assets/{goal-ui-D5Xsm2EY.js → goal-ui-CN-kZTEO.js} +2 -2
  76. package/ui/assets/index-CTPstLYq.css +1 -0
  77. package/ui/assets/index-Dp81yChE.js +37 -0
  78. package/ui/assets/inspector-jeyma7HL.js +1 -0
  79. package/ui/assets/{jobs-ui-DdP2Re41.js → jobs-ui-D6S3LJ4j.js} +1 -1
  80. package/ui/assets/{music-video-B709JZqa.js → music-video-CDqr0X6G.js} +1 -1
  81. package/ui/assets/ui-BBM3YCY6.css +1 -0
  82. package/ui/assets/ui-Cqs1-YNg.js +2 -0
  83. package/ui/assets/vendor-BWf-WCsY.js +19 -0
  84. package/ui/assets/{vendor-flow-CKiuzaeM.js → vendor-flow-DPvi8ynA.js} +1 -1
  85. package/ui/assets/vendor-icons-D3KrGMJY.js +1 -0
  86. package/ui/assets/vendor-mcp-BSR9acpG.js +60 -0
  87. package/ui/assets/videobook-b9VODNOA.js +21 -0
  88. package/ui/index.html +14 -14
  89. package/ui/assets/confirm-bodies--2sc9DbH.js +0 -1
  90. package/ui/assets/index-DEKl00St.js +0 -37
  91. package/ui/assets/index-DWfR79lA.css +0 -1
  92. package/ui/assets/inspector-EBp3IhvH.js +0 -1
  93. package/ui/assets/ui-Oirr9VOf.css +0 -1
  94. package/ui/assets/ui-jRPWsNUw.js +0 -2
  95. package/ui/assets/vendor-BFupLz37.js +0 -19
  96. package/ui/assets/vendor-icons-CGqhCIXV.js +0 -1
  97. package/ui/assets/vendor-mcp-DQEeVZH0.js +0 -67
  98. package/ui/assets/videobook-0DOLlhG2.js +0 -20
@@ -0,0 +1,141 @@
1
+ import { computeArtifactStatus } from "videobook-engine";
2
+ import { EMPTY_TAG_SUMMARY, matchesTagFilter, readAssetTagSummaries, } from "./asset-tags.js";
3
+ import { assetDuration, assetMediaUrls, assetStreamProfile, findAssetMediaFile, groupByArtifactId, indexByArtifactId, PARTIAL_DOWNLOAD_RE, scheduleMissingVideoPoster, servedVideoProxyFile, } from "./asset-media-projection.js";
4
+ function metadataNumber(meta, key) {
5
+ const value = meta?.[key];
6
+ return typeof value === "number" ? value : null;
7
+ }
8
+ function metadataString(meta, key) {
9
+ const value = meta?.[key];
10
+ return typeof value === "string" ? value : null;
11
+ }
12
+ function readJobIndexes(engine) {
13
+ const pending = engine.jobs.pending.findAll();
14
+ const failures = engine.jobs.failures.findAll();
15
+ const work = engine.jobs.artifactWork.list();
16
+ const streams = engine.streams.list();
17
+ return {
18
+ pending: indexByArtifactId(pending.ok ? pending.value : []),
19
+ failures: indexByArtifactId(failures.ok ? failures.value : []),
20
+ work: indexByArtifactId(work.ok ? work.value : []),
21
+ streams: groupByArtifactId(streams.ok ? streams.value : []),
22
+ };
23
+ }
24
+ async function readCandidate(engine, artifact, jobs, tags) {
25
+ const [manifest, metadata] = await Promise.all([
26
+ engine.files.manifest(artifact.artifactId, { includeDotfiles: true }),
27
+ engine.metadata.artifacts.read(artifact.artifactId, "original"),
28
+ ]);
29
+ const files = manifest.ok ? manifest.value.files : [];
30
+ const meta = metadata.ok ? metadata.value : null;
31
+ const mediaFile = findAssetMediaFile(files, artifact);
32
+ const work = jobs.work.get(artifact.artifactId) ?? null;
33
+ const status = computeArtifactStatus({
34
+ kind: artifact.kind,
35
+ fileNames: new Set(files.map((file) => file.name)),
36
+ primaryMediaName: mediaFile?.name ?? null,
37
+ hasOriginalMetadata: metadata.ok,
38
+ hasPartFile: files.some((file) => PARTIAL_DOWNLOAD_RE.test(file.name)),
39
+ lockData: engine.jobs.locks.get(artifact.path),
40
+ pendingTask: jobs.pending.get(artifact.artifactId) ?? null,
41
+ generationError: jobs.failures.get(artifact.artifactId) ?? null,
42
+ artifactRow: work
43
+ ? { status: work.status, meta: work.meta, deadlineAt: work.deadlineAt }
44
+ : null,
45
+ });
46
+ return {
47
+ id: artifact.artifactId,
48
+ artifact_id: artifact.artifactId,
49
+ ...(artifact.label ? { label: artifact.label } : {}),
50
+ type: artifact.kind,
51
+ created_at: new Date(artifact.createdAt).toISOString(),
52
+ status,
53
+ prompt: metadataString(meta, "prompt"),
54
+ object_hash: mediaFile?.objectHash ?? null,
55
+ tags,
56
+ source: {
57
+ artifact,
58
+ files,
59
+ meta,
60
+ mediaFile,
61
+ work,
62
+ streams: jobs.streams.get(artifact.artifactId) ?? [],
63
+ },
64
+ };
65
+ }
66
+ export async function readLibraryCandidates(engine, input) {
67
+ const artifacts = engine.artifacts.list({ sort: "newest" })
68
+ .filter((artifact) => input.kinds.has(artifact.kind));
69
+ const tagsByArtifact = readAssetTagSummaries(engine, artifacts.map((artifact) => artifact.artifactId));
70
+ const tagged = artifacts.filter((artifact) => matchesTagFilter(tagsByArtifact.get(artifact.artifactId), input.tags));
71
+ if (tagged.length === 0)
72
+ return [];
73
+ const jobs = readJobIndexes(engine);
74
+ return Promise.all(tagged.map((artifact) => readCandidate(engine, artifact, jobs, tagsByArtifact.get(artifact.artifactId) ?? EMPTY_TAG_SUMMARY)));
75
+ }
76
+ function libraryAssetWork(work) {
77
+ return work
78
+ ? {
79
+ status: work.status,
80
+ meta: work.meta,
81
+ ownerKind: work.ownerKind,
82
+ deadlineAt: work.deadlineAt,
83
+ }
84
+ : null;
85
+ }
86
+ function framesPerSecond(meta, frameRate) {
87
+ const metadataFps = Number(meta?.fps);
88
+ if (Number.isFinite(metadataFps) && metadataFps > 0)
89
+ return metadataFps;
90
+ if (!frameRate || frameRate.denominator <= 0)
91
+ return null;
92
+ const streamFps = frameRate.numerator / frameRate.denominator;
93
+ return Number.isFinite(streamFps) && streamFps > 0 ? streamFps : null;
94
+ }
95
+ export async function projectLibraryAsset(engine, bookId, candidate) {
96
+ const { artifact, files, meta, mediaFile, work, streams } = candidate.source;
97
+ const profile = assetStreamProfile(streams, meta);
98
+ const proxyMetadata = artifact.kind === "video" && files.some((file) => file.name === "proxy.mp4")
99
+ ? await engine.metadata.artifacts.read(artifact.artifactId, "proxy")
100
+ : { ok: false, error: { code: "NOT_FOUND", message: "no proxy" } };
101
+ const proxyFile = servedVideoProxyFile({
102
+ bookId,
103
+ artifact,
104
+ files,
105
+ mediaFile,
106
+ proxyMetadata,
107
+ work,
108
+ sourceProfile: profile.sourceProfile,
109
+ });
110
+ await scheduleMissingVideoPoster(engine, {
111
+ bookId,
112
+ artifact,
113
+ files,
114
+ mediaFile,
115
+ status: candidate.status,
116
+ });
117
+ return {
118
+ id: candidate.id,
119
+ artifact_id: candidate.artifact_id,
120
+ ...(candidate.label ? { label: candidate.label } : {}),
121
+ type: candidate.type,
122
+ created_at: candidate.created_at,
123
+ ...assetMediaUrls({ bookId, artifact, files, mediaFile, proxyFile }),
124
+ object_hash: candidate.object_hash,
125
+ status: candidate.status,
126
+ assetWork: libraryAssetWork(work),
127
+ width: metadataNumber(meta, "width"),
128
+ height: metadataNumber(meta, "height"),
129
+ origin: metadataString(meta, "origin"),
130
+ prompt: candidate.prompt,
131
+ duration: assetDuration({
132
+ bookId,
133
+ artifactId: artifact.artifactId,
134
+ meta,
135
+ mediaFile,
136
+ profile,
137
+ }),
138
+ fps: framesPerSecond(meta, profile.frameRate),
139
+ tags: candidate.tags,
140
+ };
141
+ }
@@ -1,12 +1,12 @@
1
1
  export const LIGHTNING_SCENE_MODEL = "kie:nano-banana-2-1";
2
- export const LIGHTNING_SCENE_ASPECT_RATIO = "16:9";
3
- export const LIGHTNING_SCENE_RESOLUTION = "2K";
2
+ export const LIGHTNING_SCENE_ASPECT_RATIO = "9:16";
3
+ export const LIGHTNING_SCENE_RESOLUTION = "4K";
4
4
  export const LIGHTNING_SCENE_OUTPUT_FORMAT = "jpg";
5
5
  export const LIGHTNING_SCENE_PROMPT_WORDS = 25;
6
6
  export const LIGHTNING_VIDEO_MODEL = "kie:bytedance/seedance-2-5";
7
- export const LIGHTNING_VIDEO_ASPECT_RATIO = "16:9";
7
+ export const LIGHTNING_VIDEO_ASPECT_RATIO = "9:16";
8
8
  export const LIGHTNING_VIDEO_RESOLUTION = "1080p";
9
- export const LIGHTNING_VIDEO_DURATION_SECONDS = 13;
9
+ export const LIGHTNING_VIDEO_DURATION_SECONDS = 12;
10
10
  export const LIGHTNING_VIDEO_OUTPUT_FORMAT = "mp4";
11
11
  export const LIGHTNING_VIDEO_GENERATE_AUDIO = true;
12
12
  export const LIGHTNING_VIDEO_PROMPT_WORDS = 60;
@@ -1,5 +1,6 @@
1
1
  import { setTimeout as delay } from "node:timers/promises";
2
- import { KieError, submitMediaJob } from "@apicity/kie";
2
+ import { submitMediaJob } from "../../usage-providers.js";
3
+ import { KieError } from "@apicity/kie";
3
4
  import { createKie } from "../../usage-providers.js";
4
5
  import { requireApiKey } from "../../api-key.js";
5
6
  import { authorizeKiePaygate, ensurePaygateSecret, mintKieOtp } from "../../jobs/kie-paygate.js";
@@ -22,7 +22,7 @@ const SEEDANCE_US_REFERENCE_MODES = [
22
22
  ];
23
23
  const XAI_MODES = [
24
24
  { id: "i2v", label: "Image to video", roles: ["first_frame"] },
25
- { id: "r2v", label: "References", roles: ["image"] },
25
+ { id: "r2v", label: "References", roles: ["image"], defaults: { duration: 10 } },
26
26
  { id: "v2v", label: "Edit video", roles: ["video"], requireAny: ["video"] },
27
27
  ];
28
28
  const MACHGEN_FRAMES = { id: "i2v", label: "Frames", roles: ["first_frame", "last_frame"], requireAny: ["first_frame"] };
@@ -157,10 +157,11 @@ export const MODEL_ENTRIES = [
157
157
  },
158
158
  { id: "kie:grok-imagine/image-to-video", media: "video", operations: ["image_to_video"], guide: "grok_imagine" },
159
159
  { id: "kie:grok-imagine/text-to-video", media: "video", operations: ["text_to_video"], guide: "grok_imagine", defaults: { duration: 6, resolution: "480p" } },
160
+ { id: "kie:grok-imagine-video-1-5-preview", media: "video", operations: ["image_to_video"], title: "Grok Imagine Video 1.5 Preview (Kie)", guide: "grok_imagine", dialect: "natural_strip_frames" },
160
161
  { id: "kie:grok-imagine/extend", media: "video", operations: ["extend_video"], hidden: true },
161
162
  { id: "kie:grok-imagine/upscale", media: "video", operations: ["upscale_video"], hidden: true },
162
163
  { id: "kie:kling-3.0-omni/text-to-video", media: "video", operations: ["text_to_video"], guide: "kling30", dialect: "natural", defaults: { customize_multi_shots: false } },
163
- { id: "kie:kling-3.0-omni/image-to-video", media: "video", operations: ["image_to_video"], guide: "kling30", dialect: "natural" },
164
+ { id: "kie:kling-3.0-omni/image-to-video", media: "video", operations: ["image_to_video"], guide: "kling30", dialect: "natural", defaults: { aspect_ratio: "auto" }, pinnedDefaults: ["aspect_ratio"] },
164
165
  { id: "kie:kling-3.0-omni/reference-to-video", media: "video", operations: ["reference_to_video"], guide: "kling30", dialect: "natural", defaults: { audio: false } },
165
166
  {
166
167
  id: "kie:kling-3.0-omni/transformation",
@@ -174,6 +175,8 @@ export const MODEL_ENTRIES = [
174
175
  ],
175
176
  },
176
177
  { id: "kie:kling-3.0/motion-control", media: "video", operations: ["motion_control"], guide: "kling30", dialect: "natural", defaults: { mode: "720p" } },
178
+ { id: "kie:kling-4.0/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "kling4" },
179
+ { id: "kie:kling-4.0/keyframe-to-video", media: "video", operations: ["image_to_video"], dialect: "kling4_keyframes", refs: { image: "keyframe_urls" } },
177
180
  { id: "kie:gemini-omni-video", media: "video", operations: ["image_to_video", "edit_video"], dialect: "natural_source_video", defaults: { duration: "8" }, modes: [{ id: "i2v", label: "Image to video", roles: ["image"] }, { id: "v2v", label: "Edit video", roles: ["video", "image"], requireAny: ["video"] }], refItem: { video: { start: 0, ends: 8 } }, aliases: { clip_start: "video_list.0.start", clip_end: "video_list.0.ends" } },
178
181
  { id: "kie:wan/3-0-video", media: "video", operations: ["text_to_video", "image_to_video", "reference_to_video"], dialect: "seedance", modes: SEEDANCE_MODES },
179
182
  { id: "kie:wan/3-0-video-prime", media: "video", operations: ["text_to_video", "image_to_video", "reference_to_video"], dialect: "seedance", modes: SEEDANCE_MODES },
@@ -186,8 +189,11 @@ export const MODEL_ENTRIES = [
186
189
  { id: "kie:minimax-h3/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "natural", rules: { refDurationMs: MiniMaxH3ReferenceClipDurationMs } },
187
190
  { id: "kie:volcengine/video-to-video-lip-sync", media: "video", operations: ["lip_sync"] },
188
191
  { id: "kie:omnihuman-1-5", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural" },
192
+ { id: "kie:omnihuman-2-0", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural" },
189
193
  { id: "kie:vidu-q4/image-to-video", media: "video", operations: ["image_to_video"], dialect: "natural_kinds" },
190
194
  { id: "kie:vidu-q4/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "natural_kinds" },
195
+ { id: "kie:pixverse-v6-max/text-to-video", media: "video", operations: ["text_to_video"], defaults: { quality: "1080p" } },
196
+ { id: "kie:pixverse-v6-max/image-to-video", media: "video", operations: ["image_to_video"], dialect: "strip_frames", defaults: { quality: "1080p" } },
191
197
  { id: "kie:veo3", media: "video", operations: ["text_to_video", "image_to_video"], title: "Veo 3 (Kie)" },
192
198
  { id: "kie:veo3_fast", media: "video", operations: ["text_to_video", "image_to_video"], title: "Veo 3 Fast (Kie)" },
193
199
  { id: "xai:grok-imagine-video", media: "video", operations: ["text_to_video", "image_to_video", "reference_to_video", "edit_video"], guide: "grok_imagine", title: "Grok Imagine Video (xAI)", dialect: "natural", refs: { first_frame: "image", audio: null }, modes: XAI_MODES },
@@ -1,4 +1,4 @@
1
- import { createFal } from "@apicity/fal";
1
+ import { createFal } from "../usage-providers.js";
2
2
  import { computeEstimate, createFalEstimateCache, resolveFalDynamicEstimate, } from "@apicity/cost";
3
3
  import { buildWireRequest } from "./request.js";
4
4
  const FAL_ESTIMATE_TTL_MS = 5 * 60 * 1000;
@@ -1,9 +1,10 @@
1
1
  import { MODEL_DISPLAY } from "@apicity/cost";
2
2
  import { modelEntry } from "./catalog.js";
3
3
  import { parseModelId } from "./model-id.js";
4
+ import { preferredDefaults } from "./preferred-defaults.js";
4
5
  import { claimedFields, resolveSlot } from "./ref-fields.js";
5
6
  import { wireSpec } from "./wire.js";
6
- import { enumValues, numberBounds, propertyAt, schemaTypes, stringMaxLength, toJsonSchema, } from "./json-schema.js";
7
+ import { acceptsNoValue, enumValues, numberBounds, propertyAt, schemaTypes, stringMaxLength, toJsonSchema, } from "./json-schema.js";
7
8
  import { REF_ROLES, } from "./types.js";
8
9
  const PROMPT_CANDIDATES = ["prompt", "text", "instruction", "description"];
9
10
  const cache = new Map();
@@ -74,7 +75,7 @@ function resolveOptions(entry, input, excluded, fixed) {
74
75
  const defaults = entry.defaults ?? {};
75
76
  const options = [];
76
77
  for (const [name, schema] of Object.entries(input.properties ?? {})) {
77
- if (excluded.has(name) || name in fixed)
78
+ if (excluded.has(name) || name in fixed || acceptsNoValue(schema))
78
79
  continue;
79
80
  options.push(optionSpec(name, name, schema, required.has(name), defaults));
80
81
  }
@@ -121,8 +122,10 @@ function build(entry) {
121
122
  excluded.add(top);
122
123
  if (promptTop)
123
124
  excluded.add(promptTop);
125
+ const preferred = preferredDefaults(entry, resolveOptions(entry, input, excluded, wire.fixed));
126
+ const tuned = { ...entry, defaults: { ...entry.defaults, ...preferred } };
124
127
  return {
125
- entry,
128
+ entry: tuned,
126
129
  id: entry.id,
127
130
  provider: parsed.provider,
128
131
  apiId: parsed.apiId,
@@ -132,7 +135,7 @@ function build(entry) {
132
135
  promptField,
133
136
  promptRequired: promptTop !== undefined && promptField === promptTop && (input.required ?? []).includes(promptTop),
134
137
  slots,
135
- options: resolveOptions(entry, input, excluded, wire.fixed),
138
+ options: resolveOptions(tuned, input, excluded, wire.fixed),
136
139
  modes: entry.modes ? [...entry.modes] : defaultModes(slots),
137
140
  };
138
141
  }
@@ -31,7 +31,6 @@ export const FRONTIER_CATALOG_GAPS = {
31
31
  "fal:bytedance.seedance2p0.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
32
32
  "fal:blackforestlabs.flux3.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
33
33
  "fal:xai.grokImagineVideo.v1p5.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
34
- "kie:grok-imagine-video-1-5-preview": "This exact endpoint is not in the Videobook catalog.",
35
34
  "xai:v1.videos.generations.imageToVideo": "The catalog uses grok-imagine-video, not the ranked 1.5 model id.",
36
35
  "fal:alibaba.happyHorse.v1p1.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
37
36
  "fal:bytedance.seedance2p5.referenceToVideo": "This exact endpoint is not in the Videobook catalog.",
@@ -0,0 +1,25 @@
1
+ import { isKieVeoModel } from "./wire.js";
2
+ export function generationHarEndpoint(model) {
3
+ switch (model.provider) {
4
+ case "kie":
5
+ if (isKieVeoModel(model.apiId))
6
+ return "api.v1.veo.generate";
7
+ if (model.apiId === "suno/generate")
8
+ return "api.v1.generate";
9
+ if (model.apiId === "suno/sounds-generate")
10
+ return "api.v1.generate.sounds";
11
+ return "api.v1.jobs.createTask";
12
+ case "fal": return "v1.queue.submit";
13
+ case "xai": return "v1.videos.generations";
14
+ case "alibaba": return "api.v1.services.aigc.multimodalGeneration.generation";
15
+ case "machgen": return "api.v0.generate";
16
+ case "elevenlabs":
17
+ switch (model.apiId) {
18
+ case "sound-generation": return "v1.soundGeneration";
19
+ case "music": return "v1.music";
20
+ case "video-to-music": return "v1.music.videoToMusic";
21
+ case "text-to-speech": return "v1.textToSpeech";
22
+ default: throw new Error(`Unsupported ElevenLabs model ${model.apiId}`);
23
+ }
24
+ }
25
+ }
@@ -43,6 +43,9 @@ export function isKeyframeItem(schema) {
43
43
  && branch.properties?.image_url !== undefined
44
44
  && branch.properties?.frame_index !== undefined);
45
45
  }
46
+ export function acceptsNoValue(schema) {
47
+ return schema.not !== undefined && Object.keys(schema.not).length === 0;
48
+ }
46
49
  export function isArraySchema(schema) {
47
50
  return branches(schema).some((branch) => branch.type === "array");
48
51
  }
@@ -0,0 +1,161 @@
1
+ const PREFERRED_DURATION_SECONDS = 12;
2
+ const PREFERRED_ASPECT_RATIO = "9:16";
3
+ const PREFERRED_IMAGE_SIZE_PRESET = "portrait_16_9";
4
+ const IMAGE_SIZE_PRESETS = new Set(["square_hd", "square", "portrait_4_3", "portrait_16_9", "landscape_4_3", "landscape_16_9"]);
5
+ const GROUP_FIELDS = {
6
+ duration: ["duration"],
7
+ aspect: ["aspect_ratio", "aspectRatio", "image_size"],
8
+ resolution: ["resolution", "output_resolution", "size", "mode"],
9
+ quality: ["quality"],
10
+ bitrate: ["bitrate_mode"],
11
+ };
12
+ const QUALITY_RANK = ["very_low", "low", "basic", "standard", "medium", "high"];
13
+ const UNTIMED_VIDEO_OPERATIONS = new Set(["edit_video", "lip_sync", "upscale_video", "motion_control"]);
14
+ export function stickySettingGroup(name) {
15
+ const entry = Object.entries(GROUP_FIELDS).find(([, fields]) => fields.includes(name));
16
+ return entry ? entry[0] : null;
17
+ }
18
+ function resolutionRank(value) {
19
+ if (typeof value === "number")
20
+ return value;
21
+ const match = /^(\d+(?:\.\d+)?)\s*(p|k|sq)?$/iu.exec(value.trim());
22
+ if (!match)
23
+ return null;
24
+ const amount = Number(match[1]);
25
+ return match[2]?.toLowerCase() === "k" ? amount * 1000 : amount;
26
+ }
27
+ function isResolutionEnum(values) {
28
+ return values.length > 0 && values.every((value) => resolutionRank(value) !== null);
29
+ }
30
+ function highestBy(values, rank) {
31
+ const ranked = values.flatMap((value) => {
32
+ const score = rank(value);
33
+ return score === null ? [] : [{ value, score }];
34
+ });
35
+ if (ranked.length === 0)
36
+ return undefined;
37
+ return ranked.reduce((best, candidate) => candidate.score > best.score ? candidate : best).value;
38
+ }
39
+ function qualityRank(value) {
40
+ const index = QUALITY_RANK.indexOf(String(value).toLowerCase());
41
+ return index < 0 ? null : index;
42
+ }
43
+ function isNumeric(option) {
44
+ return option.valueType === "number" || option.valueType === "integer" || option.valueType === "json";
45
+ }
46
+ function clampToBounds(option, value) {
47
+ const low = option.minimum ?? Number.NEGATIVE_INFINITY;
48
+ const high = option.maximum ?? Number.POSITIVE_INFINITY;
49
+ return Math.min(Math.max(value, low), high);
50
+ }
51
+ function nearestDurationChoice(values, target) {
52
+ const numeric = values.filter((value) => Number.isFinite(Number(value)));
53
+ if (numeric.length === 0)
54
+ return undefined;
55
+ const atOrBelow = numeric.filter((value) => Number(value) <= target);
56
+ const pool = atOrBelow.length > 0 ? atOrBelow : numeric;
57
+ return pool.reduce((best, candidate) => Math.abs(Number(candidate) - target) < Math.abs(Number(best) - target) ? candidate : best);
58
+ }
59
+ function preferredDuration(option) {
60
+ if (option.enumValues.length > 0)
61
+ return nearestDurationChoice(option.enumValues, PREFERRED_DURATION_SECONDS);
62
+ if (!isNumeric(option))
63
+ return undefined;
64
+ return clampToBounds(option, PREFERRED_DURATION_SECONDS);
65
+ }
66
+ function takesImageSizePreset(entry, option) {
67
+ if (option.name !== "image_size" || option.valueType !== "json" || !entry.id.startsWith("fal:"))
68
+ return false;
69
+ if (!entry.operations.includes("text_to_image"))
70
+ return false;
71
+ return option.defaultValue === undefined || IMAGE_SIZE_PRESETS.has(String(option.defaultValue));
72
+ }
73
+ function preferredAspect(entry, option) {
74
+ if (option.enumValues.includes(PREFERRED_ASPECT_RATIO))
75
+ return PREFERRED_ASPECT_RATIO;
76
+ return takesImageSizePreset(entry, option) ? PREFERRED_IMAGE_SIZE_PRESET : undefined;
77
+ }
78
+ function preferredResolution(option) {
79
+ if (!isResolutionEnum(option.enumValues.filter((value) => value !== "auto")))
80
+ return undefined;
81
+ return highestBy(option.enumValues, resolutionRank);
82
+ }
83
+ function preferredQuality(option) {
84
+ return highestBy(option.enumValues, qualityRank);
85
+ }
86
+ export function takesDuration(entry) {
87
+ return entry.media === "video" && entry.operations.some((operation) => !UNTIMED_VIDEO_OPERATIONS.has(operation));
88
+ }
89
+ function preferredValue(entry, option) {
90
+ if (option.name !== option.path || entry.pinnedDefaults?.includes(option.name))
91
+ return undefined;
92
+ const group = stickySettingGroup(option.name);
93
+ if (group === "duration")
94
+ return takesDuration(entry) ? preferredDuration(option) : undefined;
95
+ if (group === "aspect")
96
+ return preferredAspect(entry, option);
97
+ if (group === "resolution")
98
+ return preferredResolution(option);
99
+ if (group === "quality" || group === "bitrate")
100
+ return preferredQuality(option);
101
+ return undefined;
102
+ }
103
+ export function preferredDefaults(entry, options) {
104
+ if (entry.media !== "video" && entry.media !== "image")
105
+ return {};
106
+ return Object.fromEntries(options.flatMap((option) => {
107
+ const value = preferredValue(entry, option);
108
+ return value === undefined ? [] : [[option.name, value]];
109
+ }));
110
+ }
111
+ function sameResolution(left, right) {
112
+ const a = resolutionRank(left);
113
+ const b = resolutionRank(right);
114
+ return a !== null && a === b && String(left).toLowerCase().replace(/p$/u, "") === String(right).toLowerCase().replace(/p$/u, "");
115
+ }
116
+ function matchingChoice(choices, value) {
117
+ return choices.find((choice) => choice === value)
118
+ ?? choices.find((choice) => String(choice).toLowerCase() === String(value).toLowerCase())
119
+ ?? choices.find((choice) => typeof choice !== "boolean" && typeof value !== "boolean" && sameResolution(choice, value));
120
+ }
121
+ function acceptedStickyValue(control, value) {
122
+ if (control.options.length > 0)
123
+ return matchingChoice(control.options, value);
124
+ if (control.valueType !== "number")
125
+ return undefined;
126
+ const numeric = Number(value);
127
+ if (!Number.isFinite(numeric))
128
+ return undefined;
129
+ if (control.minimum !== undefined && numeric < control.minimum)
130
+ return undefined;
131
+ if (control.maximum !== undefined && numeric > control.maximum)
132
+ return undefined;
133
+ return numeric;
134
+ }
135
+ function isStickyValue(group, value) {
136
+ if (typeof value !== "string" && typeof value !== "number")
137
+ return false;
138
+ return group !== "resolution" || resolutionRank(value) !== null;
139
+ }
140
+ export function stickySettingsFrom(settings) {
141
+ return Object.fromEntries(Object.entries(settings).flatMap(([name, value]) => {
142
+ const group = stickySettingGroup(name);
143
+ if (!group || !isStickyValue(group, value))
144
+ return [];
145
+ return [[group, value]];
146
+ }));
147
+ }
148
+ function isStickySettingGroup(name) {
149
+ return Object.hasOwn(GROUP_FIELDS, name);
150
+ }
151
+ export function parseStickySettings(stored) {
152
+ return Object.fromEntries(Object.entries(stored).flatMap(([group, value]) => isStickySettingGroup(group) && isStickyValue(group, value) ? [[group, value]] : []));
153
+ }
154
+ export function stickySettingsFor(controls, sticky) {
155
+ return Object.fromEntries(controls.flatMap((control) => {
156
+ const group = stickySettingGroup(control.name);
157
+ const remembered = group ? sticky[group] : undefined;
158
+ const value = remembered === undefined ? undefined : acceptedStickyValue(control, remembered);
159
+ return value === undefined ? [] : [[control.name, value]];
160
+ }));
161
+ }
@@ -1,4 +1,4 @@
1
- import { replaceHappyHorseAssetMentions, replaceNaturalAssetMentions, rewriteImageRefMentionsToBrackets, rewriteSeedanceAssetMentions, rewriteViduReferenceMentions, stripRefMentions, } from "../generation-reference-mentions.js";
1
+ import { replaceHappyHorseAssetMentions, replaceNaturalAssetMentions, rewriteImageRefMentionsToBrackets, rewriteKling4FrameMentions, rewriteKling4ReferenceMentions, rewriteSeedanceAssetMentions, rewriteViduReferenceMentions, stripRefMentions, } from "../generation-reference-mentions.js";
2
2
  const SLOT_TOKEN = /@((image|video|audio)(\d+)|firstframe|lastframe)(?![\w-])/giu;
3
3
  function refsFor(refs, role) {
4
4
  return (refs[role] ?? []).filter((ref) => !/^https?:\/\//u.test(ref));
@@ -58,6 +58,14 @@ function rewrite(dialect, prompt, refs) {
58
58
  return rewriteImageRefMentionsToBrackets(prompt, refsFor(refs, "image"));
59
59
  case "vidu":
60
60
  return rewriteViduReferenceMentions(prompt, refsFor(refs, "image"), refsFor(refs, "audio"));
61
+ case "kling4":
62
+ return rewriteKling4ReferenceMentions(prompt, refsFor(refs, "image"), refsFor(refs, "video"));
63
+ case "kling4_keyframes":
64
+ return rewriteKling4FrameMentions(prompt, {
65
+ first: refsFor(refs, "first_frame"),
66
+ keyframes: refsFor(refs, "image"),
67
+ last: refsFor(refs, "last_frame"),
68
+ });
61
69
  case "happyhorse_edit":
62
70
  return replaceHappyHorseAssetMentions(prompt, refsFor(refs, "video")[0] ?? null, refsFor(refs, "image"));
63
71
  }