@gentbajko/slopify 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -9,7 +9,7 @@ export function joinedNarration(parts) {
9
9
  export function narrationTextParts(view, plan, segment) {
10
10
  const concat = plan.recipes.find((row) => row.key === (segment === "body" ? "audio:body:concat" : `audio:${segment}`));
11
11
  if (concat === undefined)
12
- throw new Error(`Slopify hit an internal error (the ${segment} narration is missing from the project plan). Retry stage; if it happens again, use Download diagnostics in Settings and report it.`);
12
+ throw new Error(`Slopify hit an internal error (the ${segment} narration is missing from the project plan). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
13
13
  return concat.dependsOn.map((key) => {
14
14
  const recipe = plan.recipes.find((row) => row.key === key);
15
15
  const selected = view.pieces.find((row) => row.key === key &&
@@ -18,7 +18,7 @@ export function narrationTextParts(view, plan, segment) {
18
18
  row.piece.state === "done" &&
19
19
  row.fingerprint === recipe?.fingerprint);
20
20
  if (selected === undefined || recipe === undefined)
21
- throw new Error("Some narration chunks aren't finished, so the full narration text isn't available. Let the Narration stage finish (Resume, or Retry stage on it), or regenerate the missing chunk in Edit project → Narration.");
21
+ throw new Error("Some narration chunks aren't finished, so the full narration text isn't available. Let the Narration stage finish (Resume, or Try again on it), or regenerate the missing chunk in Edit project → Narration.");
22
22
  const input = recipe.input;
23
23
  if (input.kind === "provided" &&
24
24
  Array.isArray(input.semantic) &&
@@ -29,13 +29,13 @@ export function narrationTextParts(view, plan, segment) {
29
29
  requestText: null,
30
30
  };
31
31
  if (input.kind !== "tts")
32
- throw new Error("Slopify hit an internal error (a narration chunk's text was never worked out). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
32
+ throw new Error("Slopify hit an internal error (a narration chunk's text was never worked out). Try again; if it happens again, use Download diagnostics in Settings and report it.");
33
33
  const payload = z
34
34
  .object({ text: z.string().optional(), spokenText: z.string().optional() })
35
35
  .parse(JSON.parse(selected.piece.payload ?? "{}"));
36
36
  if (input.spokenText !== undefined &&
37
37
  (payload.spokenText !== input.spokenText || payload.text !== input.text))
38
- throw new Error("A narration chunk's audio no longer matches its prepared text. Regenerate that chunk in Edit project → Narration, then Retry stage.");
38
+ throw new Error("A narration chunk's audio no longer matches its prepared text. Regenerate that chunk in Edit project → Narration, then Try again.");
39
39
  return {
40
40
  logicalKey: input.logicalKey,
41
41
  spokenText: input.spokenText === undefined ? (payload.text ?? input.text) : (payload.spokenText ?? ""),
@@ -45,7 +45,7 @@ export function narrationTextParts(view, plan, segment) {
45
45
  }
46
46
  export async function publishNarrationText(deps, context, piece) {
47
47
  if (piece.input.kind !== "local")
48
- throw new Error("Slopify hit an internal error (the narration text file step was set up wrongly). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
48
+ throw new Error("Slopify hit an internal error (the narration text file step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
49
49
  const { segment } = z
50
50
  .object({ segment: z.enum(["body", "intro", "outro"]) })
51
51
  .parse(piece.input.values);
@@ -54,10 +54,10 @@ export async function publishNarrationText(deps, context, piece) {
54
54
  .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
55
55
  .get(context.work.workId);
56
56
  if (view === undefined)
57
- throw new Error("Slopify hit an internal error (the project version being narrated is missing). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
57
+ throw new Error("Slopify hit an internal error (the project version being narrated is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
58
58
  const plan = executionPlan(deps, view, savedCatalogue(row?.recipe_context));
59
59
  if (!plan.recipes.some((recipe) => recipe.key === piece.key && recipe.fingerprint === piece.fingerprint))
60
- throw new Error("Slopify hit an internal error (the narration text no longer matches the project's saved inputs). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
60
+ throw new Error("Slopify hit an internal error (the narration text no longer matches the project's saved inputs). Try again; if it happens again, use Download diagnostics in Settings and report it.");
61
61
  const parts = narrationTextParts(view, plan, segment);
62
62
  const outputs = preparedTexts(deps, context, piece, [
63
63
  ["narration_txt", `${segment}-narration.txt`, joinedNarration(parts)],
@@ -0,0 +1,28 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename } from "node:path";
3
+ import { outputPath } from "../storage/layout.js";
4
+ // The file behind a picture the article names (`![alt](maps/relief.png)`): an image uploaded
5
+ // to the project under that file name. An article's pictures are otherwise web addresses
6
+ // Slopify never fetches, so a figure without its upload is described and drawn from its
7
+ // caption alone.
8
+ export function articlePicture(deps, view, named) {
9
+ if (named === undefined)
10
+ return undefined;
11
+ const name = basename((named.split(/[?#]/)[0] ?? "").replace(/\\/g, "/")).toLowerCase();
12
+ if (name === "")
13
+ return undefined;
14
+ for (const image of Object.values(view.revision.content.imageDefinitions)) {
15
+ if (image.source !== "provide" || image.assetId === null)
16
+ continue;
17
+ const row = deps.db
18
+ .prepare("SELECT path FROM project_assets WHERE id=? AND project_id=?")
19
+ .get(image.assetId, view.revision.projectId);
20
+ const path = typeof row?.path === "string" ? row.path : undefined;
21
+ if (path === undefined || basename(path).toLowerCase() !== name)
22
+ continue;
23
+ const file = outputPath(deps.paths, view.revision.projectId, path);
24
+ if (existsSync(file))
25
+ return file;
26
+ }
27
+ return undefined;
28
+ }
@@ -4,14 +4,43 @@ import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js"
4
4
  // Undefined for steps that name their own place (captions, export) or have no single one.
5
5
  export function pieceLabel(deps, context, piece) {
6
6
  const { key } = piece;
7
+ const variant = /^thumbnail:image:([23])$/.exec(key);
8
+ if (variant !== null)
9
+ return `Thumbnail ${variant[1] ?? ""}`;
7
10
  if (key === "thumbnail:image" || key.startsWith("thumbnail:"))
8
11
  return "Thumbnail";
12
+ if (key === "reference:image")
13
+ return "Establishing image";
9
14
  if (key === "article:body")
10
15
  return "Article";
11
16
  if (key === "document:pdf")
12
17
  return "Document";
13
18
  if (key === "youtube:description")
14
19
  return "YouTube description";
20
+ if (key.startsWith("review:")) {
21
+ const item = key.slice("review:".length);
22
+ const itemLabel = item === "narration"
23
+ ? "Narration"
24
+ : /^shorts:\d+$/.test(item)
25
+ ? `Short ${item.slice("shorts:".length)}`
26
+ : pieceLabel(deps, context, { key: item });
27
+ return `Review of ${itemLabel ?? "an item"}`;
28
+ }
29
+ if (key === "voices:files")
30
+ return "MP3 and M4B files";
31
+ if (key === "script:attribute")
32
+ return "Speaker split";
33
+ if (key === "narration:describe:future")
34
+ return "Narration descriptions";
35
+ const card = /^figure:card:(\d+)$/.exec(key);
36
+ if (card !== null)
37
+ return `On-screen card ${card[1] ?? ""}`;
38
+ const described = /^narration:describe:(\d+)$/.exec(key);
39
+ if (described !== null)
40
+ return `Narration description ${described[1] ?? ""}`;
41
+ const turn = /^audio:body:turn:(\d+):\d+$/.exec(key);
42
+ if (turn !== null)
43
+ return `Turn ${turn[1] ?? ""}`;
15
44
  if (key === "shorts:pick" || key === "shorts:future")
16
45
  return "Shorts";
17
46
  if (key === "animate:future")
@@ -1,17 +1,26 @@
1
+ import { basename } from "node:path";
2
+ import { referenceKey, thumbnailVariant } from "../admission/model.js";
1
3
  import { plainText } from "../article/plain.js";
2
4
  import { splitEndMatter } from "../article/split.js";
5
+ import { spokenPassage } from "../narration/describe.js";
3
6
  import { observeNarration } from "../narration/live.js";
4
7
  import { validatePreparation } from "../narration/preparation.js";
5
8
  import { prepareRequests } from "../narration/steering.js";
6
9
  import { chaptersFrom } from "../research/planner.js";
7
10
  import { sourcedAnswer } from "../research/synthesis.js";
11
+ import { imageReviewers } from "../reviews/model.js";
8
12
  import { discardPreparedAssets, writeAsset } from "../storage/assets.js";
9
13
  import { outputPath } from "../storage/layout.js";
10
14
  import { probeDurationMs } from "../video/ffmpeg.js";
15
+ import { parseAttribution } from "../voices/attribution.js";
16
+ import { parseScript } from "../voices/script.js";
11
17
  import { executeArticleRequests } from "./runtime-article.js";
18
+ import { imageCall } from "./runtime-image.js";
12
19
  import { frozenInstructions } from "./runtime-instructions.js";
20
+ import { articlePicture } from "./runtime-pictures.js";
13
21
  import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
14
22
  import { preparedResult, preparedTexts, publishResult } from "./runtime-publication.js";
23
+ import { clearSoftening, softenedPrompt, softeningRequested, softenMessages } from "./soften.js";
15
24
  export async function executeProviderRecipe(deps, context, providers, piece) {
16
25
  if (deps.db
17
26
  .prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
@@ -20,6 +29,8 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
20
29
  const input = piece.input;
21
30
  const wrapped = providers.forPiece(piece.id);
22
31
  if (input.kind === "llm") {
32
+ // A described figure's own picture, when the project has it and the model can look.
33
+ const picture = describedPicture(deps, context, input);
23
34
  const answer = piece.key === "article:body"
24
35
  ? await executeArticleRequests(deps, context, providers, piece)
25
36
  : await wrapped.llm({
@@ -27,7 +38,8 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
27
38
  model: input.model,
28
39
  ...(input.thinking === null ? {} : { thinking: input.thinking }),
29
40
  thinkingConfig: input.thinkingConfig,
30
- messages: input.messages,
41
+ messages: picture === undefined ? input.messages : withPicture(input.messages),
42
+ ...(picture === undefined ? {} : { images: [picture] }),
31
43
  documents: input.documents,
32
44
  webSearch: input.webSearch,
33
45
  previewLabel: piece.key,
@@ -52,7 +64,17 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
52
64
  return "done";
53
65
  }
54
66
  if (input.kind === "tts") {
55
- const spoken = await wrapped.tts({ provider: input.provider, model: input.model, voiceId: input.voice, text: input.text }, observeNarration(deps.audioPreviews, context.work.projectId, piece.id, narrationLabel(deps, context, piece), { revisionId: context.work.revisionId, workId: context.work.workId, workPieceId: piece.id }));
67
+ const spoken = await wrapped.tts({
68
+ provider: input.provider,
69
+ model: input.model,
70
+ voiceId: input.voice,
71
+ text: input.text,
72
+ ...(input.dialogue === undefined
73
+ ? {}
74
+ : {
75
+ dialogue: input.dialogue.map((line) => ({ voiceId: line.voice, text: line.text })),
76
+ }),
77
+ }, observeNarration(deps.audioPreviews, context.work.projectId, piece.id, narrationLabel(deps, context, piece), { revisionId: context.work.revisionId, workId: context.work.workId, workPieceId: piece.id }));
56
78
  if (!spoken.ok)
57
79
  return "held";
58
80
  const asset = writeAsset(deps, context.work.projectId, "narration.mp3", spoken.value.bytes);
@@ -75,6 +97,15 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
75
97
  voice: input.voice,
76
98
  ...(durationMs === undefined ? {} : { durationMs }),
77
99
  }, asset);
100
+ context.emit({
101
+ type: "narration.piece",
102
+ projectId: context.work.projectId,
103
+ key: piece.key,
104
+ durationMs: durationMs ?? null,
105
+ revisionId: context.work.revisionId,
106
+ workId: context.work.workId,
107
+ workPieceId: piece.id,
108
+ });
78
109
  deps.count?.("stage.completed", {
79
110
  stage: "audio",
80
111
  segment: input.segment,
@@ -89,47 +120,95 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
89
120
  return "done";
90
121
  }
91
122
  if (input.kind === "image") {
92
- const image = await wrapped.image({
93
- provider: input.provider,
94
- model: input.model,
95
- prompt: input.prompt,
96
- aspect: input.aspect,
97
- });
98
- if (!image.ok)
99
- return "held";
100
- const asset = writeAsset(deps, context.work.projectId, image.value.mime === "image/jpeg" ? "image.jpg" : "image.png", image.value.bytes);
101
123
  const view = executionView(deps, context.work.projectId, context.work.revisionId);
102
124
  const index = piece.key.startsWith("image:")
103
125
  ? (view?.revision.content.imageOrder.indexOf(piece.key.slice(6)) ?? 0) + 1
104
126
  : undefined;
105
- const output = preparedResult(deps, context, piece, piece.key === "thumbnail:image" ? "thumbnail" : "image", asset, null, {
106
- prompt: input.prompt,
127
+ // Which thumbnail this is (1-3), or undefined for any other image.
128
+ const variant = thumbnailVariant(piece.key);
129
+ const label = variant !== undefined
130
+ ? variant === 1
131
+ ? "Thumbnail"
132
+ : `Thumbnail ${String(variant)}`
133
+ : piece.key === referenceKey
134
+ ? "Establishing image"
135
+ : index === undefined
136
+ ? "Image"
137
+ : `Image ${String(index)}`;
138
+ const softened = await softenIfAsked(deps, context, wrapped, piece, input.prompt, label);
139
+ if (softened === "held")
140
+ return "held";
141
+ const prompt = softened ?? input.prompt;
142
+ const image = await wrapped.image(imageCall(deps, context.work.projectId, { ...input, prompt }, label));
143
+ if (!image.ok)
144
+ return "held";
145
+ const asset = writeAsset(deps, context.work.projectId, image.value.mime === "image/jpeg" ? "image.jpg" : "image.png", image.value.bytes);
146
+ const output = preparedResult(deps, context, piece, variant !== undefined ? "thumbnail" : piece.key === referenceKey ? "reference" : "image", asset, null, {
147
+ prompt,
107
148
  provider: input.provider,
108
149
  model: input.model,
109
150
  ...(index === undefined ? {} : { index }),
151
+ // The second and third thumbnails carry their number; the first keeps the meta a
152
+ // thumbnail always had.
153
+ ...(variant === undefined || variant === 1 ? {} : { index: variant }),
110
154
  });
111
- await publishResult(deps, context, piece, [output], { prompt: input.prompt }, asset);
155
+ await publishResult(deps, context, piece, [output], { prompt }, asset);
156
+ if (softened !== undefined)
157
+ clearSoftening(deps.db, context.work.projectId, [piece.key]);
112
158
  deps.count?.("stage.completed", {
113
159
  stage: context.work.kind,
114
160
  provider: input.provider,
115
161
  model: input.model,
116
- ...(piece.key === "thumbnail:image" ? { thumbnails: 1 } : { images: 1 }),
162
+ ...(variant !== undefined ? { thumbnails: 1 } : { images: 1 }),
117
163
  });
118
164
  return "done";
119
165
  }
120
- throw new Error("Slopify hit an internal error (this step has no provider request to run). Use Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
166
+ throw new Error("Slopify hit an internal error (this step has no provider request to run). Use Try again; if it happens again, use Download diagnostics in Settings and report it.");
167
+ }
168
+ // Soften and retry (`soften.ts`): the project's AI model rewords a refused image prompt
169
+ // before the image is drawn again. Undefined when nobody asked, or the project has no model.
170
+ async function softenIfAsked(deps, context, providers, piece, prompt, label) {
171
+ if (!softeningRequested(deps.db, context.work.projectId, piece.key))
172
+ return undefined;
173
+ const llm = executionView(deps, context.work.projectId, context.work.revisionId)?.revision.config
174
+ .llm;
175
+ if (llm === undefined)
176
+ return undefined;
177
+ const answer = await providers.llm({
178
+ provider: llm.provider,
179
+ model: llm.model,
180
+ ...(llm.thinking === undefined ? {} : { thinking: llm.thinking }),
181
+ messages: softenMessages(prompt),
182
+ previewLabel: `${label}: softened prompt`,
183
+ check: (value) => softenedPrompt(value.text) === ""
184
+ ? "The AI model sent back an empty prompt while softening it. Use Soften and retry again, or reword the prompt yourself in Edit project → Images."
185
+ : undefined,
186
+ });
187
+ return answer.ok ? softenedPrompt(answer.value.text) : "held";
121
188
  }
122
189
  function checkAnswer(piece, answer) {
123
190
  if (answer.text.trim() === "")
124
- return "The AI model sent back an empty answer. Use Retry stage; if it keeps happening, choose another model in the Providers section of Edit project.";
191
+ return "The AI model sent back an empty answer. Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.";
192
+ if (piece.input.kind === "llm" && piece.input.describe !== undefined)
193
+ return spokenPassage(answer.text) === ""
194
+ ? "The AI model's description had no words a narrator could say. Use Try again; if it keeps happening, choose another model in the Providers section of Edit project, or turn off Describe tables and figures there."
195
+ : undefined;
125
196
  if (piece.key === "research:planner")
126
197
  return chaptersFrom(answer.text).length === 0
127
- ? "The AI model's research plan listed no chapters, so research could not go on. Use Retry stage; if it keeps happening, choose another model in the Providers section of Edit project."
198
+ ? "The AI model's research plan listed no chapters, so research could not go on. Use Try again; if it keeps happening, choose another model in the Providers section of Edit project."
128
199
  : undefined;
129
200
  if (piece.key === "research:notes" || piece.key.startsWith("research:chapter:"))
130
201
  return sourcedAnswer(piece.key, answer.text);
202
+ // A speaker split that loses or adds words is asked for again rather than narrated.
203
+ if (piece.input.kind === "llm" && piece.input.script?.attribute === true) {
204
+ const checked = parseAttribution(answer.text, attributionSource(piece.input.messages), piece.input.script.speakers);
205
+ return checked.ok ? undefined : checked.reason;
206
+ }
131
207
  return undefined;
132
208
  }
209
+ function attributionSource(messages) {
210
+ return messages.at(-1)?.content ?? "";
211
+ }
133
212
  async function publishText(deps, context, piece, answer) {
134
213
  if (piece.input.kind === "llm" && piece.input.preparation !== undefined) {
135
214
  const checked = validatePreparation(answer.text, piece.input.preparation.source);
@@ -143,8 +222,20 @@ async function publishText(deps, context, piece, answer) {
143
222
  });
144
223
  return;
145
224
  }
225
+ if (piece.input.kind === "llm" && piece.input.describe !== undefined) {
226
+ // The passage the narration says, as it says it; nothing to download.
227
+ await publishResult(deps, context, piece, [], { text: spokenPassage(answer.text) });
228
+ return;
229
+ }
146
230
  if (piece.key === "article:body") {
147
231
  const parts = splitEndMatter(answer.text);
232
+ // A script is read before it is kept: one the narration could not speak fails here, with
233
+ // the line to fix, rather than at the narration.
234
+ if (piece.input.kind === "llm" && piece.input.script !== undefined) {
235
+ const checked = parseScript(parts.body, piece.input.script.speakers);
236
+ if (!checked.ok)
237
+ throw new Error(`The text model's script can't be read: ${checked.reason} Try again to have it written again, or fix it in Edit project → Article.`);
238
+ }
148
239
  await publishResult(deps, context, piece, preparedTexts(deps, context, piece, [
149
240
  ["instructions", "instructions.md", frozenInstructions(deps, context, piece)],
150
241
  ["article_md", "article.md", answer.text],
@@ -154,6 +245,10 @@ async function publishText(deps, context, piece, answer) {
154
245
  ]), { text: answer.text });
155
246
  return;
156
247
  }
248
+ if (piece.key === "script:attribute") {
249
+ await publishResult(deps, context, piece, preparedTexts(deps, context, piece, [["script_md", "script.md", answer.text]]), { text: answer.text });
250
+ return;
251
+ }
157
252
  if (piece.key === "research:notes") {
158
253
  await publishResult(deps, context, piece, preparedTexts(deps, context, piece, [
159
254
  ["notes", "notes.md", answer.text],
@@ -177,7 +272,7 @@ async function publishText(deps, context, piece, answer) {
177
272
  : [];
178
273
  const title = titles[Number(piece.key.split(":").at(-1)) - 1];
179
274
  if (typeof title !== "string")
180
- throw new Error("Slopify hit an internal error (a research chapter has no title in the research plan). Use Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
275
+ throw new Error("Slopify hit an internal error (a research chapter has no title in the research plan). Use Try again; if it happens again, use Download diagnostics in Settings and report it.");
181
276
  const asset = writeAsset(deps, context.work.projectId, `research-${piece.key.split(":").at(-1)}.md`, Buffer.from(answer.text));
182
277
  await publishResult(deps, context, piece, [], { title, notes: answer.text }, asset);
183
278
  return;
@@ -215,3 +310,23 @@ function checkPreparation(deps, context, piece, answer) {
215
310
  const prepared = prepareRequests(source, checked.cues, model?.tts.maxCharacters ?? 4000);
216
311
  return prepared.ok ? undefined : prepared.reason;
217
312
  }
313
+ // A described figure's own picture (`runtime-pictures.ts`), sent only to a text provider that
314
+ // looks at pictures (`reviews/model.ts`). Otherwise the figure is described from its caption
315
+ // and alt text alone.
316
+ function describedPicture(deps, context, input) {
317
+ const named = input.describe?.image;
318
+ if (named === undefined || !imageReviewers.includes(input.provider))
319
+ return undefined;
320
+ const view = executionView(deps, context.work.projectId, context.work.revisionId);
321
+ const path = view === undefined ? undefined : articlePicture(deps, view, named);
322
+ return path === undefined ? undefined : { path, name: basename(path) };
323
+ }
324
+ function withPicture(messages) {
325
+ const last = messages.findLastIndex((message) => message.role === "user");
326
+ return messages.map((message, index) => index === last
327
+ ? {
328
+ ...message,
329
+ content: `${message.content}\n\nThe picture itself is attached: look at it before you answer.`,
330
+ }
331
+ : message);
332
+ }
@@ -12,7 +12,11 @@ export function preparedResult(deps, context, piece, role, asset, durationMs = n
12
12
  slot: role === "image" ||
13
13
  role === "short_image" ||
14
14
  role === "short_video" ||
15
- role === "animated_image"
15
+ role === "animated_image" ||
16
+ // Level the volume: one levelled narration per segment.
17
+ role === "audio_levelled" ||
18
+ // The second and third thumbnails; the first keeps the slot it always had.
19
+ (role === "thumbnail" && piece.key !== "thumbnail:image")
16
20
  ? piece.key
17
21
  : `${context.work.kind}:${role}`,
18
22
  workKey: piece.key,
@@ -35,7 +39,7 @@ export function preparedResult(deps, context, piece, role, asset, durationMs = n
35
39
  export function preparedText(deps, context, piece, role, filename, text) {
36
40
  const output = preparedTexts(deps, context, piece, [[role, filename, text]])[0];
37
41
  if (output === undefined)
38
- throw new Error("Slopify hit an internal error (a text file could not be prepared). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
42
+ throw new Error("Slopify hit an internal error (a text file could not be prepared). Try again; if it happens again, use Download diagnostics in Settings and report it.");
39
43
  return output;
40
44
  }
41
45
  export function preparedTexts(deps, context, piece, texts) {
@@ -93,13 +97,17 @@ export async function publishResult(deps, context, piece, outputs, payload, asse
93
97
  function pieceKind(piece) {
94
98
  if (piece.input.kind === "llm" && piece.input.preparation !== undefined)
95
99
  return "prompt_written";
100
+ if (piece.input.kind === "llm" && piece.input.describe !== undefined)
101
+ return "prompt_written";
96
102
  if (piece.input.kind === "tts")
97
103
  return piece.input.segment === "body" ? "chunk" : "segment";
98
104
  if (piece.input.kind === "provided" && /^audio:body:.+:\d+$/.test(piece.key))
99
105
  return "chunk";
100
106
  if (piece.input.kind === "provided" && /^audio:(intro|outro):\d+$/.test(piece.key))
101
107
  return "segment";
102
- if (piece.key.startsWith("image:") || piece.key.startsWith("animate:"))
108
+ if (piece.key.startsWith("image:") ||
109
+ piece.key.startsWith("animate:") ||
110
+ piece.key.startsWith("figure:card:"))
103
111
  return "image";
104
112
  if (piece.key.startsWith("research:chapter:"))
105
113
  return "chapter";
@@ -123,6 +131,13 @@ function pieceIndex(deps, context, piece) {
123
131
  return 1;
124
132
  if (piece.key === "entry:outro:text")
125
133
  return 2;
134
+ // The cards take places of their own, past the animated images'.
135
+ const card = /^figure:card:(\d+)$/.exec(piece.key);
136
+ if (card !== null)
137
+ return 800000 + Number(card[1]);
138
+ // The narration's descriptions take places of their own, past the preparation steps'.
139
+ if (piece.input.kind === "llm" && piece.input.describe !== undefined)
140
+ return 500000 + piece.input.describe.index;
126
141
  // The shorts' steps take places of their own, past every other step of the Video stage:
127
142
  // the pick, then per short its prompts, its images and its render.
128
143
  const short = /^shorts:(?:(\d+):(prompts|image:(\d+)|render)|pick)$/.exec(piece.key);
@@ -152,6 +167,7 @@ function pieceIndex(deps, context, piece) {
152
167
  "narration:files:body",
153
168
  "narration:files:intro",
154
169
  "narration:files:outro",
170
+ "voices:files",
155
171
  ];
156
172
  const localIndex = localOrder.indexOf(piece.key);
157
173
  if (localIndex >= 0)
@@ -190,10 +206,10 @@ export function retainPartialArticle(deps, context, piece, text) {
190
206
  if (authority === undefined)
191
207
  return;
192
208
  if (authority.key !== piece.key || authority.fingerprint !== piece.fingerprint)
193
- throw new Error("Slopify hit an internal error (a partly written article doesn't match its saved step). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
209
+ throw new Error("Slopify hit an internal error (a partly written article doesn't match its saved step). Try again; if it happens again, use Download diagnostics in Settings and report it.");
194
210
  const revision = revisionById(deps.db, context.work.projectId, context.work.revisionId);
195
211
  if (revision === undefined)
196
- throw new Error("Slopify hit an internal error (the project version for a partly written article is missing). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
212
+ throw new Error("Slopify hit an internal error (the project version for a partly written article is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
197
213
  insertAsset(deps.db, result.asset);
198
214
  insertManifestOutput(deps.db, revision, {
199
215
  slot: `article:partial:${piece.id}`,