@gentbajko/slopify 2.5.0 → 3.0.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 (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  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 +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -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.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,212 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { zValidator } from "@hono/zod-validator";
3
+ import { Hono } from "hono";
4
+ import { z } from "zod";
5
+ import { defaultSpeechVoice, detectSpeechCached, primaryLanguage, } from "../../kernel/ports/system-speech.js";
6
+ import { loudnessOfDefault } from "../../slices/loudness/model.js";
7
+ import { firstRunView } from "../../slices/onboarding/first-run.js";
8
+ import { fullVideoDraft } from "../../slices/onboarding/full-video.js";
9
+ import { installPack } from "../../slices/onboarding/install.js";
10
+ import { fullVideoInputSchema, quickShortInputSchema, sampleCopyInputSchema, } from "../../slices/onboarding/model.js";
11
+ import { packById, starterSet } from "../../slices/onboarding/packs.js";
12
+ import { planShortProviders, shortDraft, } from "../../slices/onboarding/quick-short.js";
13
+ import { copySample, restoreSamples, sampleProjectIds, } from "../../slices/onboarding/sample.js";
14
+ import { dismissFirstRun, recordShortRequest, shortRequestProject, } from "../../slices/onboarding/state.js";
15
+ import { systemVoiceProvider } from "../../slices/settings/model.js";
16
+ import { readSettings } from "../../slices/settings/playback.js";
17
+ import { providerStatuses } from "../../slices/settings/readiness.js";
18
+ import { listVoices } from "../../slices/settings/repo.js";
19
+ import { addVoice } from "../../slices/settings/voices.js";
20
+ import { projectTitle } from "../../slices/storage/repo.js";
21
+ import { onInvalid, problem, titleOf } from "./problem.js";
22
+ import { createProject } from "./project-create.js";
23
+ const packParam = z.object({ id: z.string().min(1).max(40) });
24
+ // The first five minutes: the first-run screen's state, starter packs, "Make a 60-second
25
+ // short" and the bundled samples' Restore and Make my own copy.
26
+ export function onboardingRoutes(deps) {
27
+ const statuses = () => providerStatuses({
28
+ db: deps.db,
29
+ probe: deps.probe,
30
+ hostCliStatus: deps.hostCliStatus,
31
+ host: deps.speechHost,
32
+ });
33
+ const speech = () => detectSpeechCached(deps.probe, deps.speechHost ?? process);
34
+ // The best speech program found and its English voice, for a short made without a voice key.
35
+ const systemVoicePick = async () => {
36
+ const engine = (await speech()).engines[0];
37
+ const voice = engine === undefined ? undefined : defaultSpeechVoice(engine);
38
+ return engine === undefined || voice === undefined
39
+ ? undefined
40
+ : { model: engine.id, voiceId: voice.id, name: voice.name };
41
+ };
42
+ const templates = { ...deps, uuid: randomUUID };
43
+ const sample = {
44
+ db: deps.db,
45
+ paths: deps.paths,
46
+ clock: deps.clock,
47
+ ids: deps.ids,
48
+ log: deps.log,
49
+ appVersion: deps.version,
50
+ hasInflight: deps.runner.hasInflight,
51
+ ...(deps.sampleArchives === undefined ? {} : { archives: deps.sampleArchives }),
52
+ };
53
+ return new Hono()
54
+ .get("/", async (c) => {
55
+ c.header("Cache-Control", "no-store");
56
+ return c.json(firstRunView(deps.db, await statuses()));
57
+ })
58
+ .post("/dismiss", (c) => {
59
+ dismissFirstRun(deps.db, deps.clock.now().toISOString());
60
+ return c.json({ dismissed: true });
61
+ })
62
+ .post("/packs/:id", zValidator("param", packParam, onInvalid), (c) => {
63
+ const installed = installPack(templates, c.req.valid("param").id);
64
+ if (!installed.ok)
65
+ return problem(c, {
66
+ status: 404,
67
+ title: titleOf(404),
68
+ detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
69
+ });
70
+ return c.json(installed.value);
71
+ })
72
+ .post("/short", zValidator("json", quickShortInputSchema, onInvalid), async (c) => {
73
+ const input = c.req.valid("json");
74
+ const earlier = shortRequestProject(deps.db, input.requestId);
75
+ if (earlier !== undefined && projectTitle(deps.db, earlier) !== undefined)
76
+ return c.json({ projectId: earlier, replayed: true });
77
+ const pack = input.packId === undefined ? starterSet : packById(input.packId);
78
+ if (pack === undefined)
79
+ return problem(c, {
80
+ status: 404,
81
+ title: titleOf(404),
82
+ detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
83
+ });
84
+ const modelsFor = deps.modelsFor;
85
+ if (modelsFor === undefined)
86
+ return problem(c, {
87
+ status: 503,
88
+ title: titleOf(503),
89
+ detail: "Slopify can't list the models on this machine right now. Restart Slopify and try again.",
90
+ });
91
+ const plan = await planShortProviders({
92
+ statuses: await statuses(),
93
+ voices: listVoices(deps.db),
94
+ modelsFor,
95
+ suggested: pack.voice,
96
+ systemVoice: await systemVoicePick(),
97
+ });
98
+ if (!plan.ok)
99
+ return problem(c, {
100
+ status: 409,
101
+ title: titleOf(409),
102
+ detail: plan.gaps.map((gap) => gap.message).join(" "),
103
+ extensions: { gaps: plan.gaps },
104
+ });
105
+ const installed = installPack(templates, pack.id, { template: false });
106
+ if (!installed.ok)
107
+ return problem(c, {
108
+ status: 404,
109
+ title: titleOf(404),
110
+ detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
111
+ });
112
+ // The computer's voice is saved like any other, so Play and a later rebuild offer it.
113
+ const audio = plan.providers.audio;
114
+ if (plan.systemVoice &&
115
+ !listVoices(deps.db).some((voice) => voice.provider === audio.provider && voice.voiceId === audio.voice)) {
116
+ const found = await speech();
117
+ const spoken = found.engines
118
+ .flatMap((engine) => engine.voices)
119
+ .find((voice) => voice.id === audio.voice);
120
+ const language = spoken === undefined ? undefined : primaryLanguage(spoken);
121
+ addVoice({ db: deps.db, ids: deps.ids }, {
122
+ provider: systemVoiceProvider,
123
+ name: `${spoken?.name ?? audio.voice} (computer voice)`,
124
+ voiceId: audio.voice,
125
+ ...(language === undefined ? {} : { languages: [language] }),
126
+ });
127
+ }
128
+ const created = await createProject(deps, shortDraft({
129
+ topic: input.topic,
130
+ pack,
131
+ names: installed.value.prompts,
132
+ providers: plan.providers,
133
+ loudness: loudnessOfDefault(readSettings({ db: deps.db, log: deps.log }).loudness),
134
+ }));
135
+ if (!created.ok)
136
+ return problem(c, {
137
+ status: created.status,
138
+ title: titleOf(created.status),
139
+ detail: `${created.detail} ${created.fields.map((field) => field.message).join(" ")}`.trim(),
140
+ ...(created.fields.length === 0 ? {} : { extensions: { fields: created.fields } }),
141
+ });
142
+ recordShortRequest(deps.db, input.requestId, created.project.id);
143
+ // A real project now exists, so the first-run screen is done with.
144
+ dismissFirstRun(deps.db, deps.clock.now().toISOString());
145
+ return c.json({ projectId: created.project.id, replayed: false }, 201);
146
+ })
147
+ .post("/full-video", zValidator("json", fullVideoInputSchema, onInvalid), (c) => {
148
+ const made = fullVideoDraft(templates, c.req.valid("json"));
149
+ if (made.ok)
150
+ return c.json(made.value, 201);
151
+ if (made.reason === "not-found")
152
+ return problem(c, {
153
+ status: 404,
154
+ title: titleOf(404),
155
+ detail: "This short or its starter pack is no longer here, so Play couldn't be set up from it. Open Play and pick a template yourself.",
156
+ });
157
+ if (made.reason === "not-short")
158
+ return problem(c, {
159
+ status: 409,
160
+ title: titleOf(409),
161
+ detail: "This project is already a long video. Open Play to start another one on a new topic.",
162
+ });
163
+ return problem(c, {
164
+ status: 409,
165
+ title: titleOf(409),
166
+ detail: "Slopify couldn't open a Play draft for the full video. Press Make the full video on this topic again; if it keeps failing, open Play and pick the pack's template in Library → Templates.",
167
+ extensions: { reason: made.reason },
168
+ });
169
+ })
170
+ .get("/sample", (c) => {
171
+ const samples = sampleProjectIds(deps.db);
172
+ return c.json({ projectId: samples.library, samples });
173
+ })
174
+ .post("/sample/restore", async (c) => {
175
+ const restored = await restoreSamples(sample);
176
+ if (!restored.ok)
177
+ return problem(c, {
178
+ status: restored.status,
179
+ title: titleOf(restored.status),
180
+ detail: restored.detail,
181
+ });
182
+ return c.json({ projectId: restored.samples.library, samples: restored.samples });
183
+ })
184
+ .post("/sample/copy", async (c) => {
185
+ // Which sample to copy is in the body; without one, the Library of Alexandria.
186
+ const body = await c.req.json().catch(() => ({}));
187
+ const input = sampleCopyInputSchema.safeParse(body ?? {});
188
+ if (!input.success)
189
+ return problem(c, {
190
+ status: 400,
191
+ title: titleOf(400),
192
+ detail: "Slopify couldn't tell which sample to copy. Reload the page and try again.",
193
+ });
194
+ const catalogue = deps.catalogue;
195
+ const id = input.data.projectId ?? sampleProjectIds(deps.db).library ?? undefined;
196
+ if (catalogue === undefined || id === undefined)
197
+ return problem(c, {
198
+ status: 404,
199
+ title: titleOf(404),
200
+ detail: "The sample project isn't here. Bring it back with Settings → Backup & storage → Restore samples, then press Make my own copy.",
201
+ });
202
+ const copied = copySample({ ...sample, catalogue: catalogue.read() }, id);
203
+ if (!copied.ok)
204
+ return problem(c, {
205
+ status: copied.status,
206
+ title: titleOf(copied.status),
207
+ detail: copied.detail,
208
+ });
209
+ deps.hub.emitGlobal({ type: "project.updated", projectId: copied.projectId });
210
+ return c.json({ projectId: copied.projectId }, 201);
211
+ });
212
+ }
@@ -41,7 +41,7 @@ export function openFolderRoutes(deps) {
41
41
  return problem(c, {
42
42
  status: 404,
43
43
  title: titleOf(404),
44
- detail: "This output's file was deleted or moved. Use Re-run section on the project page to make it again.",
44
+ detail: "This output's file was deleted or moved. Use More → make it again in its section on the project page to make it again.",
45
45
  });
46
46
  }
47
47
  return replyForFolder(c, deps, id, download.download.path);
@@ -0,0 +1,45 @@
1
+ import { Hono } from "hono";
2
+ import { bundledPatchNotesDir, PatchNotesMissing, readPatchNote, } from "../../slices/patch-notes/library.js";
3
+ import { markPatchNotesSeen, readPatchNotes } from "../../slices/patch-notes/seen.js";
4
+ import { problem, titleOf } from "./problem.js";
5
+ // A build without its notes says so in plain words; anything else is the app's error handler's.
6
+ function missing(c, error) {
7
+ if (!(error instanceof PatchNotesMissing))
8
+ throw error;
9
+ return problem(c, { status: 500, title: titleOf(500), detail: error.message });
10
+ }
11
+ // Settings → Patch notes: the list (with which note is due to open by itself), one note's
12
+ // Markdown, and recording that the note that opened by itself was closed.
13
+ // Preserve Hono's inferred route types for the web client.
14
+ export function patchNotesRoutes(deps) {
15
+ const dir = deps.patchNotesDir ?? bundledPatchNotesDir();
16
+ return new Hono()
17
+ .get("/", async (c) => {
18
+ try {
19
+ return c.json(await readPatchNotes(deps.db, dir, deps.version));
20
+ }
21
+ catch (error) {
22
+ return missing(c, error);
23
+ }
24
+ })
25
+ .post("/seen", (c) => {
26
+ markPatchNotesSeen(deps.db, deps.version);
27
+ return c.json({ seen: true });
28
+ })
29
+ .get("/:id", async (c) => {
30
+ let markdown;
31
+ try {
32
+ markdown = await readPatchNote(dir, c.req.param("id"));
33
+ }
34
+ catch (error) {
35
+ return missing(c, error);
36
+ }
37
+ if (markdown === undefined)
38
+ return problem(c, {
39
+ status: 404,
40
+ title: titleOf(404),
41
+ detail: "These patch notes are not in this version of Slopify. Open Settings → Patch notes and pick one from the list.",
42
+ });
43
+ return c.body(markdown, 200, { "content-type": "text/markdown; charset=utf-8" });
44
+ });
45
+ }
@@ -5,6 +5,7 @@ import { modelFields } from "../../catalog/validate.js";
5
5
  import { runDraftSchema } from "../../slices/admission/repo.js";
6
6
  import { admit } from "../../slices/admission/rules.js";
7
7
  import { batchExists, enqueueBatch, pumpQueue, queueEntries } from "../../slices/batch/index.js";
8
+ import { castVoicedRun } from "../../slices/channels/runs.js";
8
9
  import { estimateRun } from "../../slices/estimate/index.js";
9
10
  import { resolveFont } from "../../slices/fonts/index.js";
10
11
  import { pickTemplates, renderPicked } from "../../slices/library/slots.js";
@@ -32,8 +33,9 @@ export function planningRoutes(deps) {
32
33
  title: item.title,
33
34
  values: { ...input.draft.values, ...item.values },
34
35
  })) ?? [input.draft];
35
- return drafts.map((draft, index) => {
36
- const picked = pickTemplates(deps.db, draft);
36
+ return drafts.map((sent, index) => {
37
+ // Cast speakers take the cast's voices as they are now, as a run started from Play does.
38
+ const picked = pickTemplates(deps.db, castVoicedRun(deps.db, sent));
37
39
  const admitted = admit({
38
40
  draft: picked.draft,
39
41
  staged: stagedFiles(deps.db),
@@ -0,0 +1,87 @@
1
+ import { modelFields } from "../../catalog/validate.js";
2
+ import { stagesOf } from "../../slices/admission/repo.js";
3
+ import { admit } from "../../slices/admission/rules.js";
4
+ import { startRun } from "../../slices/admission/start.js";
5
+ import { castVoicedRun } from "../../slices/channels/runs.js";
6
+ import { resolveFont } from "../../slices/fonts/index.js";
7
+ import { pickTemplates, renderPicked } from "../../slices/library/slots.js";
8
+ import { stagedFiles } from "../../slices/storage/repo.js";
9
+ import { record } from "../../slices/telemetry/record.js";
10
+ // Starts a project from a whole draft, the way POST /api/projects always has: the prompt
11
+ // bodies are read now, the draft is admitted, the project and its first work are committed in
12
+ // one transaction, and only then is the runner woken. "Make a 60-second short" starts its
13
+ // project through the same door.
14
+ export async function createProject(deps, sent) {
15
+ // Cast speakers take the cast's voices as they are now, as a run started from Play does.
16
+ const draft = castVoicedRun(deps.db, sent);
17
+ if (draft.checkpoints?.length)
18
+ return {
19
+ ok: false,
20
+ status: 409,
21
+ detail: "This project has checkpoints turned on, so it must be started from Play with Review and start.",
22
+ fields: [],
23
+ };
24
+ const subtitles = draft.subtitles;
25
+ if (subtitles !== undefined && subtitles.mode !== "off") {
26
+ try {
27
+ await resolveFont(deps.paths, subtitles.fontId);
28
+ }
29
+ catch {
30
+ return {
31
+ ok: false,
32
+ status: 400,
33
+ detail: "The subtitle font you picked is no longer available. Choose another font before starting.",
34
+ fields: [
35
+ {
36
+ field: "subtitles.fontId",
37
+ message: "This font is no longer available. Choose another or upload it again in Settings.",
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ }
43
+ // The bodies are read here, at the click, so an edit made since the prompt was selected is
44
+ // the one that runs.
45
+ const picked = pickTemplates(deps.db, draft);
46
+ const admitted = admit({
47
+ draft: picked.draft,
48
+ staged: stagedFiles(deps.db),
49
+ requiredSlots: picked.requiredSlots,
50
+ });
51
+ const modelErrors = modelFields(picked.draft, deps.catalogue);
52
+ if (!admitted.ok || picked.missing.length > 0 || modelErrors.length > 0)
53
+ return {
54
+ ok: false,
55
+ status: 400,
56
+ detail: "This video cannot start yet. Fix the highlighted fields, then try again.",
57
+ fields: [...picked.missing, ...modelErrors, ...(admitted.ok ? [] : admitted.fields)],
58
+ };
59
+ const storage = {
60
+ catalogue: deps.catalogue,
61
+ db: deps.db,
62
+ paths: deps.paths,
63
+ ids: deps.ids,
64
+ clock: deps.clock,
65
+ log: deps.log,
66
+ emit: (event) => {
67
+ deps.hub.emitGlobal(event);
68
+ },
69
+ };
70
+ // Rendered from the values admit() has trimmed, so the stored text carries no padding the
71
+ // user did not intend.
72
+ const { project } = startRun(storage, admitted.draft, renderPicked(picked, admitted.draft.values), false, Object.fromEntries(picked.bodies.map(({ key, body }) => [key, body])));
73
+ const telemetry = {
74
+ db: deps.db,
75
+ ids: deps.ids,
76
+ clock: deps.clock,
77
+ log: deps.log,
78
+ appVersion: deps.version,
79
+ };
80
+ // One event per project created. record() swallows its own failures, so a broken telemetry
81
+ // write cannot cost the user the run.
82
+ record(telemetry, "project.created", {});
83
+ deps.flushSoon();
84
+ // The run starts only once the project is committed.
85
+ deps.runner.tick(project.id);
86
+ return { ok: true, project, stages: stagesOf(deps.db, project.id) };
87
+ }
@@ -3,6 +3,7 @@ import { Hono } from "hono";
3
3
  import { HTTPException } from "hono/http-exception";
4
4
  import { z } from "zod";
5
5
  import { createTemplateFromProject, templateFromProjectSchema, } from "../../slices/project-templates/from-project.js";
6
+ import { makeNextChapter, nextChapterSchema } from "../../slices/project-templates/next-chapter.js";
6
7
  import { templateCreateSchema, templateDeleteSchema, templateInstantiateSchema, templateUpdateSchema, } from "../../slices/project-templates/schema.js";
7
8
  import { createTemplate, deleteTemplate, instantiateTemplate, listTemplates, readTemplate, updateTemplate, } from "../../slices/project-templates/service.js";
8
9
  import { onInvalid, problem, titleOf } from "./problem.js";
@@ -37,7 +38,7 @@ function routes(deps) {
37
38
  throw new HTTPException(404);
38
39
  return deps;
39
40
  };
40
- return new Hono()
41
+ return (new Hono()
41
42
  .get("/", (c) => c.json({ templates: listTemplates(service()) }))
42
43
  .post("/from-project/:projectId", zValidator("param", templateFromProjectSchema.unwrap().pick({ projectId: true }), onInvalid), zValidator("json", templateFromProjectSchema.unwrap().omit({ projectId: true }), onInvalid), (c) => {
43
44
  const result = createTemplateFromProject(service(), {
@@ -45,6 +46,22 @@ function routes(deps) {
45
46
  projectId: c.req.valid("param").projectId,
46
47
  });
47
48
  return result.ok ? c.json(result.value, 201) : refused(c, result);
49
+ })
50
+ // A finished audiobook chapter's "Make the next chapter": a new Play draft for the book.
51
+ .post("/next-chapter/:projectId", zValidator("param", nextChapterSchema.unwrap().pick({ projectId: true }), onInvalid), zValidator("json", nextChapterSchema.unwrap().omit({ projectId: true }), onInvalid), (c) => {
52
+ const result = makeNextChapter(service(), {
53
+ ...c.req.valid("json"),
54
+ projectId: c.req.valid("param").projectId,
55
+ });
56
+ if (result.ok)
57
+ return c.json(result.value, 201);
58
+ const status = result.reason === "not-found" ? 404 : result.reason === "conflict" ? 409 : 400;
59
+ return problem(c, {
60
+ status,
61
+ title: titleOf(status),
62
+ detail: result.message,
63
+ extensions: { reason: result.reason },
64
+ });
48
65
  })
49
66
  .post("/", zValidator("json", templateCreateSchema, onInvalid), (c) => {
50
67
  const result = createTemplate(service(), c.req.valid("json"));
@@ -74,5 +91,5 @@ function routes(deps) {
74
91
  templateId: c.req.valid("param").id,
75
92
  });
76
93
  return result.ok ? c.json(result.value, 201) : refused(c, result);
77
- });
94
+ }));
78
95
  }
@@ -1,21 +1,21 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
- import { modelFields } from "../../catalog/validate.js";
5
4
  import { derive, progressOf } from "../../kernel/runner/graph.js";
6
- import { listProjects, projectById, runDraftSchema, stageStandingsByProject, stagesOf, } from "../../slices/admission/repo.js";
7
- import { admit } from "../../slices/admission/rules.js";
8
- import { startRun } from "../../slices/admission/start.js";
5
+ import { listProjects, projectById, projectTrashed, runDraftSchema, stageStandingsByProject, stagesOf, } from "../../slices/admission/repo.js";
6
+ import { defaultChannelId } from "../../slices/channels/model.js";
7
+ import { projectChannels } from "../../slices/channels/repo.js";
9
8
  import { withProjectControl } from "../../slices/control/lock.js";
10
- import { resolveFont } from "../../slices/fonts/index.js";
11
- import { pickTemplates, renderPicked } from "../../slices/library/slots.js";
9
+ import { stagesWithEta } from "../../slices/eta/view.js";
12
10
  import { resumable } from "../../slices/rebuild/recovery-repo.js";
13
11
  import { adoptBaseline } from "../../slices/revisions/adopt.js";
14
12
  import { currentRevisionId } from "../../slices/revisions/repo.js";
15
- import { deleteProject } from "../../slices/storage/delete-project.js";
16
- import { outputsOf, stagedFiles } from "../../slices/storage/repo.js";
17
- import { record } from "../../slices/telemetry/record.js";
13
+ import { limitWaitsByProject, listingWait } from "../../slices/run-cost/panel.js";
14
+ import { outputsOf } from "../../slices/storage/repo.js";
15
+ import { trashProject } from "../../slices/trash/service.js";
16
+ import { uploadedProjects } from "../../slices/uploads/repo.js";
18
17
  import { onInvalid, problem, titleOf } from "./problem.js";
18
+ import { createProject } from "./project-create.js";
19
19
  const idParam = z.object({
20
20
  id: z
21
21
  .string()
@@ -26,28 +26,11 @@ const idParam = z.object({
26
26
  // The return type is inferred so Hono keeps the route types the SPA's client is
27
27
  // generated from; see stagingRoutes.
28
28
  export function projectRoutes(deps) {
29
- const storage = {
30
- catalogue: deps.catalogue,
29
+ const storageForTrash = {
31
30
  db: deps.db,
32
31
  paths: deps.paths,
33
- ids: deps.ids,
34
32
  clock: deps.clock,
35
33
  log: deps.log,
36
- emit: (event) => {
37
- deps.hub.emitGlobal(event);
38
- },
39
- };
40
- const telemetry = {
41
- db: deps.db,
42
- ids: deps.ids,
43
- clock: deps.clock,
44
- log: deps.log,
45
- appVersion: deps.version,
46
- };
47
- const storageForDelete = {
48
- db: deps.db,
49
- paths: deps.paths,
50
- log: deps.log,
51
34
  hasInflight: deps.runner.hasInflight,
52
35
  };
53
36
  const summarise = (project) => ({
@@ -56,76 +39,36 @@ export function projectRoutes(deps) {
56
39
  });
57
40
  return (new Hono()
58
41
  .post("/", zValidator("json", runDraftSchema, onInvalid), async (c) => {
59
- if (c.req.valid("json").checkpoints?.length)
60
- return problem(c, {
61
- status: 409,
62
- title: titleOf(409),
63
- detail: "This project has checkpoints turned on, so it must be started from Play with Review and start.",
64
- });
65
- const requestedSubtitles = c.req.valid("json").subtitles;
66
- if (requestedSubtitles !== undefined && requestedSubtitles.mode !== "off") {
67
- try {
68
- await resolveFont(deps.paths, requestedSubtitles.fontId);
69
- }
70
- catch {
71
- return problem(c, {
72
- status: 400,
73
- title: titleOf(400),
74
- detail: "The subtitle font you picked is no longer available. Choose another font before starting.",
75
- extensions: {
76
- fields: [
77
- {
78
- field: "subtitles.fontId",
79
- message: "This font is no longer available. Choose another or upload it again in Settings.",
80
- },
81
- ],
82
- },
83
- });
84
- }
85
- }
86
- // The bodies are read here, at the click, so an edit made since the
87
- // prompt was selected is the one that runs.
88
- const picked = pickTemplates(deps.db, c.req.valid("json"));
89
- const admitted = admit({
90
- draft: picked.draft,
91
- staged: stagedFiles(deps.db),
92
- requiredSlots: picked.requiredSlots,
93
- });
94
- const modelErrors = modelFields(picked.draft, deps.catalogue);
95
- if (!admitted.ok || picked.missing.length > 0 || modelErrors.length > 0) {
42
+ const created = await createProject(deps, c.req.valid("json"));
43
+ if (!created.ok)
96
44
  return problem(c, {
97
- status: 400,
98
- title: titleOf(400),
99
- detail: "This video cannot start yet. Fix the highlighted fields, then try again.",
100
- extensions: {
101
- fields: [...picked.missing, ...modelErrors, ...(admitted.ok ? [] : admitted.fields)],
102
- },
45
+ status: created.status,
46
+ title: titleOf(created.status),
47
+ detail: created.detail,
48
+ ...(created.fields.length === 0 ? {} : { extensions: { fields: created.fields } }),
103
49
  });
104
- }
105
- // Rendered from the values admit() has trimmed, so the stored text carries no
106
- // padding the user did not intend.
107
- const { project } = startRun(storage, admitted.draft, renderPicked(picked, admitted.draft.values), false, Object.fromEntries(picked.bodies.map(({ key, body }) => [key, body])));
108
- // One event per project created. record() swallows its own
109
- // failures, so a broken telemetry write cannot cost the user the run.
110
- record(telemetry, "project.created", {});
111
- deps.flushSoon();
112
- // The run starts only once the project is committed.
113
- deps.runner.tick(project.id);
114
50
  // Read back after the tick, not from the rows startRun built: the runner has
115
51
  // already claimed every eligible stage, and a body that paired status "running"
116
52
  // with "video: pending" would contradict itself.
117
- return c.json({ project: summarise(project), stages: stagesOf(deps.db, project.id) }, 201);
53
+ return c.json({ project: summarise(created.project), stages: stagesOf(deps.db, created.project.id) }, 201);
118
54
  })
119
55
  // One statement for every project's stage standings, not one per row: the list needs
120
56
  // a status word and a meter, and both come out of the same five columns.
121
57
  .get("/", (c) => {
122
58
  const standings = stageStandingsByProject(deps.db);
59
+ const channels = projectChannels(deps.db);
60
+ const uploads = uploadedProjects(deps.db);
61
+ const waits = limitWaitsByProject(deps.db);
123
62
  const projects = listProjects(deps.db).map((project) => {
124
63
  const stages = standings.get(project.id) ?? [];
64
+ const waiting = waits.get(project.id);
125
65
  return {
126
66
  ...project,
127
67
  status: derive(stages, project.paused),
128
68
  progress: progressOf(stages),
69
+ channelId: channels.get(project.id) ?? defaultChannelId,
70
+ uploadedAt: uploads.get(project.id) ?? null,
71
+ ...(waiting === undefined ? {} : { limitWaits: waiting.map(listingWait) }),
129
72
  };
130
73
  });
131
74
  return c.json({ projects });
@@ -136,7 +79,9 @@ export function projectRoutes(deps) {
136
79
  return problem(c, {
137
80
  status: 404,
138
81
  title: titleOf(404),
139
- detail: "This project no longer exists. Go back to Projects to pick another.",
82
+ detail: projectTrashed(deps.db, c.req.valid("param").id)
83
+ ? "This project is in the trash. Restore it in Settings → Trash to open it again."
84
+ : "This project no longer exists. Go back to Projects to pick another.",
140
85
  });
141
86
  }
142
87
  if (deps.catalogue !== undefined)
@@ -145,34 +90,26 @@ export function projectRoutes(deps) {
145
90
  revisionId: currentRevisionId(deps.db, project.id) ?? null,
146
91
  resumable: resumable(deps.db, project.id),
147
92
  project: summarise(project),
148
- stages: stagesOf(deps.db, project.id),
93
+ stages: stagesWithEta(deps.db, stagesOf(deps.db, project.id), project.config, deps.clock.now()),
149
94
  outputs: outputsOf(deps.db, project.id),
150
95
  });
151
96
  })
152
- // Irreversible, and only from the app: the Projects screen puts a confirmation
153
- // dialog in front of it.
97
+ // Moves the project to the trash (Settings → Trash) for 30 days; removing it for good is
98
+ // Delete now there, or the daily purge (`slices/trash`).
154
99
  .delete("/:id", zValidator("param", idParam, onInvalid), (c) => {
155
100
  const id = c.req.valid("param").id;
156
101
  return withProjectControl(deps.db, id, () => {
157
- const result = deleteProject(storageForDelete, id);
102
+ const result = trashProject(storageForTrash, id);
158
103
  if (result.ok)
159
104
  return c.body(null, 204);
105
+ const status = result.reason === "running" ? 409 : 404;
160
106
  return problem(c, {
161
- status: deleteStatus[result.reason],
162
- title: titleOf(deleteStatus[result.reason]),
163
- detail: result.detail ?? deleteDetails[result.reason],
107
+ status,
108
+ title: titleOf(status),
109
+ detail: result.reason === "running"
110
+ ? "This project is still running. Use Cancel run on the project page first, then delete it."
111
+ : "This project no longer exists. Go back to Projects to pick another.",
164
112
  });
165
113
  });
166
114
  }));
167
115
  }
168
- const deleteStatus = {
169
- "no-project": 404,
170
- running: 409,
171
- files: 500,
172
- };
173
- const deleteDetails = {
174
- "no-project": "This project no longer exists. Go back to Projects to pick another.",
175
- // The run has to be stopped before its files can go.
176
- running: "This project is still running. Use Cancel run on the project page first, then delete it.",
177
- files: "Some of this project's files could not be removed. Close any program using files in the project folder, then try deleting again.",
178
- };