@gentbajko/slopify 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +3 -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 +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.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/inworld-async.js +9 -5
  44. package/dist/adapters/tts/inworld.js +17 -3
  45. package/dist/adapters/tts/openai.js +3 -0
  46. package/dist/adapters/tts/voice-languages.js +80 -0
  47. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  48. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  49. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/OFL.txt +93 -0
  53. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  54. package/dist/assets/sample/sample-audiobook.tar +0 -0
  55. package/dist/assets/sample/sample-podcast.tar +0 -0
  56. package/dist/assets/sample/sample-project.tar +0 -0
  57. package/dist/catalog/merge.js +132 -0
  58. package/dist/catalog/registry.js +13 -13
  59. package/dist/catalog/schema.js +2 -0
  60. package/dist/catalog/store.js +147 -8
  61. package/dist/compose.yaml +71 -0
  62. package/dist/edge/autostart/docker-record.js +32 -0
  63. package/dist/edge/autostart/docker.js +50 -0
  64. package/dist/edge/autostart/index.js +78 -0
  65. package/dist/edge/autostart/launcher.js +176 -0
  66. package/dist/edge/autostart/model.js +3 -0
  67. package/dist/edge/autostart/native.js +210 -0
  68. package/dist/edge/autostart/prompt.js +66 -0
  69. package/dist/edge/autostart/service.js +81 -0
  70. package/dist/edge/cli.js +57 -51
  71. package/dist/edge/docker-install/activation.js +56 -0
  72. package/dist/edge/docker-install/apply.js +458 -0
  73. package/dist/edge/docker-install/engine.js +362 -0
  74. package/dist/edge/docker-install/run.js +154 -0
  75. package/dist/edge/docker-install/state.js +146 -0
  76. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  77. package/dist/edge/docker.js +14 -22
  78. package/dist/edge/host-cli.js +1 -1
  79. package/dist/edge/http/actions.js +32 -10
  80. package/dist/edge/http/app.js +58 -2
  81. package/dist/edge/http/auditions.js +70 -0
  82. package/dist/edge/http/autostart.js +37 -0
  83. package/dist/edge/http/channel-memory.js +92 -0
  84. package/dist/edge/http/channels.js +128 -0
  85. package/dist/edge/http/entries.js +22 -3
  86. package/dist/edge/http/files.js +1 -1
  87. package/dist/edge/http/folder-location.js +1 -1
  88. package/dist/edge/http/home.js +39 -0
  89. package/dist/edge/http/host-cli.js +29 -5
  90. package/dist/edge/http/narration-peaks.js +76 -0
  91. package/dist/edge/http/onboarding.js +152 -0
  92. package/dist/edge/http/open-folder.js +1 -1
  93. package/dist/edge/http/patch-notes.js +45 -0
  94. package/dist/edge/http/planning.js +4 -2
  95. package/dist/edge/http/project-create.js +87 -0
  96. package/dist/edge/http/projects.js +31 -99
  97. package/dist/edge/http/prompts.js +40 -3
  98. package/dist/edge/http/pronunciations.js +23 -4
  99. package/dist/edge/http/providers.js +86 -0
  100. package/dist/edge/http/reviews.js +121 -0
  101. package/dist/edge/http/revision-files.js +1 -1
  102. package/dist/edge/http/revisions.js +1 -1
  103. package/dist/edge/http/run-cost.js +25 -0
  104. package/dist/edge/http/schedules.js +111 -6
  105. package/dist/edge/http/settings.js +84 -6
  106. package/dist/edge/http/storage-files.js +50 -0
  107. package/dist/edge/http/storage.js +29 -2
  108. package/dist/edge/http/studio.js +207 -0
  109. package/dist/edge/http/style-preview.js +100 -0
  110. package/dist/edge/http/subtitles.js +1 -1
  111. package/dist/edge/http/trash.js +69 -0
  112. package/dist/edge/http/update.js +24 -3
  113. package/dist/edge/http/whats-new.js +15 -0
  114. package/dist/edge/http/youtube-edits.js +83 -0
  115. package/dist/edge/native-update.js +62 -0
  116. package/dist/host-cli/open-folder.js +10 -10
  117. package/dist/host-cli/runtime.js +9 -1
  118. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  119. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  120. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  121. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  122. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  123. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  124. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  125. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  126. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  127. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  128. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  129. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  130. package/dist/kernel/paths.js +27 -6
  131. package/dist/kernel/pipeline.js +4 -1
  132. package/dist/kernel/ports/host-cli.js +122 -14
  133. package/dist/kernel/ports/key-probe.js +1 -0
  134. package/dist/kernel/ports/languages.js +157 -0
  135. package/dist/kernel/ports/model.js +5 -2
  136. package/dist/kernel/ports/narration-aliases.js +60 -0
  137. package/dist/kernel/ports/plan-limits.js +36 -0
  138. package/dist/kernel/runner/attempt.js +17 -8
  139. package/dist/kernel/runner/audition.js +36 -0
  140. package/dist/kernel/runner/graph.js +24 -5
  141. package/dist/kernel/runner/index.js +35 -5
  142. package/dist/kernel/runner/meter.js +1 -0
  143. package/dist/kernel/runner/providers.js +142 -7
  144. package/dist/kernel/runner/retry-policy.js +43 -0
  145. package/dist/kernel/runner/standalone.js +134 -0
  146. package/dist/kernel/runner/work-authority.js +28 -2
  147. package/dist/main.js +272 -16
  148. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  149. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  150. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  151. package/dist/patch-notes/3.0.0.md +70 -0
  152. package/dist/patch-notes/index.json +26 -0
  153. package/dist/slices/admission/model.js +27 -0
  154. package/dist/slices/admission/repo.js +24 -4
  155. package/dist/slices/admission/rules.js +79 -1
  156. package/dist/slices/admission/schema.js +58 -1
  157. package/dist/slices/admission/short-mode.js +47 -0
  158. package/dist/slices/admission/start.js +41 -5
  159. package/dist/slices/article/continuation.js +2 -2
  160. package/dist/slices/article/provided-entries.js +2 -2
  161. package/dist/slices/article/run.js +6 -2
  162. package/dist/slices/article/segments.js +3 -3
  163. package/dist/slices/backups/folder.js +14 -6
  164. package/dist/slices/batch/index.js +7 -2
  165. package/dist/slices/cancel/index.js +1 -1
  166. package/dist/slices/channels/cast-images.js +110 -0
  167. package/dist/slices/channels/cast-match.js +32 -0
  168. package/dist/slices/channels/images.js +19 -0
  169. package/dist/slices/channels/model.js +5 -0
  170. package/dist/slices/channels/rebrand.js +100 -0
  171. package/dist/slices/channels/repo.js +218 -0
  172. package/dist/slices/channels/runs.js +129 -0
  173. package/dist/slices/channels/schema.js +129 -0
  174. package/dist/slices/channels/service.js +180 -0
  175. package/dist/slices/channels/studio-csv.js +110 -0
  176. package/dist/slices/channels/videos.js +120 -0
  177. package/dist/slices/checkpoints/recovery.js +10 -0
  178. package/dist/slices/control/index.js +2 -1
  179. package/dist/slices/document/fonts.js +1 -1
  180. package/dist/slices/document/render.js +1 -1
  181. package/dist/slices/episodes/related.js +90 -0
  182. package/dist/slices/episodes/repo.js +60 -0
  183. package/dist/slices/episodes/service.js +58 -0
  184. package/dist/slices/episodes/summarize.js +170 -0
  185. package/dist/slices/estimate/index.js +109 -28
  186. package/dist/slices/estimate/requests.js +60 -2
  187. package/dist/slices/fixes/rules.js +73 -0
  188. package/dist/slices/fonts/coverage.js +82 -0
  189. package/dist/slices/fonts/sfnt.js +74 -0
  190. package/dist/slices/images/run.js +8 -5
  191. package/dist/slices/images/scale.js +84 -0
  192. package/dist/slices/library/diff.js +122 -0
  193. package/dist/slices/library/history.js +100 -0
  194. package/dist/slices/library/model.js +3 -0
  195. package/dist/slices/library/photorealistic.js +49 -0
  196. package/dist/slices/library/repo.js +19 -14
  197. package/dist/slices/library/save.js +62 -12
  198. package/dist/slices/library/slots.js +7 -1
  199. package/dist/slices/library/used-by.js +153 -0
  200. package/dist/slices/loudness/level-pieces.js +41 -0
  201. package/dist/slices/loudness/loudnorm.js +164 -0
  202. package/dist/slices/loudness/model.js +153 -0
  203. package/dist/slices/model-upkeep/model.js +10 -0
  204. package/dist/slices/model-upkeep/switch.js +217 -0
  205. package/dist/slices/model-upkeep/usage.js +183 -0
  206. package/dist/slices/narration/aliases-library.js +31 -0
  207. package/dist/slices/narration/aliases-schema.js +53 -0
  208. package/dist/slices/narration/aliases.js +49 -0
  209. package/dist/slices/narration/blocks.js +352 -0
  210. package/dist/slices/narration/chunk.js +19 -7
  211. package/dist/slices/narration/concat.js +2 -2
  212. package/dist/slices/narration/describe.js +45 -0
  213. package/dist/slices/narration/pauses-model.js +63 -0
  214. package/dist/slices/narration/pauses.js +269 -0
  215. package/dist/slices/narration/peaks-model.js +1 -0
  216. package/dist/slices/narration/peaks.js +94 -0
  217. package/dist/slices/narration/preparation.js +21 -3
  218. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  219. package/dist/slices/narration/pronunciation.js +72 -25
  220. package/dist/slices/narration/run.js +6 -6
  221. package/dist/slices/narration/shared-glossary.js +1 -1
  222. package/dist/slices/narration/steering.js +15 -4
  223. package/dist/slices/notifications/notifier.js +21 -3
  224. package/dist/slices/notifications/rules.js +22 -1
  225. package/dist/slices/onboarding/copy.js +143 -0
  226. package/dist/slices/onboarding/first-run.js +46 -0
  227. package/dist/slices/onboarding/install.js +119 -0
  228. package/dist/slices/onboarding/model.js +64 -0
  229. package/dist/slices/onboarding/packs.js +491 -0
  230. package/dist/slices/onboarding/quick-short.js +129 -0
  231. package/dist/slices/onboarding/sample.js +132 -0
  232. package/dist/slices/onboarding/state.js +68 -0
  233. package/dist/slices/onboarding/template.js +73 -0
  234. package/dist/slices/patch-notes/library.js +73 -0
  235. package/dist/slices/patch-notes/seen.js +77 -0
  236. package/dist/slices/play-drafts/convert.js +125 -6
  237. package/dist/slices/play-drafts/readiness.js +3 -0
  238. package/dist/slices/play-drafts/repo.js +2 -0
  239. package/dist/slices/play-drafts/review-inputs.js +20 -3
  240. package/dist/slices/play-drafts/schema.js +89 -0
  241. package/dist/slices/play-drafts/service.js +4 -1
  242. package/dist/slices/play-drafts/start.js +43 -18
  243. package/dist/slices/project-templates/from-project.js +50 -4
  244. package/dist/slices/project-templates/one-off.js +26 -0
  245. package/dist/slices/project-templates/repo.js +2 -1
  246. package/dist/slices/project-templates/service.js +15 -4
  247. package/dist/slices/project-templates/setup.js +18 -2
  248. package/dist/slices/rebuild/admission-repo.js +1 -1
  249. package/dist/slices/rebuild/model.js +6 -0
  250. package/dist/slices/rebuild/preview-details.js +46 -3
  251. package/dist/slices/rebuild/preview-plan.js +13 -6
  252. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  253. package/dist/slices/rebuild/recipe-audio.js +66 -12
  254. package/dist/slices/rebuild/recipe-build.js +8 -4
  255. package/dist/slices/rebuild/recipe-cast.js +18 -0
  256. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  257. package/dist/slices/rebuild/recipe-describe.js +93 -0
  258. package/dist/slices/rebuild/recipe-edit.js +13 -1
  259. package/dist/slices/rebuild/recipe-exports.js +62 -8
  260. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  261. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  262. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  263. package/dist/slices/rebuild/recipe-model.js +25 -6
  264. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  265. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  266. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  267. package/dist/slices/rebuild/recipe-reference.js +15 -1
  268. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  269. package/dist/slices/rebuild/recipe-save.js +4 -1
  270. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  271. package/dist/slices/rebuild/recipe-text.js +114 -19
  272. package/dist/slices/rebuild/recipe-validation.js +14 -2
  273. package/dist/slices/rebuild/recipe-visual.js +96 -33
  274. package/dist/slices/rebuild/recipe-voices.js +286 -0
  275. package/dist/slices/rebuild/recipe-work.js +77 -6
  276. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  277. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  278. package/dist/slices/rebuild/recovery-model.js +3 -0
  279. package/dist/slices/rebuild/recovery-selection.js +28 -2
  280. package/dist/slices/rebuild/recovery.js +35 -17
  281. package/dist/slices/rebuild/repo.js +3 -3
  282. package/dist/slices/rebuild/retry.js +18 -0
  283. package/dist/slices/rebuild/review-redo.js +110 -0
  284. package/dist/slices/rebuild/runtime-actions.js +5 -0
  285. package/dist/slices/rebuild/runtime-admission.js +4 -1
  286. package/dist/slices/rebuild/runtime-animate.js +4 -4
  287. package/dist/slices/rebuild/runtime-article.js +5 -5
  288. package/dist/slices/rebuild/runtime-document.js +4 -4
  289. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  290. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  291. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  292. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  293. package/dist/slices/rebuild/runtime-export.js +84 -19
  294. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  295. package/dist/slices/rebuild/runtime-image.js +21 -2
  296. package/dist/slices/rebuild/runtime-local.js +148 -24
  297. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  298. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  299. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  300. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  301. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  302. package/dist/slices/rebuild/runtime-provider.js +126 -18
  303. package/dist/slices/rebuild/runtime-publication.js +21 -5
  304. package/dist/slices/rebuild/runtime-review.js +237 -0
  305. package/dist/slices/rebuild/runtime-run.js +17 -11
  306. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  307. package/dist/slices/rebuild/runtime-store.js +39 -10
  308. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  309. package/dist/slices/rebuild/runtime-voices.js +103 -0
  310. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  311. package/dist/slices/rebuild/soften.js +49 -0
  312. package/dist/slices/rebuild/transition-repo.js +9 -2
  313. package/dist/slices/research/run.js +4 -4
  314. package/dist/slices/research/synthesis.js +2 -2
  315. package/dist/slices/reviews/model.js +105 -0
  316. package/dist/slices/reviews/outcome.js +22 -0
  317. package/dist/slices/reviews/repo.js +106 -0
  318. package/dist/slices/reviews/rules.js +63 -0
  319. package/dist/slices/reviews/verdict.js +71 -0
  320. package/dist/slices/reviews/view.js +30 -0
  321. package/dist/slices/revisions/adopt-content.js +5 -2
  322. package/dist/slices/revisions/adopt.js +3 -1
  323. package/dist/slices/revisions/downloads.js +7 -1
  324. package/dist/slices/revisions/image-plan.js +183 -0
  325. package/dist/slices/revisions/mutation-assets.js +10 -0
  326. package/dist/slices/revisions/mutation-cues.js +71 -0
  327. package/dist/slices/revisions/mutation-request.js +2 -1
  328. package/dist/slices/revisions/mutations.js +97 -3
  329. package/dist/slices/revisions/schema.js +11 -1
  330. package/dist/slices/run-cost/limits.js +128 -0
  331. package/dist/slices/run-cost/meter.js +47 -0
  332. package/dist/slices/run-cost/panel.js +234 -0
  333. package/dist/slices/run-cost/pricing.js +92 -0
  334. package/dist/slices/run-cost/week.js +108 -0
  335. package/dist/slices/schedules/agenda.js +158 -0
  336. package/dist/slices/schedules/repo.js +58 -18
  337. package/dist/slices/schedules/scheduler.js +67 -16
  338. package/dist/slices/schedules/schema.js +138 -1
  339. package/dist/slices/schedules/service.js +43 -4
  340. package/dist/slices/schedules/similar.js +90 -0
  341. package/dist/slices/schedules/topic-list.js +163 -0
  342. package/dist/slices/schedules/topics.js +422 -0
  343. package/dist/slices/settings/first-run.js +102 -0
  344. package/dist/slices/settings/health.js +190 -0
  345. package/dist/slices/settings/key-guides.js +128 -0
  346. package/dist/slices/settings/key-test.js +85 -0
  347. package/dist/slices/settings/model.js +6 -1
  348. package/dist/slices/settings/playback.js +15 -0
  349. package/dist/slices/settings/repo.js +17 -3
  350. package/dist/slices/settings/voices.js +64 -4
  351. package/dist/slices/settings/whats-new.js +64 -0
  352. package/dist/slices/shorts/captions.js +9 -0
  353. package/dist/slices/shorts/pick.js +3 -3
  354. package/dist/slices/shorts/prompts.js +1 -1
  355. package/dist/slices/shorts/render.js +54 -5
  356. package/dist/slices/storage/asset-name.js +3 -0
  357. package/dist/slices/storage/backup-export.js +48 -9
  358. package/dist/slices/storage/backup-format.js +38 -1
  359. package/dist/slices/storage/backup-import.js +191 -19
  360. package/dist/slices/storage/delete-project.js +7 -4
  361. package/dist/slices/storage/documents.js +79 -0
  362. package/dist/slices/storage/files-location.js +531 -0
  363. package/dist/slices/storage/layout.js +14 -4
  364. package/dist/slices/storage/model.js +11 -0
  365. package/dist/slices/storage/portable.js +92 -12
  366. package/dist/slices/storage/reconcile.js +14 -1
  367. package/dist/slices/storage/repo.js +4 -1
  368. package/dist/slices/storage/schema.js +11 -0
  369. package/dist/slices/storage/trim.js +146 -0
  370. package/dist/slices/studio/disclosure.js +74 -0
  371. package/dist/slices/studio/model.js +28 -0
  372. package/dist/slices/studio/pack.js +209 -0
  373. package/dist/slices/studio/settings.js +133 -0
  374. package/dist/slices/style-preview/images.js +61 -0
  375. package/dist/slices/style-preview/render.js +161 -0
  376. package/dist/slices/style-preview/schema.js +45 -0
  377. package/dist/slices/style-preview/service.js +93 -0
  378. package/dist/slices/style-preview/settings.js +89 -0
  379. package/dist/slices/subtitles/captions.js +42 -9
  380. package/dist/slices/subtitles/model.js +22 -0
  381. package/dist/slices/subtitles/prepare.js +27 -16
  382. package/dist/slices/subtitles/transcript.js +1 -1
  383. package/dist/slices/thumbnail/run.js +7 -7
  384. package/dist/slices/trash/model.js +25 -0
  385. package/dist/slices/trash/service.js +273 -0
  386. package/dist/slices/uploads/repo.js +20 -0
  387. package/dist/slices/video/ambient-bed-schema.js +15 -0
  388. package/dist/slices/video/ambient-bed.js +95 -0
  389. package/dist/slices/video/ambient-mix.js +72 -0
  390. package/dist/slices/video/audio-inputs.js +2 -2
  391. package/dist/slices/video/cards.js +5 -2
  392. package/dist/slices/video/edit-list.js +23 -2
  393. package/dist/slices/video/edit-settings.js +13 -3
  394. package/dist/slices/video/ffmpeg.js +63 -9
  395. package/dist/slices/video/figure-card.js +492 -0
  396. package/dist/slices/video/figure-spans.js +51 -0
  397. package/dist/slices/video/plan.js +125 -13
  398. package/dist/slices/video/run.js +14 -12
  399. package/dist/slices/video/slideshow.js +39 -4
  400. package/dist/slices/voices/attribution.js +69 -0
  401. package/dist/slices/voices/audio-files.js +82 -0
  402. package/dist/slices/voices/audition.js +25 -0
  403. package/dist/slices/voices/cast.js +56 -0
  404. package/dist/slices/voices/grouping.js +49 -0
  405. package/dist/slices/voices/join.js +35 -0
  406. package/dist/slices/voices/languages.js +25 -0
  407. package/dist/slices/voices/model.js +179 -0
  408. package/dist/slices/voices/palette.js +26 -0
  409. package/dist/slices/voices/panel.js +91 -0
  410. package/dist/slices/voices/portraits.js +55 -0
  411. package/dist/slices/voices/script.js +136 -0
  412. package/dist/slices/voices/timing.js +58 -0
  413. package/dist/slices/youtube/answer.js +1 -1
  414. package/dist/slices/youtube/chapters.js +126 -0
  415. package/dist/slices/youtube/edits-repo.js +69 -0
  416. package/dist/slices/youtube/edits.js +89 -0
  417. package/dist/slices/youtube/placeholders.js +98 -0
  418. package/dist/slices/youtube/transcript.js +11 -4
  419. package/dist/updater/service.js +66 -21
  420. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  421. package/dist/web/assets/index-CX590kE-.js +303 -0
  422. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  423. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  424. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  425. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  426. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  427. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  428. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  429. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  430. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  431. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-BG8n_uc5.js} +1 -1
  432. package/dist/web/index.html +2 -2
  433. package/package.json +4 -4
  434. package/dist/edge/docker-launch.js +0 -81
  435. package/dist/edge/docker-projects/activation.js +0 -35
  436. package/dist/edge/docker-projects/claims.js +0 -85
  437. package/dist/edge/docker-projects/committed.js +0 -40
  438. package/dist/edge/docker-projects/engine.js +0 -461
  439. package/dist/edge/docker-projects/install.js +0 -254
  440. package/dist/edge/docker-projects/prune.js +0 -66
  441. package/dist/edge/docker-projects/recover.js +0 -100
  442. package/dist/edge/docker-projects/state.js +0 -225
  443. package/dist/web/assets/index-CktpxlJd.js +0 -171
  444. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  445. package/scripts/docker-run.sh +0 -3
  446. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,286 @@
1
+ import { aliasMatches, applyAliases } from "../../kernel/ports/narration-aliases.js";
2
+ import { splitText } from "../../kernel/ports/text.js";
3
+ import { narrationAliasesOf, usesNarrationPreparation } from "../admission/rules.js";
4
+ import { aliasSpans, withAliasSpans } from "../narration/aliases.js";
5
+ import { normalizeNarrationText } from "../narration/plan.js";
6
+ import { parsePronunciationGlossary, pronunciationSpans, } from "../narration/pronunciation.js";
7
+ import { withSharedGlossary } from "../narration/shared-glossary.js";
8
+ import { prepareRequests } from "../narration/steering.js";
9
+ import { groupTurns } from "../voices/grouping.js";
10
+ import { speakerPace } from "../voices/model.js";
11
+ import { parseScript } from "../voices/script.js";
12
+ import { describedText, describeFuture, describing } from "./recipe-describe.js";
13
+ import { recipe, resourceIdentity, } from "./recipe-model.js";
14
+ import { pauseValues } from "./recipe-pauses.js";
15
+ import { preparationForGroup, preparationFuture, preparationTemplate, } from "./recipe-preparation.js";
16
+ export function voiceBodyRecipes(context, voices, script, glossary) {
17
+ const parts = [];
18
+ const preparations = [];
19
+ const layout = [];
20
+ // Which parts end a turn, for the pauses between sentences (a turn gap of 0 is still a turn).
21
+ const turnEnds = new Set();
22
+ let transcript = script.fingerprint ?? script.text ?? null;
23
+ const describe = describing(context);
24
+ const parsed = script.text === null ? undefined : parseScript(script.text, voices.speakers, describe);
25
+ const prepare = voices.speakers.some((speaker) => preparesTurns(context.config, speaker));
26
+ // The narration still to be worked out: the whole script's while it is being written, or
27
+ // every turn's while a turn's delivery cues are (as a single voice waits for all of its own).
28
+ const future = () => recipe(context, "audio:body:future", "audio", {
29
+ kind: "deferred",
30
+ version: 1,
31
+ operation: "body-narration",
32
+ template: [
33
+ script.fingerprint ?? script.text ?? null,
34
+ speakerValues(voices),
35
+ ...(prepare ? [preparationTemplate(context)] : []),
36
+ ...(described.length === 0
37
+ ? []
38
+ : [["described-v1", described.map((row) => row.fingerprint)]]),
39
+ ],
40
+ }, [
41
+ ...new Set([
42
+ ...script.dependsOn,
43
+ ...described.map((row) => row.key),
44
+ ...preparations.map((row) => row.key),
45
+ ]),
46
+ ]);
47
+ let pending = false;
48
+ // A table, figure, equation or code block inside a turn is described within that turn: the
49
+ // turn says the description where the block was, in the same voice.
50
+ const described = [];
51
+ const cards = [];
52
+ let turns = parsed?.ok === true ? parsed.script.turns : [];
53
+ if (describe && script.text === null) {
54
+ const from = script.dependsOn[0];
55
+ if (from !== undefined)
56
+ described.push(describeFuture(context, { key: from, fingerprint: script.fingerprint ?? from }));
57
+ }
58
+ else if (describe && parsed?.ok === true) {
59
+ let offset = 0;
60
+ turns = turns.map((turn) => {
61
+ if (turn.markdown === undefined)
62
+ return turn;
63
+ const said = describedText(context, turn.markdown, script.dependsOn, offset);
64
+ if (said.recipes.length === 0)
65
+ return turn;
66
+ offset += said.recipes.length;
67
+ described.push(...said.recipes);
68
+ cards.push(...said.cards);
69
+ if (said.spoken === null) {
70
+ pending = true;
71
+ return turn;
72
+ }
73
+ return { ...turn, text: said.spoken.trim().replace(/\s*\n\s*/g, " ") };
74
+ });
75
+ }
76
+ preparations.push(...described);
77
+ if (script.text === null) {
78
+ // Turns not yet known are prepared behind one future step, as a single voice's text is.
79
+ const from = script.dependsOn[0];
80
+ if (prepare && from !== undefined)
81
+ preparations.push(preparationFuture(context, "body", { key: from, fingerprint: script.fingerprint ?? from }));
82
+ parts.push(future());
83
+ }
84
+ else if (parsed !== undefined && !parsed.ok) {
85
+ parts.push(refused(context, "audio:body:turn:1", script.dependsOn, `The script can't be narrated: ${parsed.reason} Fix it in Edit project → Article, then Try again.`));
86
+ }
87
+ else if (pending) {
88
+ parts.push(future());
89
+ }
90
+ else if (parsed !== undefined) {
91
+ transcript = ["voices-transcript-v1", turns.map((turn) => [turn.speaker, turn.text])];
92
+ const aliases = narrationAliasesOf(context.config);
93
+ for (const group of groupTurns(turns, voices.speakers, voices.nativeDialogue, (text) => applyAliases(text, aliases).length)) {
94
+ const first = group.turns[0];
95
+ const speaker = voices.speakers.find((one) => one.id === first?.speaker);
96
+ if (first === undefined || speaker === undefined)
97
+ continue;
98
+ const made = groupParts(context, voices, group.native, group.turns, speaker, glossary, script, preparations);
99
+ if (made.length === 0)
100
+ pending = true;
101
+ made.forEach((part, index) => {
102
+ parts.push(part);
103
+ if (index === made.length - 1)
104
+ turnEnds.add(layout.length);
105
+ layout.push([speakerPace(speaker), index === made.length - 1 ? voices.turnGapSeconds : 0]);
106
+ });
107
+ }
108
+ // No gap after the last turn: the timeline's own gap follows it.
109
+ const last = layout.at(-1);
110
+ if (last !== undefined)
111
+ last[1] = 0;
112
+ if (pending) {
113
+ parts.length = 0;
114
+ layout.length = 0;
115
+ turnEnds.clear();
116
+ parts.push(future());
117
+ }
118
+ }
119
+ const body = recipe(context, "audio:body:concat", "audio", {
120
+ kind: "local",
121
+ version: 1,
122
+ operation: "concat-turns-v1",
123
+ values: [
124
+ parts.map((part) => resourceIdentity(context, part)),
125
+ layout,
126
+ // Pauses between sentences inside a turn; the turn gap stays the layout's.
127
+ ...pauseValues(context.config, layout.map(([, gap], at) => at === layout.length - 1 ? "end" : gap > 0 || turnEnds.has(at) ? "turn" : "sentence")),
128
+ ],
129
+ }, parts.map((part) => part.key), { unresolved: parsed?.ok !== true || pending });
130
+ return {
131
+ preparations,
132
+ cards,
133
+ parts,
134
+ body,
135
+ transcript,
136
+ ...(parsed?.ok === true ? { sections: parsed.script.sections } : {}),
137
+ };
138
+ }
139
+ function groupParts(context, voices, native, turns, speaker, glossary, script, preparations) {
140
+ const first = turns[0];
141
+ if (first === undefined)
142
+ return [];
143
+ const logicalKey = `audio:body:turn:${String(first.index)}`;
144
+ const override = context.content.narrationOverrides[logicalKey];
145
+ if (override?.kind === "asset")
146
+ return [
147
+ recipe(context, `${logicalKey}:1`, "audio", {
148
+ kind: "provided",
149
+ version: 1,
150
+ assetId: override.assetId,
151
+ semantic: [turns.map((turn) => turn.text).join("\n"), speaker.id],
152
+ }, script.dependsOn, { tokenKey: logicalKey }),
153
+ ];
154
+ if (native) {
155
+ const byId = new Map(voices.speakers.map((one) => [one.id, one]));
156
+ // Narration aliases reach every line of the one request, each turn on its own so an alias
157
+ // never joins the end of one turn to the start of the next. The clean turns stay the
158
+ // transcript, as they do for a voice that gets one request per turn.
159
+ const aliases = narrationAliasesOf(context.config);
160
+ const spoken = turns.map((turn) => applyAliases(turn.text, aliases));
161
+ const logicalText = turns.map((turn) => turn.text).join("\n");
162
+ const text = spoken.join("\n");
163
+ return [
164
+ recipe(context, `${logicalKey}:1`, "audio", {
165
+ kind: "tts",
166
+ version: 1,
167
+ provider: speaker.voice.provider,
168
+ model: speaker.voice.model,
169
+ voice: speaker.voice.voice,
170
+ text,
171
+ ...(text === logicalText ? {} : { spokenText: logicalText }),
172
+ logicalKey,
173
+ logicalText,
174
+ segment: "body",
175
+ pronunciation: null,
176
+ dialogue: turns.map((turn, index) => ({
177
+ speaker: turn.speaker,
178
+ turn: turn.index,
179
+ voice: byId.get(turn.speaker)?.voice.voice ?? "",
180
+ text: spoken[index] ?? turn.text,
181
+ })),
182
+ }, script.dependsOn, { tokenKey: logicalKey }),
183
+ ];
184
+ }
185
+ const logicalText = normalizeNarrationText(override?.kind === "text" ? override.text : first.text);
186
+ const entries = speakerGlossary(context, speaker, glossary);
187
+ if (!entries.ok)
188
+ return [refused(context, logicalKey, script.dependsOn, entries.reason)];
189
+ const maxCharacters = context.catalogue?.tts.find((row) => row.provider === speaker.voice.provider &&
190
+ row.id === speaker.voice.model &&
191
+ row.enabled &&
192
+ !row.deprecated)?.tts.maxCharacters ?? Math.max(2, logicalText.length * 4);
193
+ // Narration aliases apply to every voice, the speaker's pronunciations to IPA voices only.
194
+ const spans = withAliasSpans(pronunciationSpans(logicalText, entries.entries), aliasSpans(logicalText, narrationAliasesOf(context.config)));
195
+ if (preparesTurns(context.config, speaker)) {
196
+ const prepared = preparationForGroup(context, logicalKey, logicalText, "body", script.dependsOn, maxCharacters, spans, aliasMatches(logicalText, narrationAliasesOf(context.config)), { name: speaker.name.trim(), role: speaker.role });
197
+ preparations.push(prepared.preparation);
198
+ if (prepared.refusal !== null)
199
+ return [refused(context, logicalKey, [prepared.preparation.key], prepared.refusal)];
200
+ return (prepared.requests ?? []).map((request, index) => recipe(context, `${logicalKey}:${String(index + 1)}`, "audio", {
201
+ kind: "tts",
202
+ version: 1,
203
+ provider: speaker.voice.provider,
204
+ model: speaker.voice.model,
205
+ voice: speaker.voice.voice,
206
+ text: request.text,
207
+ spokenText: request.spokenText,
208
+ logicalKey,
209
+ logicalText,
210
+ segment: "body",
211
+ pronunciation: null,
212
+ speaker: speaker.id,
213
+ turn: first.index,
214
+ }, [prepared.preparation.key], { tokenKey: logicalKey, unresolved: logicalText.length === 0 }));
215
+ }
216
+ let requests;
217
+ if (spans.length > 0) {
218
+ const prepared = prepareRequests(logicalText, [], maxCharacters, spans);
219
+ if (!prepared.ok)
220
+ return [refused(context, logicalKey, script.dependsOn, prepared.reason)];
221
+ requests = prepared.requests;
222
+ }
223
+ else
224
+ requests = splitText(logicalText, maxCharacters).map((text) => ({ text }));
225
+ return requests.map((request, index) => recipe(context, `${logicalKey}:${String(index + 1)}`, "audio", {
226
+ kind: "tts",
227
+ version: 1,
228
+ provider: speaker.voice.provider,
229
+ model: speaker.voice.model,
230
+ voice: speaker.voice.voice,
231
+ text: request.text,
232
+ ...(request.spokenText === undefined ? {} : { spokenText: request.spokenText }),
233
+ logicalKey,
234
+ logicalText,
235
+ segment: "body",
236
+ pronunciation: null,
237
+ speaker: speaker.id,
238
+ turn: first.index,
239
+ }, script.dependsOn, { tokenKey: logicalKey, unresolved: logicalText.length === 0 }));
240
+ }
241
+ // IPA pronunciations reach only the voices that read IPA: the speaker's own first, then the
242
+ // run's glossary where Use Pronunciation Glossary is on.
243
+ function speakerGlossary(context, speaker, glossary) {
244
+ if (!readsIpa(speaker))
245
+ return { ok: true, entries: [] };
246
+ const own = speaker.pronunciations?.trim()
247
+ ? parsePronunciationGlossary(speaker.pronunciations, context.config.language)
248
+ : { ok: true, entries: [] };
249
+ if (!own.ok)
250
+ return {
251
+ ok: false,
252
+ reason: `${speaker.name.trim()}'s pronunciations can't be used: ${own.reason.replace("Edit the glossary or turn off Use Pronunciation Glossary.", "Fix them under Speakers (Play → Audio, or Edit project → Providers).")}`,
253
+ };
254
+ const run = context.config.audio?.usePronunciationGlossary === true && glossary?.ok === true
255
+ ? glossary.entries
256
+ : [];
257
+ return { ok: true, entries: withSharedGlossary(own.entries, run) };
258
+ }
259
+ // A turn is prepared when the run has a Narration Preparation prompt and its speaker's voice
260
+ // takes Inworld TTS-2's delivery tags; every other speaker's turns are spoken as written.
261
+ export function preparesTurns(config, speaker) {
262
+ return (usesNarrationPreparation(config) &&
263
+ speaker.voice.provider === "inworld" &&
264
+ speaker.voice.model === "inworld-tts-2");
265
+ }
266
+ export function readsIpa(speaker) {
267
+ return (speaker.voice.provider === "inworld" &&
268
+ (speaker.voice.model === "inworld-tts-2" || speaker.voice.model === "inworld-tts-2-flash"));
269
+ }
270
+ function refused(context, logicalKey, dependsOn, reason) {
271
+ return recipe(context, `${logicalKey}:1`, "audio", { kind: "deferred", version: 1, operation: "resolve-revision-recipe", template: reason }, dependsOn, { unresolved: true, refusal: reason });
272
+ }
273
+ // What a pending script will be spoken with, so a voice change before the script lands is
274
+ // still a change.
275
+ export function speakerValues(voices) {
276
+ return [
277
+ voices.nativeDialogue,
278
+ voices.speakers.map((speaker) => [
279
+ speaker.id,
280
+ speaker.voice.provider,
281
+ speaker.voice.model,
282
+ speaker.voice.voice,
283
+ speaker.pronunciations ?? null,
284
+ ]),
285
+ ];
286
+ }
@@ -1,11 +1,17 @@
1
1
  import { z } from "zod";
2
2
  import { estimateRequests } from "../estimate/index.js";
3
+ import { skippedGlossaryNotice, skippedSpeakerPronunciations, skippedSpeakerPronunciationsNotice, } from "../narration/pronunciation.js";
4
+ import { isLocalCliProvider } from "../settings/model.js";
3
5
  import { defaultShortsPrompt, shortsImageUpperBound } from "../shorts/model.js";
6
+ import { usesVoices } from "../voices/model.js";
4
7
  import { planDependencies } from "./dependencies.js";
5
8
  import { matchingNarrationPiece } from "./narration-reuse.js";
6
9
  import { buildRecipes } from "./recipe-build.js";
7
10
  import { manualCuesNeedReview } from "./recipe-exports.js";
8
11
  import { selectedReference, } from "./recipe-model.js";
12
+ import { recipeProviderChoice } from "./recipe-provider-choice.js";
13
+ import { narrationGlossary } from "./recipe-text.js";
14
+ import { readsIpa } from "./recipe-voices.js";
9
15
  export function planRevisionWork(revision, manifest, catalogue, availableAssetIds, resolved, retainedNarration = []) {
10
16
  const selected = {
11
17
  outputs: manifest.outputs.filter(selectedReference),
@@ -27,6 +33,7 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
27
33
  resolved: { ...resolved, articleMarkdown: usableArticle ? resolved.articleMarkdown : null },
28
34
  };
29
35
  const logical = buildRecipes(logicalContext);
36
+ const glossary = narrationGlossary(logicalContext);
30
37
  const context = { ...logicalContext, catalogue };
31
38
  const recipes = buildRecipes(context).map((row) => ({
32
39
  ...row,
@@ -64,7 +71,7 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
64
71
  null,
65
72
  after: row.fingerprint,
66
73
  }));
67
- const priced = work.flatMap((row) => priceRecipes(row, recipes.find((one) => one.key === row.key)));
74
+ const priced = work.flatMap((row) => priceRecipes(row, recipes.find((one) => one.key === row.key), revision.config));
68
75
  return {
69
76
  recipes,
70
77
  work,
@@ -74,6 +81,13 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
74
81
  (revision.config.chunking?.mode ?? "whole") === "whole"
75
82
  ? "Whole-text narration is one logical request. Editing its text rebuilds every provider part of that request."
76
83
  : null,
84
+ glossaryNotice: glossary?.ok === true && glossary.skipped !== undefined
85
+ ? skippedGlossaryNotice(glossary.skipped)
86
+ : null,
87
+ // Only speakers whose voice reads IPA use their pronunciations at all.
88
+ speakerPronunciationNotice: usesVoices(revision.config) && revision.config.voices !== undefined
89
+ ? skippedSpeakerPronunciationsNotice(skippedSpeakerPronunciations(revision.config.voices.speakers.filter(readsIpa), revision.config.language))
90
+ : null,
77
91
  costs: estimateRequests(priced, catalogue),
78
92
  };
79
93
  }
@@ -187,14 +201,16 @@ const shortsTranscriptEstimate = 24000;
187
201
  // What a step is charged as. Most steps are one request; what stands in for the shorts
188
202
  // until they are picked is every clip's image prompts and, at the longest length allowed,
189
203
  // every image, which is the most the step can cost.
190
- export function priceRecipes(work, value) {
204
+ export function priceRecipes(work, value,
205
+ // The revision's settings, which name the provider of a request built only when it runs.
206
+ config) {
191
207
  const input = value?.input;
192
208
  const unfolds = value?.unfoldsImages;
193
209
  // A short's image prompts once the clips are picked: the call, and the images it leads to,
194
210
  // as many as the clip's length asks for.
195
211
  if (work.disposition === "generate" && unfolds !== undefined)
196
212
  return [
197
- priceRecipe(work, value),
213
+ priceRecipe(work, value, config),
198
214
  ...Array.from({ length: unfolds.count }, () => ({
199
215
  kind: "image",
200
216
  stage: work.key,
@@ -223,7 +239,7 @@ export function priceRecipes(work, value) {
223
239
  input?.kind !== "deferred" ||
224
240
  input.operation !== "shorts" ||
225
241
  !Array.isArray(input.template))
226
- return [priceRecipe(work, value)];
242
+ return [priceRecipe(work, value, config)];
227
243
  const [, style, provider, model, imageSeconds, count, maxSeconds, llmProvider, llmModel] = input.template;
228
244
  const clips = typeof count === "number" ? count : 0;
229
245
  const images = typeof imageSeconds === "number" && typeof maxSeconds === "number"
@@ -249,7 +265,7 @@ export function priceRecipes(work, value) {
249
265
  })),
250
266
  ];
251
267
  }
252
- export function priceRecipe(work, value) {
268
+ export function priceRecipe(work, value, config) {
253
269
  if (value === undefined || work.disposition !== "generate")
254
270
  return {
255
271
  kind: "local",
@@ -259,12 +275,21 @@ export function priceRecipe(work, value) {
259
275
  : "Local, provided, or blocked work; no admitted generation charge.",
260
276
  };
261
277
  const input = value.input;
278
+ const onPlan = config === undefined ? undefined : cliRequest(work, value, config);
279
+ if (onPlan !== undefined)
280
+ return onPlan;
262
281
  if (input.kind === "deferred" && input.operation === "narration-preparation")
263
282
  return {
264
283
  kind: "unknown",
265
284
  stage: work.key,
266
285
  detail: "Narration Preparation: one LLM call per future logical chunk or entry; source length is not yet known.",
267
286
  };
287
+ if (input.kind === "deferred" && input.operation === "narration-description")
288
+ return {
289
+ kind: "unknown",
290
+ stage: work.key,
291
+ detail: "Narration descriptions: one LLM call per table, figure, equation or code block in the article; how many is known once the article is written.",
292
+ };
268
293
  if (input.kind === "llm")
269
294
  return {
270
295
  kind: "llm",
@@ -273,7 +298,7 @@ export function priceRecipe(work, value) {
273
298
  model: input.model,
274
299
  inputCharacters: input.messages.reduce((sum, message) => sum + message.content.length, 0) +
275
300
  (input.documents ?? []).reduce((sum, document) => sum + document.content.length, 0),
276
- outputCharacters: work.key === "article:body" ? 9000 : 2400,
301
+ outputCharacters: work.key === "article:body" ? 9000 : input.describe !== undefined ? 600 : 2400,
277
302
  detail: "Actual saved request input; output length is estimated. Retries and tools are excluded.",
278
303
  };
279
304
  if (input.kind === "tts")
@@ -299,6 +324,21 @@ export function priceRecipe(work, value) {
299
324
  detail: "The numbered transcript is built when the step runs; its length is estimated. One more call is made when the first answer breaks the rules. Retries are excluded.",
300
325
  };
301
326
  }
327
+ // A review: the stage's prompt and the item (the article's text, or a short brief beside an
328
+ // image, which the provider prices as it does).
329
+ if (input.kind === "local" && input.operation === "review-v1") {
330
+ const [stage, , , provider, model, , prompt] = Array.isArray(input.values) ? input.values : [];
331
+ return {
332
+ kind: "llm",
333
+ stage: work.key,
334
+ provider: typeof provider === "string" ? provider : "",
335
+ model: typeof model === "string" ? model : "",
336
+ inputCharacters: (typeof prompt === "string" && prompt !== "" ? prompt.length : 600) +
337
+ (stage === "article" || stage === "narration" ? 20000 : 1500),
338
+ outputCharacters: 400,
339
+ detail: "The item is read when the step runs; its length is estimated. A failed item made again is reviewed again, which is not in this estimate.",
340
+ };
341
+ }
302
342
  if (input.kind === "local" && input.operation === "youtube-description-v1") {
303
343
  const [, provider, model, , prompt] = Array.isArray(input.values) ? input.values : [];
304
344
  return {
@@ -320,3 +360,34 @@ export function priceRecipe(work, value) {
320
360
  detail: "Future generated input is unknown. This group materializes under the same admitted revision; its final request count and charge are unknown.",
321
361
  };
322
362
  }
363
+ // A request built only when its step runs, on a command-line provider: it runs on the user's
364
+ // plan, so it is known at $0, as Play's review prices the same step. Its size is estimated
365
+ // for the API figure beside it; Narration Preparation's number of calls is not known yet, so
366
+ // it has none.
367
+ function cliRequest(work, value, config) {
368
+ if (value.input.kind !== "deferred")
369
+ return undefined;
370
+ const choice = recipeProviderChoice(value, config);
371
+ if (choice === undefined || !isLocalCliProvider(choice.provider))
372
+ return undefined;
373
+ if (choice.family === "image")
374
+ return { kind: "image", stage: work.key, provider: choice.provider, model: choice.model };
375
+ if (choice.family !== "llm")
376
+ return undefined;
377
+ const preparation = value.input.operation === "narration-preparation" ||
378
+ value.input.operation === "narration-description";
379
+ return {
380
+ kind: "llm",
381
+ stage: work.key,
382
+ provider: choice.provider,
383
+ model: choice.model,
384
+ inputCharacters: 6000,
385
+ outputCharacters: work.key === "article:body" ? 9000 : 2400,
386
+ ...(preparation ? { apiUnknown: true } : {}),
387
+ detail: preparation
388
+ ? value.input.operation === "narration-description"
389
+ ? "Runs on your CLI plan, so it adds no charge. Narration descriptions make one call per table, figure, equation or code block; how many is known once the article is written, so no API figure is given."
390
+ : "Runs on your CLI plan, so it adds no charge. Narration Preparation makes one call per logical chunk or entry; how many is known when the step runs, so no API figure is given."
391
+ : "Runs on your CLI plan, so it adds no charge. The request is built when the step runs, so its API figure uses an estimated length.",
392
+ };
393
+ }
@@ -25,6 +25,8 @@ export function youtubeRecipes(context, exports) {
25
25
  // ships as a new operation version rather than a silent change here.
26
26
  config.descriptionPrompt?.trim() ? renderedPrompt(context, "description") : "",
27
27
  config.title,
28
+ // The project language, only when it is not English, so English keeps its fingerprint.
29
+ ...(config.language === undefined || config.language === "en" ? [] : [config.language]),
28
30
  ],
29
31
  }, [timing.key],
30
32
  // A paid LLM call: readiness, the charge warning and the estimate treat it like one.
@@ -1,5 +1,9 @@
1
1
  import { projectPaused } from "../admission/repo.js";
2
- export function activeConflict(deps, projectId, keys) {
2
+ //
3
+ // An automatic redo (`pendingSuperseded`) runs while the rest of the run waits: admitted work
4
+ // that has not started is replaced by the redo's own, so only running work and accepted jobs
5
+ // stand in its way.
6
+ export function activeConflict(deps, projectId, keys, pendingSuperseded = false) {
3
7
  const rows = deps.db
4
8
  .prepare("SELECT work_key,logical_key,live FROM (SELECT p.work_key,r.logical_key," +
5
9
  "(w.state='running' OR (? AND w.state='pending' AND w.dispatch_state='allowed' AND NOT EXISTS(" +
@@ -10,7 +14,7 @@ export function activeConflict(deps, projectId, keys) {
10
14
  "FROM revision_work w JOIN revision_work_pieces p ON p.work_id=w.id " +
11
15
  "LEFT JOIN revision_work_reservations r ON r.work_id=w.id AND r.piece_id=p.id " +
12
16
  "WHERE w.project_id=?) WHERE live OR accepted")
13
- .all(projectPaused(deps.db, projectId) ? 0 : 1, projectId)
17
+ .all(pendingSuperseded || projectPaused(deps.db, projectId) ? 0 : 1, projectId)
14
18
  .filter((row) => keys.some((key) => row.work_key === key ||
15
19
  row.logical_key === key ||
16
20
  (key.endsWith(":future") && String(row.work_key).startsWith(key.slice(0, -6)))));
@@ -18,7 +22,7 @@ export function activeConflict(deps, projectId, keys) {
18
22
  return undefined;
19
23
  return rows.some((row) => Number(row.live) === 1) ? "running" : "accepted-job";
20
24
  }
21
- export const acceptedJobMessage = "An accepted provider job for this section is waiting to be collected. Use Retry stage or Resume, then rerun.";
25
+ export const acceptedJobMessage = "An accepted provider job for this section is waiting to be collected. Use Try again or Continue the run, then rerun.";
22
26
  export function conflictRefusal(conflict) {
23
27
  return conflict === "running"
24
28
  ? { ok: false, reason: "running" }
@@ -6,6 +6,9 @@ export const recoveryActionSchema = z.discriminatedUnion("kind", [
6
6
  z.object({ kind: z.literal("resume") }).strict(),
7
7
  z.object({ kind: z.literal("retry"), stage: z.enum(stageKinds) }).strict(),
8
8
  z.object({ kind: z.literal("rerun"), stage: z.enum(stageKinds) }).strict(),
9
+ // Make one reviewed item again (`slices/reviews`): an image, the thumbnail, the article, the
10
+ // narration or one short, and everything that depends on it, its review included.
11
+ z.object({ kind: z.literal("redo"), item: z.string().min(1).max(200) }).strict(),
9
12
  ]);
10
13
  export const recoveryRequestSchema = revisionControlSchema
11
14
  .extend({
@@ -9,19 +9,45 @@ export function sectionRoots(view, plan, stage) {
9
9
  return false;
10
10
  if (stage === "video")
11
11
  return (row.input.kind === "local" &&
12
- (row.key.startsWith("export:") || row.key === "subtitles:files"));
12
+ (row.key.startsWith("export:") ||
13
+ row.key === "subtitles:files" ||
14
+ row.key === "voices:files"));
13
15
  if (stage === "audio")
14
16
  return (row.input.kind === "tts" ||
15
17
  (row.input.kind === "deferred" &&
16
18
  ["body-narration", "intro-narration", "outro-narration"].includes(row.input.operation)));
17
19
  if (stage === "article")
18
- return row.key === "article:body" && row.kind === "provider";
20
+ return ((row.key === "article:body" || row.key === "script:attribute") && row.kind === "provider");
19
21
  if (stage === "document")
20
22
  return row.key === "document:pdf";
21
23
  return row.kind === "provider";
22
24
  })
23
25
  .map((row) => row.key);
24
26
  }
27
+ // What making one reviewed item again regenerates and where its rebuild starts. The article
28
+ // and the narration are redone like More → make it again in its section on their stage; an image or the thumbnail
29
+ // by its own token, like Regenerate; a short by "Make this short again"'s token.
30
+ export function redoTarget(view, plan, item) {
31
+ const section = (stage) => {
32
+ const edit = regenerationEdit(view, plan, stage);
33
+ return edit === undefined ? undefined : { roots: sectionRoots(view, plan, stage), edit };
34
+ };
35
+ if (item === "narration")
36
+ return section("audio");
37
+ if (item === "article:body")
38
+ return section("article");
39
+ const own = (roots) => ({
40
+ roots,
41
+ edit: { config: view.revision.config, content: view.revision.content, regenerate: [item] },
42
+ });
43
+ if (/^shorts:\d+$/.test(item))
44
+ return plan.recipes.some((row) => row.key === `${item}:prompts`)
45
+ ? own([`${item}:prompts`])
46
+ : undefined;
47
+ return plan.recipes.some((row) => row.key === item && row.kind === "provider")
48
+ ? own([item])
49
+ : undefined;
50
+ }
25
51
  export function dependentClosure(recipes, roots) {
26
52
  const selected = new Set(roots);
27
53
  let changed = true;