@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,217 @@
1
+ import { transact } from "../../kernel/db/tx.js";
2
+ import { projectById, stagesOf, updateProjectConfig } from "../admission/repo.js";
3
+ import { draftRow } from "../play-drafts/repo.js";
4
+ import { documentOf, saveDraft } from "../play-drafts/service.js";
5
+ import { templateById } from "../project-templates/repo.js";
6
+ import { updateTemplate } from "../project-templates/service.js";
7
+ import { ensureBaseline, saveRevision } from "../revisions/index.js";
8
+ import { currentRevisionId } from "../revisions/repo.js";
9
+ import { configChoices, formChoices, retiredModelUsage, retiredStatus, } from "./usage.js";
10
+ function keepThinking(catalogue, provider, model, thinking) {
11
+ if (thinking === undefined)
12
+ return {};
13
+ const row = catalogue.llm.find((entry) => entry.provider === provider && entry.id === model);
14
+ return row?.llm.thinking?.[thinking] === undefined ? {} : { thinking };
15
+ }
16
+ function matches(choice, from) {
17
+ return choice !== undefined && choice.provider === from.provider && choice.model === from.model;
18
+ }
19
+ // The form with one model choice replaced, or undefined when the form no longer picks `from`
20
+ // there (it was changed meanwhile, so there is nothing to switch).
21
+ export function switchForm(form, catalogue, request) {
22
+ if (!matches(formChoices(form)[request.slot], request.from))
23
+ return undefined;
24
+ const { to } = request;
25
+ switch (request.slot) {
26
+ case "llm": {
27
+ const { thinking, ...rest } = form.llm;
28
+ return {
29
+ ...form,
30
+ llm: { ...rest, model: to, ...keepThinking(catalogue, rest.provider, to, thinking) },
31
+ };
32
+ }
33
+ case "audio":
34
+ return { ...form, audio: { ...form.audio, model: to } };
35
+ case "images":
36
+ return { ...form, images: { ...form.images, model: to } };
37
+ case "animate":
38
+ return form.videoEdit === undefined
39
+ ? undefined
40
+ : { ...form, videoEdit: { ...form.videoEdit, animateModel: to } };
41
+ }
42
+ }
43
+ export function switchConfig(config, catalogue, request) {
44
+ if (!matches(configChoices(config)[request.slot], request.from))
45
+ return undefined;
46
+ const { to } = request;
47
+ switch (request.slot) {
48
+ case "llm": {
49
+ if (config.llm === undefined)
50
+ return undefined;
51
+ const { thinking, ...rest } = config.llm;
52
+ return {
53
+ ...config,
54
+ llm: { ...rest, model: to, ...keepThinking(catalogue, rest.provider, to, thinking) },
55
+ };
56
+ }
57
+ case "audio":
58
+ return config.audio === undefined
59
+ ? undefined
60
+ : { ...config, audio: { ...config.audio, model: to } };
61
+ case "images":
62
+ return config.images === undefined
63
+ ? undefined
64
+ : { ...config, images: { ...config.images, model: to } };
65
+ case "animate":
66
+ return config.videoEdit === undefined
67
+ ? undefined
68
+ : { ...config, videoEdit: { ...config.videoEdit, animateModel: to } };
69
+ }
70
+ }
71
+ const changedMeanwhile = "This changed while you were looking at it. Reload Settings → Models to see what still uses a retired model.";
72
+ // One click on "Switch to <model>": changes that one choice and nothing else. A template gets a
73
+ // new version, and the schedules that ran its latest version follow it to the new one.
74
+ export async function switchRetiredModel(deps, catalogue, request) {
75
+ if (retiredStatus(catalogue, request.slot, {
76
+ provider: request.from.provider,
77
+ model: request.to,
78
+ }) !== null)
79
+ return {
80
+ ok: false,
81
+ message: `${request.to} is not an active model of this provider. Reload Settings → Models and use the suggested switch, or choose a model yourself in the ${request.kind}.`,
82
+ };
83
+ switch (request.kind) {
84
+ case "template":
85
+ return switchTemplate(deps, catalogue, request, request.id);
86
+ case "schedule": {
87
+ const row = deps.db
88
+ .prepare("SELECT s.template_id AS templateId,s.template_version AS version,t.head_version AS head FROM schedules s JOIN project_templates t ON t.id=s.template_id WHERE s.id=? AND s.deleted_at IS NULL")
89
+ .get(request.id);
90
+ if (row === undefined)
91
+ return { ok: false, message: changedMeanwhile };
92
+ if (row.version !== row.head)
93
+ return {
94
+ ok: false,
95
+ message: "This schedule runs an older version of its template. In Library → Schedules, choose Edit, pick the template again so it runs the latest version, then save.",
96
+ };
97
+ return switchTemplate(deps, catalogue, request, String(row.templateId));
98
+ }
99
+ case "draft":
100
+ return switchDraft(deps, catalogue, request);
101
+ case "project":
102
+ return switchProject(deps, catalogue, request);
103
+ }
104
+ }
105
+ function switchTemplate(deps, catalogue, request, templateId) {
106
+ return transact(deps.db, () => {
107
+ const template = templateById(deps.db, templateId);
108
+ if (template === undefined)
109
+ return { ok: false, message: changedMeanwhile };
110
+ const form = switchForm(template.document.form, catalogue, request);
111
+ if (form === undefined)
112
+ return { ok: true, changed: false };
113
+ const saved = updateTemplate(deps, {
114
+ id: template.id,
115
+ name: template.name,
116
+ document: { ...template.document, form },
117
+ baseVersion: template.version,
118
+ mutationId: deps.uuid(),
119
+ });
120
+ if (!saved.ok)
121
+ return {
122
+ ok: false,
123
+ message: saved.reason === "missing-prompt"
124
+ ? `The template "${template.name}" uses a prompt or intro/outro that was deleted, so it cannot be saved. Open it in Library → Templates, choose another, and save.`
125
+ : changedMeanwhile,
126
+ };
127
+ deps.db
128
+ .prepare("UPDATE schedules SET template_version=?,version=version+1,updated_at=? WHERE template_id=? AND template_version=? AND deleted_at IS NULL AND status IN ('active','paused')")
129
+ .run(saved.value.version, deps.clock.now().toISOString(), template.id, template.version);
130
+ return { ok: true, changed: true };
131
+ });
132
+ }
133
+ function switchDraft(deps, catalogue, request) {
134
+ const row = draftRow(deps.db, request.id);
135
+ const document = row === undefined ? null : documentOf(row);
136
+ if (row === undefined || document === null || row.state !== "active")
137
+ return { ok: false, message: changedMeanwhile };
138
+ const form = switchForm(document.form, catalogue, request);
139
+ if (form === undefined)
140
+ return { ok: true, changed: false };
141
+ const saved = saveDraft({ ...deps, uuid: deps.uuid }, {
142
+ id: row.id,
143
+ baseVersion: row.version,
144
+ mutationId: deps.uuid(),
145
+ document: { ...document, form },
146
+ });
147
+ return saved.ok ? { ok: true, changed: true } : { ok: false, message: changedMeanwhile };
148
+ }
149
+ async function switchProject(deps, catalogue, request) {
150
+ const project = projectById(deps.db, request.id);
151
+ if (project === undefined)
152
+ return { ok: false, message: changedMeanwhile };
153
+ if (stagesOf(deps.db, project.id).some((stage) => stage.state === "running"))
154
+ return {
155
+ ok: false,
156
+ message: "This project is running. Wait for it to finish or pause it, then switch.",
157
+ };
158
+ if (currentRevisionId(deps.db, project.id) === undefined) {
159
+ // A project never edited has no saved versions yet: its configuration is the run itself.
160
+ const config = switchConfig(project.config, catalogue, request);
161
+ if (config === undefined)
162
+ return { ok: true, changed: false };
163
+ updateProjectConfig(deps.db, project.id, config, deps.clock.now().toISOString());
164
+ return { ok: true, changed: true };
165
+ }
166
+ const baseline = await ensureBaseline(deps, project.id);
167
+ if (!baseline.ok)
168
+ return { ok: false, message: changedMeanwhile };
169
+ const { revision } = baseline.view;
170
+ const config = switchConfig(revision.config, catalogue, request);
171
+ if (config === undefined)
172
+ return { ok: true, changed: false };
173
+ // The same save Edit project makes: a new version with only the model changed.
174
+ const saved = await saveRevision(deps, {
175
+ projectId: project.id,
176
+ baseRevisionId: revision.id,
177
+ idempotencyKey: deps.uuid(),
178
+ edit: { config, content: revision.content },
179
+ });
180
+ return saved.ok
181
+ ? { ok: true, changed: true }
182
+ : {
183
+ ok: false,
184
+ message: `Slopify could not save the new model on "${project.title}"${saved.reason === "conflict"
185
+ ? " because the project changed meanwhile"
186
+ : "fields" in saved && saved.fields?.[0] !== undefined
187
+ ? ` (${saved.fields[0].message})`
188
+ : ""}. Open the project, choose Edit project and change it under Providers.`,
189
+ };
190
+ }
191
+ // "Switch all": every listed use that has a suggestion and no reason to hold back, one by one.
192
+ export async function switchAllRetired(deps, catalogue) {
193
+ let switched = 0;
194
+ const failed = [];
195
+ // Templates first: switching one also moves the schedules that follow it.
196
+ const order = ["template", "schedule", "draft", "project"];
197
+ const usages = [...retiredModelUsage(deps.db, catalogue)].sort((a, b) => order.indexOf(a.kind) - order.indexOf(b.kind));
198
+ for (const usage of usages) {
199
+ if (usage.blocked !== null || usage.replacement === null)
200
+ continue;
201
+ const result = await switchRetiredModel(deps, catalogue, requestOf(usage));
202
+ if (!result.ok)
203
+ failed.push({ key: usage.key, message: result.message });
204
+ else if (result.changed)
205
+ switched++;
206
+ }
207
+ return { switched, failed };
208
+ }
209
+ export function requestOf(usage) {
210
+ return {
211
+ kind: usage.kind,
212
+ id: usage.id,
213
+ slot: usage.slot,
214
+ from: { provider: usage.provider, model: usage.model },
215
+ to: usage.replacement?.id ?? "",
216
+ };
217
+ }
@@ -0,0 +1,185 @@
1
+ import { z } from "zod";
2
+ import { isVideoModel } from "../../catalog/schema.js";
3
+ import { listProjects, stagesOf } from "../admission/repo.js";
4
+ import { draftRows } from "../play-drafts/repo.js";
5
+ import { documentOf } from "../play-drafts/service.js";
6
+ import { templateById } from "../project-templates/repo.js";
7
+ import { currentRevisionId, revisionById } from "../revisions/repo.js";
8
+ import { isUncataloguedProvider } from "../settings/model.js";
9
+ import { usageSlots } from "./model.js";
10
+ export { slotLabels, usageKinds, usageSlots } from "./model.js";
11
+ function familyOf(slot) {
12
+ return slot === "llm" ? "llm" : slot === "audio" ? "tts" : "image";
13
+ }
14
+ // The model choices a Play form (a draft or a template) makes.
15
+ export function formChoices(form) {
16
+ const edit = form.videoEdit;
17
+ return {
18
+ llm: form.llm,
19
+ audio: form.audio,
20
+ images: form.images,
21
+ ...(edit !== undefined && edit.animate !== "off"
22
+ ? { animate: { provider: form.images.provider, model: edit.animateModel } }
23
+ : {}),
24
+ };
25
+ }
26
+ // The same for a project's run configuration.
27
+ export function configChoices(config) {
28
+ const edit = config.videoEdit;
29
+ return {
30
+ ...(config.llm === undefined ? {} : { llm: config.llm }),
31
+ ...(config.audio === undefined ? {} : { audio: config.audio }),
32
+ ...(config.images === undefined ? {} : { images: config.images }),
33
+ ...(edit !== undefined && edit.animate !== "off" && config.images !== undefined
34
+ ? { animate: { provider: config.images.provider, model: edit.animateModel } }
35
+ : {}),
36
+ };
37
+ }
38
+ // Whether a choice names a catalogued model that is no longer offered. Command-line tools
39
+ // list their own models and are checked by the health check instead.
40
+ export function retiredStatus(catalogue, slot, choice) {
41
+ if (choice.provider === "" ||
42
+ choice.model.trim() === "" ||
43
+ isUncataloguedProvider(choice.provider))
44
+ return null;
45
+ const row = catalogue[familyOf(slot)].find((model) => model.provider === choice.provider &&
46
+ model.id === choice.model &&
47
+ isVideoModel(model) === (slot === "animate"));
48
+ if (row === undefined)
49
+ return "unlisted";
50
+ if (row.deprecated)
51
+ return "retired";
52
+ return row.enabled ? null : "unlisted";
53
+ }
54
+ // The closest active model from the same provider and list: the one sharing the longest
55
+ // start of its ID (a newer version of the same line), the catalogue's order breaking ties.
56
+ export function suggestReplacement(catalogue, slot, choice) {
57
+ let best = null;
58
+ for (const row of catalogue[familyOf(slot)]) {
59
+ if (row.provider !== choice.provider ||
60
+ row.id === choice.model ||
61
+ !row.enabled ||
62
+ row.deprecated ||
63
+ isVideoModel(row) !== (slot === "animate"))
64
+ continue;
65
+ const score = sharedStart(row.id.toLowerCase(), choice.model.toLowerCase());
66
+ if (best === null || score > best.score)
67
+ best = { id: row.id, name: row.name, score };
68
+ }
69
+ return best === null ? null : { id: best.id, name: best.name };
70
+ }
71
+ function sharedStart(a, b) {
72
+ let index = 0;
73
+ while (index < a.length && index < b.length && a[index] === b[index])
74
+ index++;
75
+ return index;
76
+ }
77
+ function usagesOf(catalogue, base, choices, blocked) {
78
+ const found = [];
79
+ for (const slot of usageSlots) {
80
+ const choice = choices[slot];
81
+ if (choice === undefined)
82
+ continue;
83
+ const why = retiredStatus(catalogue, slot, choice);
84
+ if (why === null)
85
+ continue;
86
+ const replacement = suggestReplacement(catalogue, slot, choice);
87
+ found.push({
88
+ ...base,
89
+ key: `${base.kind}:${base.id}:${slot}`,
90
+ slot,
91
+ provider: choice.provider,
92
+ model: choice.model,
93
+ why,
94
+ replacement,
95
+ blocked: blocked ??
96
+ (replacement === null
97
+ ? "This provider has no other model to switch to. Choose another provider for this step and save."
98
+ : null),
99
+ });
100
+ }
101
+ return found;
102
+ }
103
+ const scheduleRow = z.object({
104
+ id: z.string(),
105
+ name: z.string(),
106
+ template_id: z.string(),
107
+ template_version: z.number(),
108
+ });
109
+ const headRow = z.object({ head_version: z.number() });
110
+ export function choiceSites(db) {
111
+ const sites = [];
112
+ const heads = new Map();
113
+ for (const row of db.prepare("SELECT id FROM project_templates ORDER BY id").all()) {
114
+ const id = z.object({ id: z.string() }).parse(row).id;
115
+ const template = templateById(db, id);
116
+ if (template === undefined)
117
+ continue;
118
+ heads.set(id, template.version);
119
+ sites.push({
120
+ kind: "template",
121
+ id,
122
+ name: template.name,
123
+ choices: formChoices(template.document.form),
124
+ blocked: null,
125
+ });
126
+ }
127
+ for (const raw of db
128
+ .prepare("SELECT id,name,template_id,template_version FROM schedules WHERE deleted_at IS NULL AND status IN ('active','paused') ORDER BY lower(name),id")
129
+ .all()) {
130
+ const row = scheduleRow.parse(raw);
131
+ const template = templateById(db, row.template_id, row.template_version);
132
+ if (template === undefined)
133
+ continue;
134
+ const head = heads.get(row.template_id) ??
135
+ headRow.parse(db.prepare("SELECT head_version FROM project_templates WHERE id=?").get(row.template_id)).head_version;
136
+ sites.push({
137
+ kind: "schedule",
138
+ id: row.id,
139
+ name: row.name,
140
+ choices: formChoices(template.document.form),
141
+ blocked: head === row.template_version
142
+ ? null
143
+ : `This schedule runs an older version of the template "${template.name}". In Library → Schedules, choose Edit, pick the template again so it runs the latest version, then save.`,
144
+ });
145
+ }
146
+ for (const row of draftRows(db)) {
147
+ if (row.state !== "active")
148
+ continue;
149
+ const document = documentOf(row);
150
+ if (document === null)
151
+ continue;
152
+ sites.push({
153
+ kind: "draft",
154
+ id: row.id,
155
+ name: row.title.trim() === "" ? "Untitled draft" : row.title,
156
+ choices: formChoices(document.form),
157
+ blocked: null,
158
+ });
159
+ }
160
+ for (const project of listProjects(db)) {
161
+ const stages = stagesOf(db, project.id);
162
+ // Only a project with steps still to run will use its models again.
163
+ if (!stages.some((stage) => ["pending", "running", "failed", "canceled"].includes(stage.state)))
164
+ continue;
165
+ const revisionId = currentRevisionId(db, project.id);
166
+ const config = revisionId === undefined
167
+ ? project.config
168
+ : (revisionById(db, project.id, revisionId)?.config ?? project.config);
169
+ sites.push({
170
+ kind: "project",
171
+ id: project.id,
172
+ name: project.title,
173
+ choices: configChoices(config),
174
+ blocked: stages.some((stage) => stage.state === "running")
175
+ ? "This project is running. Wait for it to finish or pause it, then switch."
176
+ : null,
177
+ });
178
+ }
179
+ return sites;
180
+ }
181
+ // Every template, schedule, draft and unfinished project that still picks a retired or
182
+ // unlisted model, with the suggested replacement. Nothing is changed here.
183
+ export function retiredModelUsage(db, catalogue) {
184
+ return choiceSites(db).flatMap((site) => usagesOf(catalogue, { kind: site.kind, id: site.id, name: site.name }, site.choices, site.blocked));
185
+ }
@@ -0,0 +1,31 @@
1
+ import { transact } from "../../kernel/db/tx.js";
2
+ import { aliasProblems, narrationAliasSchema } from "./aliases-schema.js";
3
+ export function listNarrationAliases(db) {
4
+ return db
5
+ .prepare("SELECT written,spoken,whole_word,case_sensitive FROM narration_aliases ORDER BY position,id")
6
+ .all()
7
+ .flatMap((row) => {
8
+ // A row edited by hand into something the editor can't save is left out, not fatal.
9
+ const parsed = narrationAliasSchema.safeParse({
10
+ written: row.written,
11
+ spoken: row.spoken,
12
+ wholeWord: row.whole_word === 1,
13
+ caseSensitive: row.case_sensitive === 1,
14
+ });
15
+ return parsed.success ? [parsed.data] : [];
16
+ });
17
+ }
18
+ export function saveNarrationAliases(deps, rows) {
19
+ const fields = aliasProblems(rows);
20
+ if (fields.length > 0)
21
+ return { ok: false, fields };
22
+ const aliases = rows.map((row) => narrationAliasSchema.parse(row));
23
+ const now = deps.clock.now().toISOString();
24
+ transact(deps.db, () => {
25
+ deps.db.prepare("DELETE FROM narration_aliases").run();
26
+ const insert = deps.db.prepare("INSERT INTO narration_aliases(id,position,written,spoken,whole_word,case_sensitive,updated_at) VALUES (?,?,?,?,?,?,?)");
27
+ for (const [position, alias] of aliases.entries())
28
+ insert.run(deps.ids.next(), position, alias.written, alias.spoken, alias.wholeWord ? 1 : 0, alias.caseSensitive ? 1 : 0, now);
29
+ });
30
+ return { ok: true, aliases };
31
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ import { aliasCountMax, aliasSpokenMax, aliasWrittenMax, } from "../../kernel/ports/narration-aliases.js";
3
+ // One alias as a project config and Library → Aliases store it.
4
+ export const narrationAliasSchema = z
5
+ .object({
6
+ written: z.string().trim().min(1).max(aliasWrittenMax),
7
+ spoken: z.string().trim().min(1).max(aliasSpokenMax),
8
+ wholeWord: z.boolean(),
9
+ caseSensitive: z.boolean(),
10
+ })
11
+ .strict()
12
+ .readonly();
13
+ export const narrationAliasesSchema = z.array(narrationAliasSchema).max(aliasCountMax).readonly();
14
+ export function aliasProblems(rows) {
15
+ const problems = [];
16
+ if (rows.length > aliasCountMax)
17
+ problems.push({
18
+ field: "aliases",
19
+ message: `Keep at most ${aliasCountMax} aliases; remove some, then Save.`,
20
+ });
21
+ const seen = new Map();
22
+ for (const [index, raw] of rows.entries()) {
23
+ const row = index + 1;
24
+ const parsed = narrationAliasSchema.safeParse(raw);
25
+ if (!parsed.success) {
26
+ const written = z.object({ written: z.string() }).safeParse(raw);
27
+ const spoken = z.object({ spoken: z.string() }).safeParse(raw);
28
+ problems.push({
29
+ field: `aliases.${index}`,
30
+ message: !written.success || written.data.written.trim() === ""
31
+ ? `Alias ${row}: write the word or phrase as it appears in the article.`
32
+ : !spoken.success || spoken.data.spoken.trim() === ""
33
+ ? `Alias ${row}: write how the narrator should say it.`
34
+ : `Alias ${row}: keep the written form under ${aliasWrittenMax} characters and the spoken form under ${aliasSpokenMax}.`,
35
+ });
36
+ continue;
37
+ }
38
+ const key = aliasKey(parsed.data);
39
+ const earlier = seen.get(key);
40
+ if (earlier !== undefined)
41
+ problems.push({
42
+ field: `aliases.${index}`,
43
+ message: `Alias ${row}: alias ${earlier} already covers the same written form; keep one of them.`,
44
+ });
45
+ else
46
+ seen.set(key, row);
47
+ }
48
+ return problems;
49
+ }
50
+ function aliasKey(alias) {
51
+ const written = alias.written.trim().replace(/\s+/gu, " ");
52
+ return alias.caseSensitive ? `=${written}` : `~${written.toLocaleLowerCase("en")}`;
53
+ }
@@ -0,0 +1,49 @@
1
+ import { aliasMatches, } from "../../kernel/ports/narration-aliases.js";
2
+ // Narration aliases ride the same rails as the Pronunciation Glossary: a span of the clean
3
+ // narration text with what the voice gets instead. The clean text (what the transcript and
4
+ // captions show) is untouched. A span never holds whitespace, so an alias whose written form
5
+ // has several words puts all of the spoken form on its first word and nothing on the rest.
6
+ export function aliasSpans(source, aliases) {
7
+ const spans = [];
8
+ for (const match of aliasMatches(source, aliases))
9
+ for (const [index, word] of Array.from(source.slice(match.start, match.end).matchAll(/\S+/gu)).entries()) {
10
+ const start = match.start + word.index;
11
+ spans.push({ start, end: start + word[0].length, text: index === 0 ? match.spoken : "" });
12
+ }
13
+ return spans;
14
+ }
15
+ // An alias wins over a glossary pronunciation of the same words: it says what to read, and the
16
+ // glossary only how to read what is written.
17
+ export function withAliasSpans(glossary, aliases) {
18
+ if (aliases.length === 0)
19
+ return glossary;
20
+ const kept = glossary.filter((span) => !aliases.some((alias) => alias.start < span.end && span.start < alias.end));
21
+ return [...kept, ...aliases].sort((a, b) => a.start - b.start);
22
+ }
23
+ // The sentences Narration Preparation reads, with the aliases already applied, so its
24
+ // delivery cues fit what will actually be said. The sentences are cut from the clean text
25
+ // first, so their numbers stay the ones the cues are checked and placed against.
26
+ export function aliasedSentences(sentences, matches) {
27
+ if (matches.length === 0)
28
+ return sentences;
29
+ let offset = 0;
30
+ return sentences.map((sentence) => {
31
+ const start = offset;
32
+ const end = start + sentence.text.length;
33
+ offset = end;
34
+ let text = "";
35
+ let at = start;
36
+ for (const match of matches) {
37
+ if (match.end <= start || match.start >= end)
38
+ continue;
39
+ if (match.start >= at)
40
+ text += sentence.text.slice(at - start, match.start - start);
41
+ // The spoken form goes with the sentence the written form starts in.
42
+ if (match.start >= start)
43
+ text += match.spoken;
44
+ at = Math.max(at, Math.min(match.end, end));
45
+ }
46
+ text += sentence.text.slice(at - start);
47
+ return { sentence: sentence.sentence, text };
48
+ });
49
+ }