@gentbajko/slopify 2.4.0 → 3.0.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.
Files changed (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -1,7 +1,14 @@
1
1
  import { stageKinds } from "../../kernel/pipeline.js";
2
+ import { imageScaleProblem } from "../images/scale.js";
3
+ import { loudnessFields } from "../loudness/model.js";
4
+ import { pauseFields } from "../narration/pauses-model.js";
5
+ import { reviewFields } from "../reviews/rules.js";
2
6
  import { shortsSettingsProblems } from "../shorts/model.js";
7
+ import { ambientBedProblems, usesAmbientBed } from "../video/ambient-bed.js";
3
8
  import { usesAnimation, videoEditProblems } from "../video/edit-settings.js";
9
+ import { usesVoices, voicesProblems } from "../voices/model.js";
4
10
  import { sourceOf } from "./model.js";
11
+ import { shortModeFields } from "./short-mode.js";
5
12
  export const titleMax = 200;
6
13
  export const valueMax = 200;
7
14
  export const numberPerPromptMax = 20;
@@ -84,7 +91,8 @@ export function admit(input) {
84
91
  draft.outro?.mode === "llm" ||
85
92
  usesNarrationPreparation(draft) ||
86
93
  usesYoutubeDescription(draft) ||
87
- usesShorts(draft);
94
+ usesShorts(draft) ||
95
+ (usesVoices(draft) && draft.voices?.source === "attribute");
88
96
  if (needsLlm && !chosen(draft.llm)) {
89
97
  fields.push({ field: "llm", message: "Choose a text (LLM) provider and model." });
90
98
  }
@@ -93,6 +101,7 @@ export function admit(input) {
93
101
  }
94
102
  const voiced = draft.audio;
95
103
  fields.push(...narrationPreparationFields(draft));
104
+ fields.push(...voicesFields(draft));
96
105
  if (sources.audio === "generate") {
97
106
  if (voiced === undefined || !chosen(voiced)) {
98
107
  fields.push({ field: "audio", message: "Choose a narration provider and model." });
@@ -124,7 +133,15 @@ export function admit(input) {
124
133
  }
125
134
  fields.push(...youtubeDescriptionFields(draft));
126
135
  fields.push(...shortsFields(draft));
136
+ fields.push(...shortModeFields(draft));
127
137
  fields.push(...videoEditFields(draft));
138
+ fields.push(...ambientBedFields(draft, input.staged));
139
+ fields.push(...loudnessFields(draft.loudness));
140
+ fields.push(...pauseFields(draft));
141
+ fields.push(...referenceFields(draft));
142
+ fields.push(...reviewFields(draft));
143
+ if (usesReference(draft) && draft.reference?.source === "provide")
144
+ checkFile(input.staged, draft.provided.reference, "images", "provided.reference", "Upload the establishing image, or set Establishing image to Off in the Images section.", fields);
128
145
  checkProvided(draft, input.staged, fields);
129
146
  checkValues(draft, input.requiredSlots, fields);
130
147
  if (!Number.isFinite(draft.silenceGapSeconds) ||
@@ -207,6 +224,9 @@ function checkImagePrompts(draft, fields) {
207
224
  message: `A video can have at most ${imagesPerRunMax} images; these prompts ask for ${total}. Lower the numbers.`,
208
225
  });
209
226
  }
227
+ const scaleProblem = draft.imageScale === undefined ? undefined : imageScaleProblem(draft.imageScale);
228
+ if (scaleProblem !== undefined)
229
+ fields.push({ field: "imageScale", message: scaleProblem });
210
230
  }
211
231
  function checkProvided(draft, staged, fields) {
212
232
  const { sources, provided } = draft;
@@ -243,6 +263,26 @@ function checkProvided(draft, staged, fields) {
243
263
  checkFile(staged, id, "images", `provided.images.${index}`, "Pick an image.", fields);
244
264
  }
245
265
  }
266
+ checkShortsMusic(draft, staged, fields);
267
+ }
268
+ // The shorts' background music is optional, so only a file that was picked is checked, and
269
+ // only while Shorts is on. Named by where the control sits: Play's Export rail.
270
+ function checkShortsMusic(draft, staged, fields) {
271
+ const id = draft.provided.shortsMusic;
272
+ if (draft.shorts?.enabled !== true || id === undefined)
273
+ return;
274
+ const where = "under Outputs → Export → More shorts options → Background music";
275
+ const file = staged.find((candidate) => candidate.id === id);
276
+ if (file === undefined || file.stageKind !== "audio")
277
+ fields.push({
278
+ field: "shorts.music",
279
+ message: `The shorts' background music file is no longer available, so it can't be added to the project. Choose the file again ${where}, or remove it.`,
280
+ });
281
+ else if (file.state !== "staged")
282
+ fields.push({
283
+ field: "shorts.music",
284
+ message: `The shorts' background music is still uploading, so the run can't start yet. Wait for it to finish, or remove it ${where}.`,
285
+ });
246
286
  }
247
287
  // A run never starts with provided content that is missing or still copying.
248
288
  function checkFile(staged, id, kind, field, missing, fields) {
@@ -297,6 +337,21 @@ export function narrationPreparationFields(draft) {
297
337
  ]
298
338
  : [];
299
339
  }
340
+ // Multiple voices, checked only while narration is generated: a format left set with
341
+ // narration Off or uploaded asks for nothing.
342
+ export function voicesFields(draft) {
343
+ if (draft.voices === undefined || !usesVoices(draft))
344
+ return [];
345
+ const fields = [...voicesProblems(draft.voices)];
346
+ // Preparation adds delivery tags to the turns of speakers on Inworld TTS-2, so it needs one.
347
+ if (!blank(draft.narrationPrompt) &&
348
+ !draft.voices.speakers.some((speaker) => speaker.voice.provider === "inworld" && speaker.voice.model === "inworld-tts-2"))
349
+ fields.push({
350
+ field: "narrationPrompt",
351
+ message: "Narration preparation adds delivery cues only to speakers on Inworld's TTS-2 model, and no speaker uses it. Choose that model for a speaker under Speakers, or clear Narration Preparation in Audio → Advanced.",
352
+ });
353
+ return fields;
354
+ }
300
355
  // The YouTube description is written from the narration's word timings, so it needs
301
356
  // narration; it runs whether the video renders or only the WAV is exported.
302
357
  export function usesYoutubeDescription(draft) {
@@ -312,6 +367,30 @@ export function youtubeDescriptionFields(draft) {
312
367
  ]
313
368
  : [];
314
369
  }
370
+ // The establishing image belongs to the Images stage: it is made, and the images drawn with
371
+ // it, only while images are Generate.
372
+ export function usesReference(draft) {
373
+ return draft.reference !== undefined && draft.sources.images === "generate";
374
+ }
375
+ // Whether the thumbnail is drawn with it too: on unless turned off, and only for a thumbnail
376
+ // the image provider makes.
377
+ export function referenceForThumbnail(draft) {
378
+ return (usesReference(draft) &&
379
+ draft.reference?.thumbnail !== false &&
380
+ (draft.sources.thumbnail === "from_prompt" || draft.sources.thumbnail === "prompt_by_llm"));
381
+ }
382
+ export function referenceFields(draft) {
383
+ return usesReference(draft) &&
384
+ draft.reference?.source === "prompt" &&
385
+ (draft.reference.prompt === undefined || draft.reference.prompt.trim() === "")
386
+ ? [
387
+ {
388
+ field: "reference.prompt",
389
+ message: "Pick an image prompt for the establishing image, upload one instead, or set Establishing image to Off in the Images section.",
390
+ },
391
+ ]
392
+ : [];
393
+ }
315
394
  // Shorts are cut from the narration and captioned from its word timings, so they need
316
395
  // narration, like the YouTube description; they run whether the video renders or not.
317
396
  export function usesShorts(draft) {
@@ -347,9 +426,55 @@ export function videoEditFields(draft) {
347
426
  message: problem.message,
348
427
  }));
349
428
  }
429
+ // The ambient bed, checked only while the long video renders with narration; its own file only
430
+ // while the source is "upload". Named by where the control sits: Play's Export rail.
431
+ export function ambientBedFields(draft, staged) {
432
+ const bed = draft.ambientBed;
433
+ if (bed === undefined || !usesAmbientBed(draft))
434
+ return [];
435
+ const where = "under Outputs → Export → Ambient sound";
436
+ const fields = ambientBedProblems(bed).map((problem) => ({
437
+ field: `ambientBed.${problem.field}`,
438
+ message: `${problem.message} Change it ${where}.`,
439
+ }));
440
+ if (bed.source !== "upload")
441
+ return fields;
442
+ const id = draft.provided.ambientBed;
443
+ const file = id === undefined ? undefined : staged.find((candidate) => candidate.id === id);
444
+ if (file === undefined || file.stageKind !== "audio")
445
+ fields.push({
446
+ field: "ambientBed.file",
447
+ message: `The ambient sound's audio file is missing, so it can't be added to the project. Choose the file ${where}, or pick Rain, Fireplace or Wind instead.`,
448
+ });
449
+ else if (file.state !== "staged")
450
+ fields.push({
451
+ field: "ambientBed.file",
452
+ message: `The ambient sound's audio file is still uploading, so the run can't start yet. Wait for it to finish ${where}.`,
453
+ });
454
+ return fields;
455
+ }
350
456
  function chosen(choice) {
351
457
  return choice !== undefined && choice.provider.trim() !== "" && choice.model.trim() !== "";
352
458
  }
459
+ // The aliases a run narrates with: its copied Library → Aliases while Use narration aliases is
460
+ // on for generated audio, otherwise none. Any voice provider: an alias is plain text.
461
+ export function narrationAliasesOf(draft) {
462
+ return draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
463
+ ? (draft.narrationAliases ?? [])
464
+ : [];
465
+ }
466
+ // Describe tables and figures in the narration: generated narration, the setting on, and a
467
+ // text model to write the descriptions. Without a text model the narration flattens the
468
+ // article as it always did, so the default being on never asks for one.
469
+ export function usesDescribedNarration(draft) {
470
+ return (draft.sources.audio === "generate" && draft.audio?.describeFigures === true && chosen(draft.llm));
471
+ }
472
+ // "Show tables and figures on screen": a video, and the narration describing its blocks.
473
+ export function usesFigureCards(draft) {
474
+ return (usesDescribedNarration(draft) &&
475
+ draft.sources.video === "generate" &&
476
+ draft.showFigures === true);
477
+ }
353
478
  export function usesPronunciationGlossary(draft) {
354
479
  return (draft.sources.audio === "generate" &&
355
480
  draft.audio?.usePronunciationGlossary === true &&
@@ -1,12 +1,19 @@
1
1
  import { z } from "zod";
2
+ import { languageSchema } from "../../kernel/ports/languages.js";
2
3
  import { thinkingModes } from "../../kernel/ports/llm.js";
4
+ import { castSnapshotSchema } from "../channels/schema.js";
3
5
  import { checkpointStageSchema } from "../checkpoints/schema.js";
4
6
  import { documentSettingsSchema } from "../document/theme-schema.js";
7
+ import { narrationAliasesSchema } from "../narration/aliases-schema.js";
5
8
  import { chunkModes } from "../narration/chunk.js";
9
+ import { reviewModes, reviewStages } from "../reviews/model.js";
6
10
  import { subtitleConfigSchema } from "../subtitles/model.js";
11
+ import { ambientBedSchema } from "../video/ambient-bed-schema.js";
7
12
  import { animateModes, atmospheres, colorGrades, cutModes, lookLevels, transitionKinds, } from "../video/edit-settings.js";
8
- import { entryModes, formats, motionStyles, stageSources } from "./model.js";
13
+ import { voicesSettingsSchema } from "../voices/model.js";
14
+ import { entryModes, formats, motionStyles, referenceSources, runModes, stageSources, } from "./model.js";
9
15
  import { defaultEdgeSilenceSeconds, defaultImageSeconds, defaultMotionStyle, defaultZoomPercent, } from "./rules.js";
16
+ const hexColour = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
10
17
  const providerChoice = z.object({
11
18
  provider: z.string(),
12
19
  model: z.string(),
@@ -29,6 +36,8 @@ export const videoEditSchema = z.object({
29
36
  // The shape Play posts and the shape `projects.config` holds, in one place: the second
30
37
  // is the first plus the rendered prompt texts.
31
38
  export const runDraftSchema = z.object({
39
+ // Absent is the long video.
40
+ mode: z.enum(runModes).optional(),
32
41
  checkpoints: z
33
42
  .array(checkpointStageSchema)
34
43
  .max(3)
@@ -55,8 +64,12 @@ export const runDraftSchema = z.object({
55
64
  voice: z.string(),
56
65
  usePronunciationGlossary: z.boolean().optional(),
57
66
  shareGlossary: z.boolean().optional(),
67
+ useNarrationAliases: z.boolean().optional(),
68
+ describeFigures: z.boolean().optional(),
69
+ skipCode: z.boolean().optional(),
58
70
  })
59
71
  .optional(),
72
+ narrationAliases: narrationAliasesSchema.optional(),
60
73
  sharedGlossary: z
61
74
  .array(z.object({
62
75
  term: z.string().min(1).max(200),
@@ -66,10 +79,20 @@ export const runDraftSchema = z.object({
66
79
  .readonly()
67
80
  .optional(),
68
81
  images: providerChoice.optional(),
82
+ reference: z
83
+ .object({
84
+ source: z.enum(referenceSources),
85
+ prompt: z.string().optional(),
86
+ thumbnail: z.boolean().optional(),
87
+ })
88
+ .optional(),
69
89
  articlePrompt: z.string().optional(),
70
90
  narrationPrompt: z.string().optional(),
71
91
  imagePrompts: z.array(z.object({ name: z.string(), number: z.number() })),
92
+ // The ranges are `slices/images/scale.ts`'s, checked by admission, not the schema's.
93
+ imageScale: z.object({ perHour: z.number(), words: z.number() }).optional(),
72
94
  thumbnailPrompt: z.string().optional(),
95
+ thumbnailCount: z.union([z.literal(1), z.literal(3)]).optional(),
73
96
  intro: entryChoice.optional(),
74
97
  outro: entryChoice.optional(),
75
98
  values: z.record(z.string(), z.string()),
@@ -79,6 +102,11 @@ export const runDraftSchema = z.object({
79
102
  audio: z.string().optional(),
80
103
  images: z.array(z.string()).optional(),
81
104
  thumbnail: z.string().optional(),
105
+ reference: z.string().optional(),
106
+ // The staged file of the shorts' background music; used only while Shorts is on.
107
+ shortsMusic: z.string().optional(),
108
+ // The staged file of the ambient bed; used only while its source is "upload".
109
+ ambientBed: z.string().optional(),
82
110
  }),
83
111
  // Optional until Play carries the control; unknown keys are stripped by this schema, so
84
112
  // a mode not listed here would never reach the audio stage.
@@ -99,6 +127,7 @@ export const runDraftSchema = z.object({
99
127
  subtitles: subtitleConfigSchema.optional(),
100
128
  document: documentSettingsSchema.optional(),
101
129
  youtubeDescription: z.boolean().optional(),
130
+ showFigures: z.boolean().optional(),
102
131
  descriptionPrompt: z.string().optional(),
103
132
  // The ranges are `slices/shorts/model.ts`'s, checked by admission, not the schema's.
104
133
  shorts: z
@@ -117,6 +146,44 @@ export const runDraftSchema = z.object({
117
146
  .optional(),
118
147
  // The ranges are `slices/video/edit-settings.ts`'s, checked by admission, not the schema's.
119
148
  videoEdit: videoEditSchema.optional(),
149
+ channelId: z.string().optional(),
150
+ useBrandKit: z.literal(false).optional(),
151
+ cast: castSnapshotSchema.optional(),
152
+ earlierEpisodes: z
153
+ .array(z.object({ title: z.string(), summary: z.string() }))
154
+ .readonly()
155
+ .optional(),
156
+ titleStyle: z
157
+ .object({
158
+ fontId: z
159
+ .string()
160
+ .max(160)
161
+ .regex(/^[A-Za-z0-9_-]+$/)
162
+ .optional(),
163
+ color: hexColour.optional(),
164
+ })
165
+ .optional(),
166
+ endScreen: z.object({ text: z.string().max(200) }).optional(),
167
+ // The retry range is `slices/reviews/model.ts`'s, checked by admission, not the schema's.
168
+ reviews: z
169
+ .object({
170
+ provider: z.string(),
171
+ model: z.string(),
172
+ thinking: z.enum(thinkingModes).optional(),
173
+ retries: z.number().optional(),
174
+ stages: z.partialRecord(z.enum(reviewStages), z.object({ mode: z.enum(reviewModes), prompt: z.string().optional() })),
175
+ })
176
+ .optional(),
177
+ // The rules are `slices/voices/model.ts`'s, checked by admission, not the schema's.
178
+ voices: voicesSettingsSchema.optional(),
179
+ ambientBed: ambientBedSchema.optional(),
180
+ // Level the volume (`loudness/model.ts`); the range is checked by admission.
181
+ loudness: z.object({ videoLufs: z.number(), audioFilesLufs: z.number() }).strict().optional(),
182
+ // Pauses between sentences and paragraphs (`narration/pauses-model.ts`); checked by admission.
183
+ sentencePauseSeconds: z.number().optional(),
184
+ paragraphPauseSeconds: z.number().optional(),
185
+ // Absent is English (`kernel/ports/languages.ts`).
186
+ language: languageSchema.optional(),
120
187
  });
121
188
  export const runConfigSchema = runDraftSchema.extend({
122
189
  rendered: z.record(z.string(), z.string()),
@@ -0,0 +1,47 @@
1
+ import { sourceOf } from "./model.js";
2
+ // The short-only run mode: the same stages as a long video, narrowed to one vertical clip of
3
+ // about a minute. The article is written for a short, the narration and images are made as
4
+ // usual, and the Video stage renders the whole narration through the Shorts renderer
5
+ // (`shorts/render.ts`) with its word-by-word captions instead of the slideshow. A short has
6
+ // no thumbnail, PDF or shorts of its own.
7
+ export function usesShortMode(draft) {
8
+ return draft.mode === "short";
9
+ }
10
+ // Said where each control sits, so a draft or a Save that breaks the mode names the fix.
11
+ export function shortModeFields(draft) {
12
+ if (!usesShortMode(draft))
13
+ return [];
14
+ const fields = [];
15
+ if (draft.format !== "9:16")
16
+ fields.push({
17
+ field: "format",
18
+ message: "A short is vertical. Choose the 9:16 format, or make a long video instead.",
19
+ });
20
+ if (sourceOf(draft.sources, "video") !== "generate")
21
+ fields.push({
22
+ field: "sources.video",
23
+ message: "A short is a video. Set Video to Generate.",
24
+ });
25
+ if (sourceOf(draft.sources, "audio") === "off")
26
+ fields.push({
27
+ field: "sources.audio",
28
+ message: "A short's length and captions come from its narration. Set Narration to Generate or Provide.",
29
+ });
30
+ if (sourceOf(draft.sources, "images") === "off")
31
+ fields.push({
32
+ field: "sources.images",
33
+ message: "A short shows images behind its captions. Set Images to Generate or Provide.",
34
+ });
35
+ for (const kind of ["thumbnail", "document"])
36
+ if (sourceOf(draft.sources, kind) !== "off")
37
+ fields.push({
38
+ field: `sources.${kind}`,
39
+ message: `A short has no ${kind === "thumbnail" ? "thumbnail" : "PDF"}. Set ${kind === "thumbnail" ? "Thumbnail" : "Document"} to Off.`,
40
+ });
41
+ if (draft.shorts?.enabled === true)
42
+ fields.push({
43
+ field: "shorts.enabled",
44
+ message: "A short isn't cut into more shorts. Turn Shorts off.",
45
+ });
46
+ return fields;
47
+ }
@@ -1,15 +1,23 @@
1
1
  import { transact } from "../../kernel/db/tx.js";
2
2
  import { stageKinds } from "../../kernel/pipeline.js";
3
3
  import { storeArticleText } from "../article/store.js";
4
+ import { resolveChannelId, setProjectChannel } from "../channels/repo.js";
4
5
  import { defaultDocumentTheme } from "../document/model.js";
6
+ import { earlierEpisodesFor } from "../episodes/related.js";
7
+ import { listNarrationAliases } from "../narration/aliases-library.js";
5
8
  import { collectSharedGlossary } from "../narration/shared-glossary.js";
6
9
  import { admitInitialRevision } from "../rebuild/runtime-admission.js";
7
10
  import { adoptBaseline } from "../revisions/adopt.js";
11
+ import { insertAsset } from "../revisions/repo.js";
12
+ import { discardPreparedAssets } from "../storage/assets.js";
13
+ import { prepareStagedFile } from "../storage/prepare.js";
8
14
  import { attachStagedFile, storeText } from "../storage/staging.js";
9
15
  import { releaseStagedFile } from "../storage/staging-refs.js";
16
+ import { usesAmbientBed } from "../video/ambient-bed.js";
10
17
  import { sourceOf, } from "./model.js";
11
18
  import { insertProject, insertStage } from "./repo.js";
12
- import { usesPronunciationGlossary } from "./rules.js";
19
+ import { usesPronunciationGlossary, usesReference } from "./rules.js";
20
+ import { render } from "./substitute.js";
13
21
  // Provide → `provided` with its output attached; Off → `skipped`;
14
22
  // everything else → `pending`.
15
23
  export function initialState(source) {
@@ -26,8 +34,20 @@ export function startRun(deps, draft, rendered, retainStaged = false, templates)
26
34
  const shared = usesPronunciationGlossary(draft) && draft.audio?.shareGlossary === true
27
35
  ? collectSharedGlossary(deps).entries
28
36
  : [];
37
+ // Library → Aliases as they are now: later edits there never change this project.
38
+ const aliases = draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
39
+ ? listNarrationAliases(deps.db)
40
+ : [];
41
+ // The title may name keywords too ("D&D Lore: {{Topic}}"), filled like a prompt's, so a
42
+ // template or Play keeps the pattern and each project gets its own title.
43
+ const title = render(draft.title, draft.values).trim() || draft.title;
44
+ // The channel's related earlier episodes as they are now (`slices/episodes`): later edits
45
+ // there never change this project. A draft never brings its own.
46
+ const { earlierEpisodes: _carried, ...fresh } = draft;
47
+ const earlier = earlierEpisodesFor(deps.db, draft, title);
29
48
  const config = {
30
- ...draft,
49
+ ...fresh,
50
+ title,
31
51
  // A new project always names its document theme: a config without one reads as the
32
52
  // DiceMaster of older projects (see documentThemeOf), which no new project should get.
33
53
  ...(sourceOf(draft.sources, "document") === "generate" && draft.document === undefined
@@ -35,10 +55,12 @@ export function startRun(deps, draft, rendered, retainStaged = false, templates)
35
55
  : {}),
36
56
  rendered,
37
57
  ...(shared.length === 0 ? {} : { sharedGlossary: shared }),
58
+ ...(aliases.length === 0 ? {} : { narrationAliases: aliases }),
59
+ ...(earlier === undefined ? {} : { earlierEpisodes: earlier }),
38
60
  };
39
61
  const project = {
40
62
  id,
41
- title: draft.title,
63
+ title,
42
64
  format: draft.format,
43
65
  config,
44
66
  createdAt: at,
@@ -63,19 +85,40 @@ export function startRun(deps, draft, rendered, retainStaged = false, templates)
63
85
  // the project folder rather than moved, so a rollback leaves the staging files exactly
64
86
  // as the form left them and the same Play can simply be pressed again.
65
87
  const moved = [];
66
- transact(deps.db, () => {
67
- insertProject(deps.db, project);
68
- for (const stage of stages) {
69
- insertStage(deps.db, stage);
70
- }
71
- attachProvided(deps, id, draft, moved, retainStaged);
72
- if (deps.catalogue !== undefined) {
73
- const baseline = adoptBaseline(deps, id, templates);
74
- if (!baseline.ok)
75
- throw new Error("The new project has no revision.");
76
- admitInitialRevision(deps, baseline.view, deps.catalogue.read());
77
- }
78
- });
88
+ let music;
89
+ let bed;
90
+ try {
91
+ transact(deps.db, () => {
92
+ insertProject(deps.db, project);
93
+ setProjectChannel(deps.db, id, resolveChannelId(deps.db, draft.channelId));
94
+ for (const stage of stages) {
95
+ insertStage(deps.db, stage);
96
+ }
97
+ attachProvided(deps, id, draft, moved, retainStaged);
98
+ music = attachShortsMusic(deps, id, draft, moved);
99
+ bed = attachAmbientBed(deps, id, draft, moved);
100
+ // The music is named by the first revision, as Edit project → Shorts names it, so the
101
+ // project gets its revision now even where it would otherwise wait for its first view.
102
+ // The ambient bed's own file is named the same way.
103
+ if (deps.catalogue !== undefined || music !== undefined || bed !== undefined) {
104
+ const baseline = adoptBaseline(deps, id, templates, {
105
+ ...(music === undefined ? {} : { shortsMusic: music.id }),
106
+ ...(bed === undefined ? {} : { ambientBed: bed.id }),
107
+ });
108
+ if (!baseline.ok)
109
+ throw new Error("The new project has no revision.");
110
+ if (deps.catalogue !== undefined)
111
+ admitInitialRevision(deps, baseline.view, deps.catalogue.read());
112
+ }
113
+ });
114
+ }
115
+ catch (error) {
116
+ // The copy was written outside the transaction; nothing names it once it rolled back.
117
+ const written = [music, bed].flatMap((asset) => (asset === undefined ? [] : [asset]));
118
+ if (written.length > 0)
119
+ discardPreparedAssets(deps, written);
120
+ throw error;
121
+ }
79
122
  for (const source of retainStaged ? [] : moved) {
80
123
  releaseStagedFile(deps, source);
81
124
  }
@@ -103,6 +146,9 @@ function attachProvided(deps, projectId, draft, collected, retainStaged) {
103
146
  if (sources.thumbnail === "provide") {
104
147
  attach(deps, projectId, "thumbnail", provided.thumbnail, "thumbnail", collected, retainStaged);
105
148
  }
149
+ if (usesReference(draft) && draft.reference?.source === "provide") {
150
+ attach(deps, projectId, "images", provided.reference, "reference", collected, retainStaged);
151
+ }
106
152
  if (sources.images === "provide") {
107
153
  // Slideshow order is the order the user left the list in.
108
154
  for (const [index, stagedFileId] of (provided.images ?? []).entries()) {
@@ -110,6 +156,34 @@ function attachProvided(deps, projectId, draft, collected, retainStaged) {
110
156
  }
111
157
  }
112
158
  }
159
+ // The Shorts step's background music, copied into the project as an asset of its own (never
160
+ // an output), exactly as Edit project → Shorts keeps an uploaded one. Only while Shorts is on.
161
+ function attachShortsMusic(deps, projectId, draft, collected) {
162
+ const stagedFileId = draft.provided.shortsMusic;
163
+ if (draft.shorts?.enabled !== true || stagedFileId === undefined)
164
+ return undefined;
165
+ const result = prepareStagedFile(deps, { projectId, stagedFileId, role: "audio_body" });
166
+ if (!result.ok)
167
+ // admit() already refused a missing or still-copying file, as for the stages' own files.
168
+ throw new Error(`the shorts' background music could not be attached: ${result.reason}`);
169
+ insertAsset(deps.db, result.asset);
170
+ collected.push(stagedFileId);
171
+ return result.asset;
172
+ }
173
+ // The ambient bed's own file, copied into the project as an asset of its own like the shorts'
174
+ // music. Only while the long video gets the bed and its source is "upload".
175
+ function attachAmbientBed(deps, projectId, draft, collected) {
176
+ const stagedFileId = draft.provided.ambientBed;
177
+ if (draft.ambientBed?.source !== "upload" || !usesAmbientBed(draft) || stagedFileId === undefined)
178
+ return undefined;
179
+ const result = prepareStagedFile(deps, { projectId, stagedFileId, role: "audio_body" });
180
+ if (!result.ok)
181
+ // admit() already refused a missing or still-copying file, as for the stages' own files.
182
+ throw new Error(`the ambient sound's audio file could not be attached: ${result.reason}`);
183
+ insertAsset(deps.db, result.asset);
184
+ collected.push(stagedFileId);
185
+ return result.asset;
186
+ }
113
187
  function attach(deps, projectId, kind, stagedFileId, role, collected, retainStaged, index) {
114
188
  if (stagedFileId === undefined) {
115
189
  throw new Error(`the ${kind} stage is provided but carries no staged file`);
@@ -82,7 +82,7 @@ function truncated(answer) {
82
82
  // Empty response → failed attempt.
83
83
  function written(answer) {
84
84
  return answer.text.trim() === ""
85
- ? "The AI model returned an empty article. Retry stage; if it keeps happening, choose a different model in Edit project → Providers."
85
+ ? "The AI model returned an empty article. Try again; if it keeps happening, choose a different model in Edit project → Providers."
86
86
  : undefined;
87
87
  }
88
88
  function finished(answer) {
@@ -91,6 +91,6 @@ function finished(answer) {
91
91
  return empty;
92
92
  }
93
93
  return truncated(answer)
94
- ? `The article was still unfinished after ${String(continuationLimit)} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then Retry stage.`
94
+ ? `The article was still unfinished after ${String(continuationLimit)} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then Try again.`
95
95
  : undefined;
96
96
  }
@@ -20,11 +20,11 @@ export async function prepareProvidedArticleSegments(deps, audioContext, provide
20
20
  audioContext.signal.throwIfAborted();
21
21
  const article = stagesOf(deps.db, projectId).find((stage) => stage.kind === "article");
22
22
  if (!article)
23
- throw new Error("Slopify hit an internal error (the project has no article stage). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
23
+ throw new Error("Slopify hit an internal error (the project has no article stage). Try again; if it happens again, use Download diagnostics in Settings and report it.");
24
24
  const outputs = outputsOf(deps.db, projectId);
25
25
  const plain = outputs.find((output) => output.role === "article_txt");
26
26
  if (!plain)
27
- throw new Error("The article text you provided is missing, so the intro and outro can't be written from it. Paste the article again in Edit project → Article, then Retry stage.");
27
+ throw new Error("The article text you provided is missing, so the intro and outro can't be written from it. Paste the article again in Edit project → Article, then Try again.");
28
28
  const narration = readFileSync(outputPath(deps.paths, projectId, plain.path), "utf8");
29
29
  const saved = outputs.find((output) => output.stageKind === "article" && output.role === "instructions");
30
30
  let instructions = saved
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { messageRoles } from "../../kernel/ports/llm.js";
4
4
  import { insertPiece, piecesOf, setPiece } from "../../kernel/runner/piece-repo.js";
5
5
  import { projectById } from "../admission/repo.js";
6
+ import { withEarlierEpisodes } from "../episodes/related.js";
6
7
  import { outputPath } from "../storage/layout.js";
7
8
  import { outputsOf } from "../storage/repo.js";
8
9
  import { storeText } from "../storage/staging.js";
@@ -20,10 +21,13 @@ export async function runArticle(deps, context, providers) {
20
21
  if (choice === undefined || articlePrompt === undefined) {
21
22
  // Admission refuses a run whose article is Generate without both, so
22
23
  // reaching here is a bug in admission rather than something the user did.
23
- throw new Error("No AI model or article prompt is set for writing the article. Choose a model in Edit project → Providers and check Edit project → Prompts, then Retry stage.");
24
+ throw new Error("No AI model or article prompt is set for writing the article. Choose a model in Edit project → Providers and check Edit project → Prompts, then Try again.");
24
25
  }
25
26
  const notes = researchNotes(deps, projectId);
26
- const brief = { articlePrompt, ...(notes === undefined ? {} : { notes }) };
27
+ const brief = {
28
+ articlePrompt: withEarlierEpisodes(articlePrompt, project.config.earlierEpisodes),
29
+ ...(notes === undefined ? {} : { notes }),
30
+ };
27
31
  const result = await articleBody(deps, context, providers, choice, brief);
28
32
  if (!result.ok)
29
33
  return "held";
@@ -7,12 +7,12 @@ export async function writeSegment(providers, choice, config, category, article,
7
7
  return { ok: true, value: undefined };
8
8
  const body = config.rendered[category];
9
9
  if (body === undefined)
10
- throw new Error(`Slopify hit an internal error (the ${category} text was never filled in). Retry stage; if it happens again, use Download diagnostics in Settings and report it.`);
10
+ throw new Error(`Slopify hit an internal error (the ${category} text was never filled in). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
11
11
  const common = { category, name: picked.name, mode: picked.mode };
12
12
  if (picked.mode === "text")
13
13
  return { ok: true, value: { ...common, text: body, tokens: noTokens } };
14
14
  if (choice === undefined)
15
- throw new Error(`No AI model is set for writing the ${category}. Choose one in Edit project → Providers, then Retry stage.`);
15
+ throw new Error(`No AI model is set for writing the ${category}. Choose one in Edit project → Providers, then Try again.`);
16
16
  const messages = segmentMessages(body, config, article);
17
17
  sent.push({ label: category === "intro" ? "Intro" : "Outro", messages });
18
18
  const answer = await providers.llm({
@@ -21,7 +21,7 @@ export async function writeSegment(providers, choice, config, category, article,
21
21
  ...(choice.thinking === undefined ? {} : { thinking: choice.thinking }),
22
22
  messages,
23
23
  check: (given) => given.text.trim() === ""
24
- ? `The AI model returned an empty ${category}. Retry stage; if it keeps happening, choose a different model in Edit project → Providers.`
24
+ ? `The AI model returned an empty ${category}. Try again; if it keeps happening, choose a different model in Edit project → Providers.`
25
25
  : undefined,
26
26
  });
27
27
  if (!answer.ok)