@gentbajko/slopify 2.5.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -1,9 +1,12 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
+ import { versionsOrCurrent } from "../../slices/library/history.js";
4
5
  import { promptKinds } from "../../slices/library/model.js";
5
- import { listPrompts } from "../../slices/library/repo.js";
6
- import { createPrompt, removePrompt, updatePrompt } from "../../slices/library/save.js";
6
+ import { setPromptPhotorealistic, withPhotorealistic, } from "../../slices/library/photorealistic.js";
7
+ import { listPrompts, promptById } from "../../slices/library/repo.js";
8
+ import { createPrompt, removePrompt, restorePrompt, updatePrompt, } from "../../slices/library/save.js";
9
+ import { usedBy } from "../../slices/library/used-by.js";
7
10
  import { onInvalid, problem, titleOf } from "./problem.js";
8
11
  const idParam = z.object({
9
12
  id: z
@@ -12,6 +15,8 @@ const idParam = z.object({
12
15
  .max(64)
13
16
  .regex(/^[0-9A-Za-z_-]+$/),
14
17
  });
18
+ // History's Restore names the version by its number.
19
+ export const versionParam = idParam.extend({ version: z.coerce.number().int().positive() });
15
20
  // Shape only. The rules - trimming, emptiness, length, the slot lint - are the slice's,
16
21
  // so one set of messages reaches the editor.
17
22
  const promptBody = z.object({
@@ -26,7 +31,7 @@ export function promptRoutes(deps) {
26
31
  return (new Hono()
27
32
  // Every kind in one list: 04 Prompts filters by tab, and Duplicate needs the body it
28
33
  // is copying.
29
- .get("/", (c) => c.json({ prompts: listPrompts(deps.db) }))
34
+ .get("/", (c) => c.json({ prompts: withPhotorealistic(deps.db, listPrompts(deps.db)) }))
30
35
  .post("/", zValidator("json", promptBody, onInvalid), (c) => {
31
36
  const result = createPrompt(library, c.req.valid("json"));
32
37
  return result.ok ? c.json(result.value, 201) : refused(c, result, "prompt");
@@ -34,6 +39,38 @@ export function promptRoutes(deps) {
34
39
  .put("/:id", zValidator("param", idParam, onInvalid), zValidator("json", promptBody, onInvalid), (c) => {
35
40
  const result = updatePrompt(library, c.req.valid("param").id, c.req.valid("json"));
36
41
  return result.ok ? c.json(result.value) : refused(c, result, "prompt");
42
+ })
43
+ // An Image prompt's "Draws photorealistic pictures" tick (`slices/library/photorealistic.ts`).
44
+ .put("/:id/photorealistic", zValidator("param", idParam, onInvalid), zValidator("json", z.object({ photorealistic: z.boolean() }), onInvalid), (c) => {
45
+ const result = setPromptPhotorealistic(deps.db, c.req.valid("param").id, c.req.valid("json").photorealistic);
46
+ if (result.ok)
47
+ return c.body(null, 204);
48
+ return result.reason === "not-found"
49
+ ? refused(c, { ok: false, reason: "not-found" }, "prompt")
50
+ : problem(c, {
51
+ status: 400,
52
+ title: titleOf(400),
53
+ detail: "Only an Image prompt can be marked photorealistic. Open the prompt from the Image tab in Library → Prompts.",
54
+ });
55
+ })
56
+ // Every saved version, newest first (`slices/library/history.ts`).
57
+ .get("/:id/history", zValidator("param", idParam, onInvalid), (c) => {
58
+ const prompt = promptById(deps.db, c.req.valid("param").id);
59
+ return prompt === undefined
60
+ ? refused(c, { ok: false, reason: "not-found" }, "prompt")
61
+ : c.json({ versions: versionsOrCurrent(deps.db, "prompt", prompt) });
62
+ })
63
+ .post("/:id/history/:version/restore", zValidator("param", versionParam, onInvalid), (c) => {
64
+ const { id, version } = c.req.valid("param");
65
+ const result = restorePrompt(library, id, version);
66
+ return result.ok ? c.json(result.value) : refused(c, result, "prompt");
67
+ })
68
+ // The templates, schedules and projects that name it.
69
+ .get("/:id/used-by", zValidator("param", idParam, onInvalid), (c) => {
70
+ const prompt = promptById(deps.db, c.req.valid("param").id);
71
+ return prompt === undefined
72
+ ? refused(c, { ok: false, reason: "not-found" }, "prompt")
73
+ : c.json(usedBy(deps.db, { item: "prompt", kind: prompt.kind, name: prompt.name }));
37
74
  })
38
75
  // A project holds its own rendered text, so nothing cascades and a
39
76
  // template used by past projects is deleted like any other.
@@ -1,17 +1,36 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
+ import { aliasCountMax } from "../../kernel/ports/narration-aliases.js";
5
+ import { listNarrationAliases, saveNarrationAliases, } from "../../slices/narration/aliases-library.js";
4
6
  import { collectSharedGlossary } from "../../slices/narration/shared-glossary.js";
5
- import { onInvalid } from "./problem.js";
7
+ import { onInvalid, problem, titleOf } from "./problem.js";
6
8
  // The pronunciations of every project's glossary, merged, for Edit project to copy into a
7
9
  // project that shares them. `except` leaves out the project being edited, whose own glossary
8
- // already comes first.
10
+ // already comes first. Library → Aliases lives here too: the other way to change how a word
11
+ // is said.
9
12
  export function pronunciationRoutes(deps) {
10
- return new Hono().get("/shared", zValidator("query", z.object({
13
+ const library = { db: deps.db, ids: deps.ids, clock: deps.clock };
14
+ return (new Hono()
15
+ .get("/shared", zValidator("query", z.object({
11
16
  except: z
12
17
  .string()
13
18
  .max(64)
14
19
  .regex(/^[0-9A-Za-z_-]*$/)
15
20
  .optional(),
16
- }), onInvalid), (c) => c.json(collectSharedGlossary(deps, c.req.valid("query").except)));
21
+ }), onInvalid), (c) => c.json(collectSharedGlossary(deps, c.req.valid("query").except)))
22
+ .get("/aliases", (c) => c.json({ aliases: listNarrationAliases(deps.db) }))
23
+ // The editor saves the whole ordered list at once; each row is checked in the slice, so
24
+ // the editor gets "Alias 3: …" sentences rather than schema paths.
25
+ .put("/aliases", zValidator("json", z.object({ aliases: z.array(z.unknown()).max(aliasCountMax * 2) }), onInvalid), (c) => {
26
+ const result = saveNarrationAliases(library, c.req.valid("json").aliases);
27
+ if (result.ok)
28
+ return c.json({ aliases: result.aliases });
29
+ return problem(c, {
30
+ status: 400,
31
+ title: titleOf(400),
32
+ detail: `These aliases can't be saved yet: ${result.fields.map((field) => field.message).join(" ")}`,
33
+ extensions: { fields: result.fields },
34
+ });
35
+ }));
17
36
  }
@@ -1,19 +1,40 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { zValidator } from "@hono/zod-validator";
2
3
  import { Hono } from "hono";
3
4
  import { z } from "zod";
5
+ import { keyProbes } from "../../adapters/key-probes.js";
4
6
  import { videoModelsOf } from "../../catalog/schema.js";
7
+ import { defaultSpeechVoice, detectSpeechCached } from "../../kernel/ports/system-speech.js";
8
+ import { switchAllRetired, switchRetiredModel } from "../../slices/model-upkeep/switch.js";
9
+ import { retiredModelUsage, usageKinds, usageSlots } from "../../slices/model-upkeep/usage.js";
5
10
  import { cliPathMaxLength, saveCliPath } from "../../slices/settings/cli-paths.js";
11
+ import { dismissFirstRun, firstRunStatus } from "../../slices/settings/first-run.js";
12
+ import { checkProviderHealth } from "../../slices/settings/health.js";
13
+ import { keyGuides } from "../../slices/settings/key-guides.js";
14
+ import { testProviderKey } from "../../slices/settings/key-test.js";
6
15
  import { keyStatus, removeProviderKey, saveProviderKey } from "../../slices/settings/keys.js";
7
- import { isLocalCliProvider, providerById, providerIds } from "../../slices/settings/model.js";
16
+ import { isUncataloguedProvider, providerById, providerIds } from "../../slices/settings/model.js";
8
17
  import { createModelCatalog } from "../../slices/settings/models.js";
9
18
  import { providerStatuses } from "../../slices/settings/readiness.js";
10
19
  import { onInvalid, problem, titleOf } from "./problem.js";
11
20
  const providerParam = z.object({ id: z.enum(providerIds) });
21
+ const healthQuery = z.object({ provider: z.enum(providerIds).optional() });
12
22
  // ceiling: no format check is allowed on a key, so the only thing said about the value is
13
23
  // that it is a string of a length a key could plausibly have. Raise the bound if a provider
14
24
  // ever issues something longer.
15
25
  const pathBody = z.object({ path: z.string().max(cliPathMaxLength) });
16
26
  const keyBody = z.object({ key: z.string().min(1).max(4096) });
27
+ const modelId = z.string().min(1).max(200);
28
+ const switchBody = z
29
+ .object({
30
+ kind: z.enum(usageKinds),
31
+ id: z.string().min(1).max(64),
32
+ slot: z.enum(usageSlots),
33
+ from: z.object({ provider: z.string().min(1).max(64), model: modelId }).strict(),
34
+ to: modelId,
35
+ })
36
+ .strict();
37
+ const noFetch = "Slopify cannot reach the internet from here yet because it is still starting. Wait a moment, reload the page and try again.";
17
38
  // The return type is inferred so Hono keeps the route types the SPA's client is
18
39
  // generated from; see stagingRoutes.
19
40
  export function providerRoutes(deps) {
@@ -22,6 +43,7 @@ export function providerRoutes(deps) {
22
43
  db: deps.db,
23
44
  probe: deps.probe,
24
45
  hostCliStatus: deps.hostCliStatus,
46
+ host: deps.speechHost,
25
47
  };
26
48
  const catalog = createModelCatalog({
27
49
  load: deps.modelsFor ?? (() => Promise.reject(new Error("Model catalog unavailable"))),
@@ -29,7 +51,67 @@ export function providerRoutes(deps) {
29
51
  now: () => deps.clock.now().getTime(),
30
52
  report: (provider) => deps.log.write("warn", "model-catalog", { detail: `Could not load models for ${provider}` }),
31
53
  });
54
+ const upkeep = () => ({ ...deps, uuid: deps.drafts?.uuid ?? randomUUID });
32
55
  return (new Hono()
56
+ // The step lists Settings → Providers shows beside each key field.
57
+ .get("/key-guides", (c) => c.json({ guides: keyGuides }))
58
+ // What the first launch found, and the providers Play picks by default.
59
+ .get("/first-run", async (c) => {
60
+ c.header("Cache-Control", "no-store");
61
+ return c.json(await firstRunStatus({ db: deps.db, ...(deps.modelsFor === undefined ? {} : { modelsFor: deps.modelsFor }) }, await providerStatuses(readiness)));
62
+ })
63
+ .post("/first-run/dismiss", (c) => {
64
+ dismissFirstRun(deps.db);
65
+ return c.body(null, 204);
66
+ })
67
+ // "Check all": every CLI signed in, every key valid, every chosen model still offered and
68
+ // answering for the key. `?provider=codex` checks that one only (its row's Check again,
69
+ // or a sign-in fix-it's).
70
+ .post("/health", zValidator("query", healthQuery, onInvalid), async (c) => {
71
+ if (deps.fetch === undefined)
72
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
73
+ const catalogue = deps.catalogue;
74
+ return c.json(await checkProviderHealth({
75
+ ...readiness,
76
+ clock: deps.clock,
77
+ fetch: deps.fetch,
78
+ probes: keyProbes,
79
+ ...(catalogue === undefined ? {} : { catalogue: () => catalogue.read() }),
80
+ ...(deps.modelsFor === undefined ? {} : { modelsFor: deps.modelsFor }),
81
+ ...(deps.cliLogin === undefined ? {} : { login: deps.cliLogin }),
82
+ }, c.req.valid("query").provider));
83
+ })
84
+ // Checks for new, repriced and retired models now instead of waiting for the daily check.
85
+ .post("/catalogue/check", async (c) => {
86
+ if (!deps.catalogue?.sync)
87
+ return problem(c, {
88
+ status: 503,
89
+ title: titleOf(503),
90
+ detail: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
91
+ });
92
+ await deps.catalogue.sync();
93
+ return c.json(deps.catalogue.status());
94
+ })
95
+ // Every template, schedule, draft and unfinished project still using a retired model.
96
+ .get("/catalogue/retired", (c) => {
97
+ c.header("Cache-Control", "no-store");
98
+ return c.json({
99
+ usages: deps.catalogue === undefined ? [] : retiredModelUsage(deps.db, deps.catalogue.read()),
100
+ });
101
+ })
102
+ .post("/catalogue/retired/switch", zValidator("json", switchBody, onInvalid), async (c) => {
103
+ if (deps.catalogue === undefined)
104
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
105
+ const result = await switchRetiredModel(upkeep(), deps.catalogue.read(), c.req.valid("json"));
106
+ if (!result.ok)
107
+ return problem(c, { status: 409, title: titleOf(409), detail: result.message });
108
+ return c.json(result);
109
+ })
110
+ .post("/catalogue/retired/switch-all", async (c) => {
111
+ if (deps.catalogue === undefined)
112
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
113
+ return c.json(await switchAllRetired(upkeep(), deps.catalogue.read()));
114
+ })
33
115
  .get("/catalogue", (c) => c.json(deps.catalogue?.status() ?? {
34
116
  warning: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
35
117
  }))
@@ -55,6 +137,19 @@ export function providerRoutes(deps) {
55
137
  // What Settings draws its rails from and Play its dropdowns: every provider, with
56
138
  // the one fact that decides whether it is selectable.
57
139
  .get("/", async (c) => c.json({ providers: await providerStatuses(readiness) }))
140
+ // The system voice's speech programs and their voices, for Settings → Voices' picker.
141
+ .get("/system-voice/voices", async (c) => {
142
+ const found = await detectSpeechCached(deps.probe, deps.speechHost ?? process);
143
+ return c.json({
144
+ engines: found.engines.map((engine) => ({
145
+ id: engine.id,
146
+ name: engine.name,
147
+ voices: engine.voices,
148
+ defaultVoice: defaultSpeechVoice(engine)?.id ?? null,
149
+ })),
150
+ issue: found.issue ?? null,
151
+ });
152
+ })
58
153
  .get("/:id/models", zValidator("param", providerParam, onInvalid), async (c) => {
59
154
  const { id } = c.req.valid("param");
60
155
  // Animate images lists the provider's image-to-video models, which only the catalogue
@@ -64,7 +159,7 @@ export function providerRoutes(deps) {
64
159
  models: deps.catalogue === undefined ? [] : videoModelsOf(deps.catalogue.read(), id),
65
160
  allowsCustom: false,
66
161
  });
67
- if (deps.catalogue && !isLocalCliProvider(id))
162
+ if (deps.catalogue && !isUncataloguedProvider(id))
68
163
  return c.json({
69
164
  models: deps.catalogue.models(id, providerById(id).family).map((m) => ({
70
165
  ...m,
@@ -101,6 +196,25 @@ export function providerRoutes(deps) {
101
196
  catalog.invalidate(id);
102
197
  // The response says a key is stored and shows the mask, never the value.
103
198
  return c.json(keyStatus(keys, id));
199
+ })
200
+ // The Test button: the cheapest read the provider offers, answered in plain words. With
201
+ // a `key` in the body, that pasted key is tried instead of the saved one and is neither
202
+ // stored nor logged; with no body, the saved key is.
203
+ .post("/:id/key/test", zValidator("param", providerParam, onInvalid), async (c) => {
204
+ const { id } = c.req.valid("param");
205
+ if (providerById(id).auth !== "key")
206
+ return refusal(c, id, "cli-provider");
207
+ if (deps.fetch === undefined)
208
+ return problem(c, { status: 503, title: titleOf(503), detail: noFetch });
209
+ const raw = await c.req.json().catch(() => undefined);
210
+ const candidate = raw === undefined ? undefined : keyBody.safeParse(raw);
211
+ if (candidate !== undefined && !candidate.success)
212
+ return problem(c, {
213
+ status: 400,
214
+ title: titleOf(400),
215
+ detail: `The pasted ${providerById(id).displayName} key could not be tested: it is empty or longer than any key. Paste the whole key in Settings → Providers → ${providerById(id).displayName}, then choose Test again.`,
216
+ });
217
+ return c.json(await testProviderKey({ db: deps.db, clock: deps.clock, fetch: deps.fetch, probes: keyProbes }, id, candidate?.data.key));
104
218
  })
105
219
  .delete("/:id/key", zValidator("param", providerParam, onInvalid), (c) => {
106
220
  const { id } = c.req.valid("param");
@@ -118,7 +232,9 @@ function refusal(c, id, reason) {
118
232
  return problem(c, {
119
233
  status: 400,
120
234
  title: titleOf(400),
121
- detail: `${name} signs in through its own command-line tool, so it has no API key to save here. Sign in with that tool on your computer instead.`,
235
+ detail: providerById(id).auth === "local"
236
+ ? `${name} is your computer's own speech, so it has no API key to save. It works as soon as a speech program is installed.`
237
+ : `${name} signs in through its own command-line tool, so it has no API key to save here. Sign in with that tool on your computer instead.`,
122
238
  });
123
239
  }
124
240
  if (reason === "absent") {
@@ -0,0 +1,131 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { revisionControlSchema } from "../../slices/control/revision-control-schema.js";
5
+ import { recoverProject } from "../../slices/rebuild/recovery.js";
6
+ import { redoStarting } from "../../slices/rebuild/review-redo.js";
7
+ import { actOnVerdict, listVerdicts, verdictById } from "../../slices/reviews/repo.js";
8
+ import { latestReviews } from "../../slices/reviews/view.js";
9
+ import { currentRevisionId } from "../../slices/revisions/repo.js";
10
+ import { getRevisionView } from "../../slices/revisions/view.js";
11
+ import { onInvalid, problem, titleOf } from "./problem.js";
12
+ const id = z
13
+ .string()
14
+ .min(1)
15
+ .max(64)
16
+ .regex(/^[0-9A-Za-z_-]+$/);
17
+ const projectParam = z.object({ id });
18
+ const verdictParam = projectParam.extend({ verdictId: id });
19
+ // The project page's review column: each reviewed item's latest verdict with its reasons,
20
+ // Overrule to accept a failed item as it is, and Redo to have it made again through the
21
+ // same path as More → make it again in its section.
22
+ export function reviewRoutes(deps) {
23
+ const gone = (c) => problem(c, {
24
+ status: 404,
25
+ title: titleOf(404),
26
+ detail: "This review no longer exists; the project may have been deleted or restored from a backup. Reload the project page.",
27
+ });
28
+ const notify = (projectId) => {
29
+ try {
30
+ deps.hub.emit(projectId, { type: "project.updated", projectId });
31
+ }
32
+ catch (error) {
33
+ deps.log.write("error", "review.notify", {
34
+ projectId,
35
+ detail: error instanceof Error ? error.message : String(error),
36
+ });
37
+ }
38
+ };
39
+ return new Hono()
40
+ .get("/:id/reviews", zValidator("param", projectParam, onInvalid), (c) => {
41
+ const projectId = c.req.valid("param").id;
42
+ const head = currentRevisionId(deps.db, projectId);
43
+ const view = head === undefined ? undefined : getRevisionView(deps, projectId, head);
44
+ const outputs = (view?.outputs ?? [])
45
+ .filter((row) => row.selected)
46
+ .map((row) => ({
47
+ workKey: row.workKey,
48
+ fingerprint: row.fingerprint,
49
+ outputId: row.output.id,
50
+ }));
51
+ return c.json({ reviews: latestReviews(listVerdicts(deps.db, projectId), outputs) });
52
+ })
53
+ .post("/:id/reviews/:verdictId/overrule", zValidator("param", verdictParam, onInvalid), (c) => {
54
+ const { id: projectId, verdictId } = c.req.valid("param");
55
+ const busy = () => problem(c, {
56
+ status: 409,
57
+ title: titleOf(409),
58
+ detail: "This verdict can't be overruled: it passed, or Slopify has already started making the item again. Wait for the new version to land, then reload the project page and overrule its review if it fails again.",
59
+ extensions: { reason: "conflict" },
60
+ });
61
+ // Checked and changed in one synchronous step, so a redo cannot start in between.
62
+ if (redoStarting(verdictId))
63
+ return busy();
64
+ const result = actOnVerdict(deps.db, projectId, verdictId, "overruled", deps.clock.now().toISOString());
65
+ if (!result.ok)
66
+ return result.reason === "not-found" ? gone(c) : busy();
67
+ // A called-off redo released the work that was waiting on the item.
68
+ try {
69
+ deps.runner.tick(projectId);
70
+ }
71
+ catch {
72
+ // The next tick of this project picks the released work up.
73
+ }
74
+ notify(projectId);
75
+ return c.json({ review: result.value });
76
+ })
77
+ .post("/:id/reviews/:verdictId/redo", zValidator("param", verdictParam, onInvalid), zValidator("json", revisionControlSchema, onInvalid), async (c) => {
78
+ const { id: projectId, verdictId } = c.req.valid("param");
79
+ const verdict = verdictById(deps.db, projectId, verdictId);
80
+ if (verdict === undefined)
81
+ return gone(c);
82
+ if (deps.rebuild === undefined)
83
+ return problem(c, {
84
+ status: 503,
85
+ title: titleOf(503),
86
+ detail: "Redo is not ready yet because Slopify is still starting. Wait a moment, reload the page and try again.",
87
+ });
88
+ if (verdict.redoState === "pending" || verdict.redoState === "started")
89
+ return problem(c, {
90
+ status: 409,
91
+ title: titleOf(409),
92
+ detail: "Slopify is already making this item again. Wait for it to land, then reload the project page.",
93
+ extensions: { reason: "conflict" },
94
+ });
95
+ const result = await recoverProject(deps.rebuild, projectId, {
96
+ ...c.req.valid("json"),
97
+ action: { kind: "redo", item: verdict.itemKey },
98
+ });
99
+ if (!result.ok)
100
+ return refused(c, result, projectId);
101
+ actOnVerdict(deps.db, projectId, verdictId, "redone", deps.clock.now().toISOString());
102
+ notify(projectId);
103
+ return c.json(result, 202);
104
+ });
105
+ function refused(c, result, projectId) {
106
+ const status = result.reason === "no-project"
107
+ ? 404
108
+ : result.reason === "invalid-selection" || result.reason === "invalid-edit"
109
+ ? 400
110
+ : 409;
111
+ const lead = result.reason === "running"
112
+ ? "This item, or work that depends on it, is still running. Wait for it to finish, or Pause the project, then use Redo again."
113
+ : result.reason === "readiness"
114
+ ? "Slopify can't make this item again yet. Fix what is listed in Settings → Providers or Edit project, then use Redo again."
115
+ : result.reason === "conflict" || result.reason === "control-changed"
116
+ ? "This project changed since the page loaded. Reload the project page, then use Redo again."
117
+ : result.reason === "invalid-selection"
118
+ ? "This item can't be made again."
119
+ : "Slopify couldn't make this item again.";
120
+ return problem(c, {
121
+ status,
122
+ title: titleOf(status),
123
+ detail: [lead, ...(result.fields ?? []).map((row) => row.message)].join(" "),
124
+ extensions: {
125
+ reason: result.reason,
126
+ fields: result.fields ?? [],
127
+ currentRevisionId: currentRevisionId(deps.db, projectId) ?? null,
128
+ },
129
+ });
130
+ }
131
+ }
@@ -1,9 +1,8 @@
1
- import { createReadStream } from "node:fs";
2
- import { Readable } from "node:stream";
3
1
  import { zValidator } from "@hono/zod-validator";
4
2
  import { Hono } from "hono";
5
3
  import { z } from "zod";
6
4
  import { findRevisionDownload, revisionImagesZip } from "../../slices/revisions/downloads.js";
5
+ import { fileResponse } from "./byte-range.js";
7
6
  import { replyForFolder } from "./folder-location.js";
8
7
  import { onInvalid, problem, titleOf } from "./problem.js";
9
8
  const id = z
@@ -36,9 +35,8 @@ export function revisionFileRoutes(deps) {
36
35
  // `?inline=1` lets the project page open a PDF in a browser tab instead of saving
37
36
  // it. Only PDFs: anything else the browser might render stays a download.
38
37
  const inline = c.req.query("inline") === "1" && value.contentType === "application/pdf";
39
- return c.body(Readable.toWeb(createReadStream(value.path)), 200, {
38
+ return fileResponse(c, value.path, value.bytes, {
40
39
  "content-type": value.contentType,
41
- "content-length": String(value.bytes),
42
40
  "content-disposition": `${inline ? "inline" : "attachment"}; filename="${value.filename}"`,
43
41
  "x-content-type-options": "nosniff",
44
42
  });
@@ -65,7 +63,7 @@ function unavailable(c, reason) {
65
63
  status: 404,
66
64
  title: titleOf(404),
67
65
  detail: reason === "missing-file"
68
- ? "This file was deleted or moved from the project folder. In the Edit tab, use Rebuild affected outputs to make it again."
66
+ ? "This file was deleted or moved from the project folder. In the Edit tab, use Choose what to remake (in the project's More menu) to make it again."
69
67
  : "This file is no longer part of the project. Reload the page to see its current files.",
70
68
  extensions: { reason },
71
69
  });
@@ -37,7 +37,7 @@ const details = {
37
37
  conflict: "This project changed while you were editing. Reload the page to see the latest, then make your change again.",
38
38
  "idempotency-conflict": "This change was already sent with different details. Reload the page, then try again.",
39
39
  "invalid-edit": "Some of your changes are not valid. Fix the highlighted fields and save again.",
40
- "stale-preview": "What needs rebuilding changed since you opened this review. Cancel it and open Rebuild affected outputs again to see the latest.",
40
+ "stale-preview": "What needs rebuilding changed since you opened this review. Cancel it and open Choose what to remake (in the project's More menu) again to see the latest.",
41
41
  "invalid-selection": "Some of the outputs you picked can no longer be rebuilt. Reload the page and choose again.",
42
42
  "review-required": 'This rebuild touches content you supplied yourself. Tick "Keep the provided content" for each item in the review, then choose Start rebuild.',
43
43
  "cost-ack-required": "Some costs for this rebuild are unknown. Tick the box confirming you understand, then choose Start rebuild.",
@@ -0,0 +1,25 @@
1
+ import { zValidator } from "@hono/zod-validator";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { projectExists } from "../../slices/admission/repo.js";
5
+ import { runCostOf } from "../../slices/run-cost/panel.js";
6
+ import { onInvalid, problem, titleOf } from "./problem.js";
7
+ const idParam = z.object({ id: z.string().min(1).max(64) });
8
+ // The project's Run cost tab and its "Waiting for … limits" line: what the run's provider
9
+ // calls used and cost, and any CLI plan limit a stage is waiting on. Read from the stored
10
+ // calls alone; nothing here reaches a provider.
11
+ //
12
+ // The return type is inferred so Hono keeps the route types the SPA's client is generated
13
+ // from; see stagingRoutes.
14
+ export function runCostRoutes(deps) {
15
+ return new Hono().get("/:id/run-cost", zValidator("param", idParam, onInvalid), (c) => {
16
+ const id = c.req.valid("param").id;
17
+ if (!projectExists(deps.db, id))
18
+ return problem(c, {
19
+ status: 404,
20
+ title: titleOf(404),
21
+ detail: "This project no longer exists. Go back to Projects to pick another.",
22
+ });
23
+ return c.json(runCostOf(deps.db, id));
24
+ });
25
+ }