@gentbajko/slopify 4.0.0 → 4.2.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 (375) hide show
  1. package/dist/adapters/account-limits.js +1 -1
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +1 -1
  4. package/dist/adapters/alignment/multilingual.js +2 -1
  5. package/dist/adapters/alignment/runner.js +1 -1
  6. package/dist/adapters/alignment/worker.js +1 -1
  7. package/dist/adapters/explain.js +10 -9
  8. package/dist/adapters/host-cli/index.js +2 -1
  9. package/dist/adapters/host-cli/transport.js +2 -1
  10. package/dist/adapters/image/bytes.js +2 -1
  11. package/dist/adapters/image/codex.js +6 -5
  12. package/dist/adapters/image/fal.js +4 -3
  13. package/dist/adapters/image/google.js +3 -2
  14. package/dist/adapters/image/models.js +2 -2
  15. package/dist/adapters/image/openai.js +2 -1
  16. package/dist/adapters/image/replicate.js +3 -2
  17. package/dist/adapters/image/video.js +2 -1
  18. package/dist/adapters/key-probes.js +1 -1
  19. package/dist/adapters/llm/antigravity-models.js +1 -1
  20. package/dist/adapters/llm/antigravity.js +7 -6
  21. package/dist/adapters/llm/assistant-cli.js +1 -1
  22. package/dist/adapters/llm/claude-code-models.js +1 -1
  23. package/dist/adapters/llm/claude-code.js +3 -2
  24. package/dist/adapters/llm/codex-models.js +1 -1
  25. package/dist/adapters/llm/document-workspace.js +3 -2
  26. package/dist/adapters/llm/image-workspace.js +3 -2
  27. package/dist/adapters/llm/openrouter.js +2 -1
  28. package/dist/adapters/llm/run-cli.js +3 -2
  29. package/dist/adapters/tts/gemini.js +2 -1
  30. package/dist/adapters/tts/inworld-async.js +2 -1
  31. package/dist/adapters/tts/inworld.js +3 -2
  32. package/dist/adapters/tts/system.js +2 -1
  33. package/dist/catalog/registry.js +20 -13
  34. package/dist/catalog/schema.js +2 -1
  35. package/dist/catalog/validate.js +5 -2
  36. package/dist/edge/http/actions.js +7 -7
  37. package/dist/edge/http/app.js +5 -3
  38. package/dist/edge/http/assistant.js +3 -1
  39. package/dist/edge/http/backups.js +1 -1
  40. package/dist/edge/http/channel-memory.js +2 -2
  41. package/dist/edge/http/channels.js +1 -1
  42. package/dist/edge/http/checkpoints.js +7 -5
  43. package/dist/edge/http/design-templates.js +3 -3
  44. package/dist/edge/http/designs.js +1 -1
  45. package/dist/edge/http/document-themes.js +3 -3
  46. package/dist/edge/http/draft-problem.js +1 -1
  47. package/dist/edge/http/extension-auth.js +1 -1
  48. package/dist/edge/http/home.js +9 -1
  49. package/dist/edge/http/model-checks.js +4 -4
  50. package/dist/edge/http/onboarding.js +29 -12
  51. package/dist/edge/http/outputs.js +3 -2
  52. package/dist/edge/http/patch-notes.js +2 -2
  53. package/dist/edge/http/planning.js +1 -1
  54. package/dist/edge/http/project-create.js +1 -1
  55. package/dist/edge/http/project-templates.js +2 -2
  56. package/dist/edge/http/projects.js +22 -4
  57. package/dist/edge/http/prompts.js +2 -2
  58. package/dist/edge/http/pronunciation-check.js +133 -0
  59. package/dist/edge/http/pronunciations.js +26 -0
  60. package/dist/edge/http/providers.js +9 -10
  61. package/dist/edge/http/refusal.js +1 -1
  62. package/dist/edge/http/remote-access.js +4 -4
  63. package/dist/edge/http/remote-guard.js +2 -2
  64. package/dist/edge/http/reviews.js +1 -1
  65. package/dist/edge/http/revision-files.js +1 -1
  66. package/dist/edge/http/revisions.js +4 -4
  67. package/dist/edge/http/schedules.js +13 -11
  68. package/dist/edge/http/settings.js +6 -2
  69. package/dist/edge/http/social.js +5 -4
  70. package/dist/edge/http/staging.js +1 -1
  71. package/dist/edge/http/storage.js +12 -4
  72. package/dist/edge/http/studio.js +71 -19
  73. package/dist/edge/http/style-preview.js +2 -2
  74. package/dist/edge/http/template-studio.js +12 -9
  75. package/dist/edge/http/trash.js +1 -1
  76. package/dist/edge/http/youtube-edits.js +1 -1
  77. package/dist/edge/mcp/excluded-routes.js +4 -3
  78. package/dist/edge/mcp/instructions.js +3 -3
  79. package/dist/edge/mcp/library-tools.js +8 -5
  80. package/dist/edge/mcp/remake-tools.js +7 -4
  81. package/dist/edge/mcp/server.js +1 -1
  82. package/dist/edge/mcp/tools-channels.js +9 -9
  83. package/dist/edge/mcp/tools-designs.js +9 -9
  84. package/dist/edge/mcp/tools-library.js +31 -7
  85. package/dist/edge/mcp/tools-planning.js +18 -18
  86. package/dist/edge/mcp/tools-projects.js +10 -10
  87. package/dist/edge/mcp/tools-publishing.js +9 -9
  88. package/dist/edge/mcp/tools-runs.js +9 -9
  89. package/dist/edge/mcp/tools-schedules.js +6 -6
  90. package/dist/edge/mcp/tools-setup.js +20 -14
  91. package/dist/edge/mcp/tools-social.js +5 -4
  92. package/dist/edge/mcp/tools-storage.js +3 -3
  93. package/dist/edge/mcp/tools-template-studio.js +18 -18
  94. package/dist/edge/remote-access/model.js +1 -1
  95. package/dist/extension/slopify-studio-chrome.zip +0 -0
  96. package/dist/extension/slopify-studio-firefox.zip +0 -0
  97. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  98. package/dist/kernel/pipeline.js +12 -0
  99. package/dist/kernel/ports/settings-places.js +31 -0
  100. package/dist/kernel/ports/system-speech.js +31 -3
  101. package/dist/kernel/runner/attempt.js +11 -3
  102. package/dist/kernel/runner/providers.js +3 -2
  103. package/dist/kernel/runner/standalone.js +2 -2
  104. package/dist/main.js +5 -3
  105. package/dist/model-catalog.js +20 -0
  106. package/dist/patch-notes/4.0.0.md +13 -13
  107. package/dist/patch-notes/4.1.0.md +42 -0
  108. package/dist/patch-notes/4.2.0.md +28 -0
  109. package/dist/patch-notes/index.json +12 -0
  110. package/dist/slices/admission/listing-facts.js +95 -0
  111. package/dist/slices/admission/repo.js +9 -6
  112. package/dist/slices/admission/rules.js +7 -7
  113. package/dist/slices/admission/schema.js +2 -0
  114. package/dist/slices/admission/start.js +7 -0
  115. package/dist/slices/admission/state-words.js +34 -5
  116. package/dist/slices/assistant/cost.js +1 -1
  117. package/dist/slices/assistant/gate.js +1 -0
  118. package/dist/slices/assistant/model.js +10 -3
  119. package/dist/slices/assistant/turn.js +5 -3
  120. package/dist/slices/autopilot/warnings.js +1 -1
  121. package/dist/slices/backups/service.js +5 -5
  122. package/dist/slices/channels/cast-images.js +1 -1
  123. package/dist/slices/channels/schema.js +1 -1
  124. package/dist/slices/channels/videos.js +1 -1
  125. package/dist/slices/control/providers.js +2 -2
  126. package/dist/slices/designs/budget.js +1 -1
  127. package/dist/slices/designs/calls.js +2 -2
  128. package/dist/slices/designs/check-context.js +4 -4
  129. package/dist/slices/designs/fields.js +4 -4
  130. package/dist/slices/designs/gate.js +7 -5
  131. package/dist/slices/designs/render-input.js +1 -1
  132. package/dist/slices/designs/requests.js +1 -1
  133. package/dist/slices/designs/runner.js +1 -1
  134. package/dist/slices/designs/sample.js +2 -1
  135. package/dist/slices/designs/service.js +1 -1
  136. package/dist/slices/designs/template-checks.js +2 -2
  137. package/dist/slices/designs/template-make.js +3 -3
  138. package/dist/slices/designs/templates.js +3 -3
  139. package/dist/slices/document/fonts.js +1 -1
  140. package/dist/slices/document/model.js +7 -4
  141. package/dist/slices/document/render.js +2 -1
  142. package/dist/slices/document/theme-schema.js +1 -1
  143. package/dist/slices/document/theme.js +49 -3
  144. package/dist/slices/episodes/summarize.js +2 -2
  145. package/dist/slices/estimate/index.js +28 -22
  146. package/dist/slices/estimate/requests.js +4 -4
  147. package/dist/slices/fixes/rules.js +3 -1
  148. package/dist/slices/fonts/catalog.js +2 -1
  149. package/dist/slices/fonts/coverage.js +2 -1
  150. package/dist/slices/images/appearance.js +2 -1
  151. package/dist/slices/images/scenes.js +2 -1
  152. package/dist/slices/keywords/digest.js +2 -1
  153. package/dist/slices/keywords/rules.js +2 -2
  154. package/dist/slices/keywords/specs.js +5 -5
  155. package/dist/slices/keywords/values.js +1 -1
  156. package/dist/slices/library/transfer.js +3 -3
  157. package/dist/slices/loudness/loudnorm.js +2 -1
  158. package/dist/slices/loudness/model.js +3 -2
  159. package/dist/slices/loudness/sentence-level.js +2 -1
  160. package/dist/slices/model-checks/rules.js +13 -0
  161. package/dist/slices/model-upkeep/model.js +1 -1
  162. package/dist/slices/model-upkeep/switch.js +3 -3
  163. package/dist/slices/model-upkeep/usage.js +13 -2
  164. package/dist/slices/narration/breaths.js +2 -1
  165. package/dist/slices/narration/clean-pieces.js +2 -1
  166. package/dist/slices/narration/pauses-model.js +3 -2
  167. package/dist/slices/narration/pronunciation-check.js +226 -0
  168. package/dist/slices/narration/pronunciation.js +7 -2
  169. package/dist/slices/narration/pronunciations-library.js +94 -0
  170. package/dist/slices/notifications/subject.js +2 -2
  171. package/dist/slices/onboarding/copy.js +19 -2
  172. package/dist/slices/onboarding/first-run.js +13 -38
  173. package/dist/slices/onboarding/install.js +2 -1
  174. package/dist/slices/onboarding/model.js +26 -21
  175. package/dist/slices/onboarding/preferred-model.js +14 -0
  176. package/dist/slices/onboarding/quick-short.js +103 -64
  177. package/dist/slices/onboarding/sample.js +4 -1
  178. package/dist/slices/onboarding/state.js +7 -0
  179. package/dist/slices/onboarding/template.js +6 -5
  180. package/dist/slices/outputs/add.js +3 -2
  181. package/dist/slices/outputs/service.js +2 -2
  182. package/dist/slices/patch-notes/library.js +1 -1
  183. package/dist/slices/play-drafts/readiness.js +3 -3
  184. package/dist/slices/play-drafts/review-inputs.js +1 -1
  185. package/dist/slices/play-drafts/service.js +17 -7
  186. package/dist/slices/project-templates/next-chapter.js +2 -1
  187. package/dist/slices/project-templates/provider-defaults.js +22 -0
  188. package/dist/slices/project-templates/setup-sections.js +26 -0
  189. package/dist/slices/project-templates/setup.js +6 -1
  190. package/dist/slices/rebuild/preview-details.js +5 -5
  191. package/dist/slices/rebuild/recipe-preparation.js +1 -1
  192. package/dist/slices/rebuild/recipe-save.js +2 -2
  193. package/dist/slices/rebuild/recipe-text.js +16 -4
  194. package/dist/slices/rebuild/recipe-validation.js +5 -4
  195. package/dist/slices/rebuild/recipe-voices.js +3 -2
  196. package/dist/slices/rebuild/recipe-work.js +4 -4
  197. package/dist/slices/rebuild/recovery.js +6 -6
  198. package/dist/slices/rebuild/remake-outdated.js +5 -5
  199. package/dist/slices/rebuild/review-redo.js +1 -1
  200. package/dist/slices/rebuild/runtime-animate.js +2 -2
  201. package/dist/slices/rebuild/runtime-article.js +4 -3
  202. package/dist/slices/rebuild/runtime-document.js +5 -4
  203. package/dist/slices/rebuild/runtime-export-bed.js +2 -1
  204. package/dist/slices/rebuild/runtime-export-edit.js +2 -1
  205. package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
  206. package/dist/slices/rebuild/runtime-export-short.js +1 -1
  207. package/dist/slices/rebuild/runtime-export.js +4 -3
  208. package/dist/slices/rebuild/runtime-image.js +4 -3
  209. package/dist/slices/rebuild/runtime-local.js +3 -2
  210. package/dist/slices/rebuild/runtime-narration-text.js +3 -2
  211. package/dist/slices/rebuild/runtime-provider.js +7 -6
  212. package/dist/slices/rebuild/runtime-review.js +6 -5
  213. package/dist/slices/rebuild/runtime-shorts.js +6 -5
  214. package/dist/slices/rebuild/runtime-store.js +17 -2
  215. package/dist/slices/rebuild/runtime-subtitles.js +13 -12
  216. package/dist/slices/rebuild/runtime-voices.js +2 -1
  217. package/dist/slices/rebuild/runtime-youtube.js +3 -2
  218. package/dist/slices/rebuild/service-readiness.js +11 -3
  219. package/dist/slices/research/synthesis.js +3 -2
  220. package/dist/slices/reviews/external-model.js +1 -1
  221. package/dist/slices/reviews/holds.js +5 -4
  222. package/dist/slices/reviews/model.js +8 -5
  223. package/dist/slices/reviews/rules.js +3 -3
  224. package/dist/slices/reviews/verdict.js +2 -1
  225. package/dist/slices/revisions/image-plan.js +1 -1
  226. package/dist/slices/revisions/mutation-assets.js +2 -1
  227. package/dist/slices/revisions/mutations.js +3 -2
  228. package/dist/slices/schedules/agenda.js +7 -5
  229. package/dist/slices/schedules/design-check.js +4 -4
  230. package/dist/slices/schedules/design-run.js +3 -3
  231. package/dist/slices/schedules/repo.js +6 -5
  232. package/dist/slices/schedules/schema.js +3 -0
  233. package/dist/slices/schedules/topics.js +5 -5
  234. package/dist/slices/settings/first-run.js +6 -32
  235. package/dist/slices/settings/health.js +30 -15
  236. package/dist/slices/settings/key-test.js +1 -1
  237. package/dist/slices/settings/model.js +4 -2
  238. package/dist/slices/settings/playback.js +2 -2
  239. package/dist/slices/shorts/keep.js +2 -1
  240. package/dist/slices/shorts/pick.js +4 -3
  241. package/dist/slices/shorts/prompts.js +2 -1
  242. package/dist/slices/shorts/render.js +3 -2
  243. package/dist/slices/social/media.js +5 -4
  244. package/dist/slices/social/pack.js +1 -1
  245. package/dist/slices/social/post-prompt.js +2 -1
  246. package/dist/slices/social/reshape.js +2 -2
  247. package/dist/slices/social/serve.js +1 -1
  248. package/dist/slices/social/service.js +3 -2
  249. package/dist/slices/social/state.js +1 -1
  250. package/dist/slices/storage/backup-import.js +2 -2
  251. package/dist/slices/storage/portable.js +1 -1
  252. package/dist/slices/storage/trim.js +23 -13
  253. package/dist/slices/studio/calendar.js +3 -1
  254. package/dist/slices/studio/disclosure.js +4 -4
  255. package/dist/slices/studio/model.js +1 -1
  256. package/dist/slices/studio/pack.js +9 -8
  257. package/dist/slices/studio/plan.js +1 -1
  258. package/dist/slices/studio/settings.js +6 -6
  259. package/dist/slices/studio/videos.js +23 -4
  260. package/dist/slices/style-preview/render.js +3 -3
  261. package/dist/slices/style-preview/schema.js +1 -1
  262. package/dist/slices/subtitles/captions.js +1 -1
  263. package/dist/slices/template-studio/anatomy-view.js +46 -3
  264. package/dist/slices/template-studio/bakeoff.js +3 -3
  265. package/dist/slices/template-studio/budget.js +6 -3
  266. package/dist/slices/template-studio/cards.js +78 -0
  267. package/dist/slices/template-studio/checks.js +99 -50
  268. package/dist/slices/template-studio/for-all.js +5 -5
  269. package/dist/slices/template-studio/free-video.js +1 -1
  270. package/dist/slices/template-studio/free.js +5 -5
  271. package/dist/slices/template-studio/model.js +19 -15
  272. package/dist/slices/template-studio/pack-import.js +14 -14
  273. package/dist/slices/template-studio/pack.js +9 -7
  274. package/dist/slices/template-studio/paid.js +12 -5
  275. package/dist/slices/template-studio/quote.js +18 -11
  276. package/dist/slices/template-studio/ready.js +7 -7
  277. package/dist/slices/template-studio/runner.js +1 -1
  278. package/dist/slices/template-studio/save.js +12 -12
  279. package/dist/slices/template-studio/say-it-as.js +3 -3
  280. package/dist/slices/template-studio/schedule-gate.js +1 -1
  281. package/dist/slices/template-studio/schedule-price.js +3 -1
  282. package/dist/slices/template-studio/schedule-sample.js +5 -1
  283. package/dist/slices/template-studio/schema.js +25 -0
  284. package/dist/slices/template-studio/service.js +3 -1
  285. package/dist/slices/template-studio/status.js +26 -9
  286. package/dist/slices/template-studio/topic.js +3 -3
  287. package/dist/slices/thumbnail/by-llm.js +1 -1
  288. package/dist/slices/uploads/repo.js +37 -1
  289. package/dist/slices/uploads/rules.js +3 -2
  290. package/dist/slices/video/edit-list.js +2 -2
  291. package/dist/slices/video/edit-settings.js +1 -1
  292. package/dist/slices/video/encoding.js +14 -5
  293. package/dist/slices/video/ffmpeg.js +2 -2
  294. package/dist/slices/video/plan.js +2 -1
  295. package/dist/slices/video/slideshow.js +1 -1
  296. package/dist/slices/voices/attribution.js +3 -2
  297. package/dist/slices/voices/model.js +9 -8
  298. package/dist/slices/voices/script.js +2 -1
  299. package/dist/slices/youtube/answer.js +2 -1
  300. package/dist/slices/youtube/edits-repo.js +1 -1
  301. package/dist/tutorials/AI-CLIs.md +25 -25
  302. package/dist/tutorials/Agents-MCP.md +32 -32
  303. package/dist/tutorials/Assistant.md +5 -5
  304. package/dist/tutorials/Backups.md +7 -7
  305. package/dist/tutorials/Calendar.md +17 -25
  306. package/dist/tutorials/Cast-Library.md +19 -19
  307. package/dist/tutorials/Channels.md +40 -35
  308. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  309. package/dist/tutorials/Create-Article.md +114 -0
  310. package/dist/tutorials/Create-Extras.md +112 -0
  311. package/dist/tutorials/Create-Images.md +165 -0
  312. package/dist/tutorials/Create-Narration.md +178 -0
  313. package/dist/tutorials/Create-Overview.md +244 -0
  314. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  315. package/dist/tutorials/Docker.md +3 -3
  316. package/dist/tutorials/Document-Themes.md +32 -32
  317. package/dist/tutorials/Editing-a-Project.md +95 -94
  318. package/dist/tutorials/FAQ.md +18 -18
  319. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  320. package/dist/tutorials/Home-and-Projects.md +101 -76
  321. package/dist/tutorials/Home.md +33 -33
  322. package/dist/tutorials/Install.md +13 -13
  323. package/dist/tutorials/Intros-and-Outros.md +33 -34
  324. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  325. package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
  326. package/dist/tutorials/Models.md +27 -27
  327. package/dist/tutorials/Multiple-Voices.md +41 -40
  328. package/dist/tutorials/Narration-Aliases-and-Glossary.md +101 -19
  329. package/dist/tutorials/Notifications.md +21 -17
  330. package/dist/tutorials/Other-Languages.md +15 -15
  331. package/dist/tutorials/PDF-Documents.md +38 -29
  332. package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
  333. package/dist/tutorials/Project-Page.md +118 -120
  334. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  335. package/dist/tutorials/Prompts.md +45 -45
  336. package/dist/tutorials/Providers-and-Keys.md +105 -98
  337. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  338. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  339. package/dist/tutorials/Remote-Access.md +2 -2
  340. package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
  341. package/dist/tutorials/Schedules.md +27 -28
  342. package/dist/tutorials/Settings-Reference.md +183 -204
  343. package/dist/tutorials/Shorts.md +34 -33
  344. package/dist/tutorials/Start-at-Login.md +3 -3
  345. package/dist/tutorials/Studio-Extension.md +22 -24
  346. package/dist/tutorials/Template-Studio.md +45 -42
  347. package/dist/tutorials/Templates.md +50 -79
  348. package/dist/tutorials/Trash.md +4 -4
  349. package/dist/tutorials/Troubleshooting.md +44 -44
  350. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  351. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  352. package/dist/tutorials/Video-Editing.md +34 -34
  353. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  354. package/dist/tutorials/YouTube-Description.md +26 -26
  355. package/dist/tutorials/Your-First-Short.md +35 -35
  356. package/dist/tutorials/_Sidebar.md +15 -15
  357. package/dist/web/assets/index-BNftKQrA.css +1 -0
  358. package/dist/web/assets/index-CQASqwjT.js +460 -0
  359. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  360. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  361. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  362. package/dist/web/index.html +2 -2
  363. package/package.json +2 -2
  364. package/dist/tutorials/Library-Overview.md +0 -76
  365. package/dist/tutorials/Play-Images.md +0 -165
  366. package/dist/tutorials/Play-Narration.md +0 -167
  367. package/dist/tutorials/Play-Outputs.md +0 -109
  368. package/dist/tutorials/Play-Overview.md +0 -227
  369. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  370. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  371. package/dist/web/assets/index-CkAI_2E7.css +0 -1
  372. package/dist/web/assets/index-DcD0cdfo.js +0 -334
  373. package/dist/web/assets/pdf-TODRwF31.js +0 -54
  374. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  375. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { zValidator } from "@hono/zod-validator";
3
3
  import { Hono } from "hono";
4
4
  import { z } from "zod";
5
- import { defaultSpeechVoice, detectSpeechCached, primaryLanguage, } from "../../kernel/ports/system-speech.js";
5
+ import { defaultSpeechVoice, detectSpeechCached, forgetDetectedSpeech, primaryLanguage, speechInstall, } from "../../kernel/ports/system-speech.js";
6
6
  import { loudnessOfDefault } from "../../slices/loudness/model.js";
7
7
  import { firstRunView } from "../../slices/onboarding/first-run.js";
8
8
  import { fullVideoDraft } from "../../slices/onboarding/full-video.js";
@@ -15,9 +15,10 @@ import { dismissFirstRun, recordShortRequest, shortRequestProject, } from "../..
15
15
  import { systemVoiceProvider } from "../../slices/settings/model.js";
16
16
  import { readSettings } from "../../slices/settings/playback.js";
17
17
  import { providerStatuses } from "../../slices/settings/readiness.js";
18
- import { listVoices } from "../../slices/settings/repo.js";
18
+ import { listVoices, writeSetting } from "../../slices/settings/repo.js";
19
19
  import { addVoice } from "../../slices/settings/voices.js";
20
20
  import { projectTitle } from "../../slices/storage/repo.js";
21
+ import { autostartAnsweredKey } from "../autostart/model.js";
21
22
  import { onInvalid, problem } from "./problem.js";
22
23
  import { createProject } from "./project-create.js";
23
24
  const packParam = z.object({ id: z.string().min(1).max(40) });
@@ -30,7 +31,16 @@ export function onboardingRoutes(deps) {
30
31
  hostCliStatus: deps.hostCliStatus,
31
32
  host: deps.speechHost,
32
33
  });
33
- const speech = () => detectSpeechCached(deps.probe, deps.speechHost ?? process);
34
+ const host = deps.speechHost ?? process;
35
+ const speech = () => detectSpeechCached(deps.probe, host);
36
+ const view = async () => firstRunView(deps.db, await statuses(), speechInstall(host));
37
+ // Done with the first-run screen (Skip, Done, a first short, or a project made elsewhere):
38
+ // it also answers "Start Slopify when I log in" as No, so a fresh install is never asked it
39
+ // in a dialog over Home. The screen's own row and Settings → General still turn it on.
40
+ const doneWithFirstRun = () => {
41
+ dismissFirstRun(deps.db, deps.clock.now().toISOString());
42
+ writeSetting(deps.db, autostartAnsweredKey, "1");
43
+ };
34
44
  // The best speech program found and its English voice, for a short made without a voice key.
35
45
  const systemVoicePick = async () => {
36
46
  const engine = (await speech()).engines[0];
@@ -53,10 +63,16 @@ export function onboardingRoutes(deps) {
53
63
  return new Hono()
54
64
  .get("/", async (c) => {
55
65
  c.header("Cache-Control", "no-store");
56
- return c.json(firstRunView(deps.db, await statuses()));
66
+ return c.json(await view());
67
+ })
68
+ .post("/check", async (c) => {
69
+ // The checklist's Check again: looks for a speech program afresh instead of answering
70
+ // from the last minute's look, so a program installed a moment ago is found.
71
+ forgetDetectedSpeech(deps.probe);
72
+ return c.json(await view());
57
73
  })
58
74
  .post("/dismiss", (c) => {
59
- dismissFirstRun(deps.db, deps.clock.now().toISOString());
75
+ doneWithFirstRun();
60
76
  return c.json({ dismissed: true });
61
77
  })
62
78
  .post("/packs/:id", zValidator("param", packParam, onInvalid), (c) => {
@@ -64,7 +80,7 @@ export function onboardingRoutes(deps) {
64
80
  if (!installed.ok)
65
81
  return problem(c, {
66
82
  status: 404,
67
- detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
83
+ detail: "Slopify doesn't have that starter template. Reload the page and pick one of the templates listed.",
68
84
  });
69
85
  return c.json(installed.value);
70
86
  })
@@ -77,7 +93,7 @@ export function onboardingRoutes(deps) {
77
93
  if (pack === undefined)
78
94
  return problem(c, {
79
95
  status: 404,
80
- detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
96
+ detail: "Slopify doesn't have that starter template. Reload the page and pick one of the templates listed.",
81
97
  });
82
98
  const modelsFor = deps.modelsFor;
83
99
  if (modelsFor === undefined)
@@ -91,6 +107,7 @@ export function onboardingRoutes(deps) {
91
107
  modelsFor,
92
108
  suggested: pack.voice,
93
109
  systemVoice: await systemVoicePick(),
110
+ install: speechInstall(host),
94
111
  });
95
112
  if (!plan.ok)
96
113
  return problem(c, {
@@ -102,7 +119,7 @@ export function onboardingRoutes(deps) {
102
119
  if (!installed.ok)
103
120
  return problem(c, {
104
121
  status: 404,
105
- detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
122
+ detail: "Slopify doesn't have that starter template. Reload the page and pick one of the templates listed.",
106
123
  });
107
124
  // The computer's voice is saved like any other, so Play and a later rebuild offer it.
108
125
  const audio = plan.providers.audio;
@@ -135,7 +152,7 @@ export function onboardingRoutes(deps) {
135
152
  });
136
153
  recordShortRequest(deps.db, input.requestId, created.project.id);
137
154
  // A real project now exists, so the first-run screen is done with.
138
- dismissFirstRun(deps.db, deps.clock.now().toISOString());
155
+ doneWithFirstRun();
139
156
  return c.json({ projectId: created.project.id, replayed: false }, 201);
140
157
  })
141
158
  .post("/full-video", zValidator("json", fullVideoInputSchema, onInvalid), (c) => {
@@ -145,16 +162,16 @@ export function onboardingRoutes(deps) {
145
162
  if (made.reason === "not-found")
146
163
  return problem(c, {
147
164
  status: 404,
148
- detail: "This short or its starter pack is no longer here, so Play couldn't be set up from it. Open Play and pick a template yourself.",
165
+ detail: "This short or its starter template is no longer here, so Create couldn't be set up from it. Open Create and pick a template yourself.",
149
166
  });
150
167
  if (made.reason === "not-short")
151
168
  return problem(c, {
152
169
  status: 409,
153
- detail: "This project is already a long video. Open Play to start another one on a new topic.",
170
+ detail: "This project is already a long video. Open Create to start another one on a new topic.",
154
171
  });
155
172
  return problem(c, {
156
173
  status: 409,
157
- detail: "Slopify couldn't open a Play draft for the full video. Press Make the full video on this topic again; if it keeps failing, open Play and pick the pack's template in Library → Templates.",
174
+ detail: "Slopify couldn't open a draft on Create for the full video. Press Make the full video on this topic again; if it keeps failing, press Make a video on the starter template in Templates.",
158
175
  extensions: { reason: made.reason },
159
176
  });
160
177
  })
@@ -1,5 +1,6 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { addOutputPreviewRequestSchema, addOutputRequestSchema, } from "../../slices/outputs/model.js";
4
5
  import { addOutput, previewAddedOutput, } from "../../slices/outputs/service.js";
5
6
  import { currentRevisionId } from "../../slices/revisions/repo.js";
@@ -21,10 +22,10 @@ const details = {
21
22
  "no-revision": "This saved version of the project no longer exists. Reload the page, then open Add another output again.",
22
23
  conflict: "This project changed while you were choosing. Reload the page, then open Add another output again.",
23
24
  "idempotency-conflict": "This output was already requested with different details. Reload the page, then try again.",
24
- "invalid-edit": "This project is missing a choice the new output needs. Set the fields listed in this project's Settings, save, then add the output again.",
25
+ "invalid-edit": "This project is missing a choice the new output needs. Set the fields listed in Edit settings, save, then add the output again.",
25
26
  already: "This project already makes that output. Find it in its section on the left.",
26
27
  "needs-article": "That output is made from the article, which this project does not have yet. Wait for the Article step to finish, then add it.",
27
- "narration-taken": "This project already has narration, and a project has one. Keep it, or change its format under Speakers in this project's Settings → Providers.",
28
+ "narration-taken": `This project already has narration, and a project has one. Keep it, or change its format under Speakers in ${editSettings.narration}.`,
28
29
  "adaptation-not-accepted": 'This output rewrites your article as a conversation. Tick "Adapt my article into a conversation" in Add another output, then add it.',
29
30
  };
30
31
  // Add another output: what it reuses, makes and costs, then the revision that switches it on.
@@ -3,7 +3,7 @@ import { bundledPatchNotesDir, PatchNotesMissing, readPatchNote, } from "../../s
3
3
  import { markPatchNotesSeen, readPatchNotes } from "../../slices/patch-notes/seen.js";
4
4
  import { problem } from "./problem.js";
5
5
  import { bundleMissing } from "./refusal.js";
6
- // Settings → Patch notes: the list (with which note is due to open by itself), one note's
6
+ // Settings → What's new: the list (with which note is due to open by itself), one note's
7
7
  // Markdown, and recording that the note that opened by itself was closed.
8
8
  // Preserve Hono's inferred route types for the web client.
9
9
  export function patchNotesRoutes(deps) {
@@ -32,7 +32,7 @@ export function patchNotesRoutes(deps) {
32
32
  if (markdown === undefined)
33
33
  return problem(c, {
34
34
  status: 404,
35
- detail: "These patch notes are not in this version of Slopify. Open Settings → Patch notes and pick one from the list.",
35
+ detail: "These patch notes are not in this version of Slopify. Open Settings → What's new and pick one from the list.",
36
36
  });
37
37
  return c.body(markdown, 200, { "content-type": "text/markdown; charset=utf-8" });
38
38
  });
@@ -94,7 +94,7 @@ export function planningRoutes(deps) {
94
94
  if (input.draft.checkpoints?.length)
95
95
  return problem(c, {
96
96
  status: 409,
97
- detail: "Videos with checkpoints turned on cannot be queued. Start them one at a time from Play with Review and start.",
97
+ detail: "Videos that pause for your review (Pauses and checks) can't be queued. Make them one at a time from Create.",
98
98
  });
99
99
  if (batchExists(deps.db, input.requestId))
100
100
  return c.json({ queue: queueEntries(deps.db, input.requestId) });
@@ -18,7 +18,7 @@ export async function createProject(deps, sent) {
18
18
  return {
19
19
  ok: false,
20
20
  status: 409,
21
- detail: "This project has checkpoints turned on, so it must be started from Play with Review and start.",
21
+ detail: "This project pauses for your review (Pauses and checks), so it starts from Create: press Make the video there.",
22
22
  fields: [],
23
23
  };
24
24
  const subtitles = draft.subtitles;
@@ -23,11 +23,11 @@ function refused(c, result) {
23
23
  statuses: { "not-found": 404, conflict: 409, "referenced-by-schedule": 409 },
24
24
  otherwise: 400,
25
25
  detail: result.reason === "referenced-by-schedule"
26
- ? "Some schedules still use this template. In Library > Schedules, cancel and delete them first; their run history is kept."
26
+ ? "Some schedules still use this template. In Calendar → Schedules, cancel and delete them first; their run history is kept."
27
27
  : result.reason === "missing-prompt"
28
28
  ? "A prompt or intro/outro entry this template uses was deleted. Choose another in the template, then save again."
29
29
  : result.reason === "not-found"
30
- ? "This template no longer exists; it may have been deleted. Go to Library > Templates to see your templates."
30
+ ? "This template no longer exists; it may have been deleted. Go to Templates to see your templates."
31
31
  : result.reason === "conflict"
32
32
  ? "This template or its project changed while you were editing. Reload the page to see the latest, then make your change again."
33
33
  : "Some template settings are not valid, for example an image you supplied yourself. Check the template's settings and try again.",
@@ -4,13 +4,15 @@ import { z } from "zod";
4
4
  import { transact } from "../../kernel/db/tx.js";
5
5
  import { derive, progressOf } from "../../kernel/runner/graph.js";
6
6
  import { listProjectHeads } from "../../slices/admission/heads.js";
7
+ import { listingFacts } from "../../slices/admission/listing-facts.js";
7
8
  import { projectById, projectTrashed, runDraftSchema, stageStandingsByProject, stagesOf, stepFailuresByProject, } from "../../slices/admission/repo.js";
8
- import { readsByWaitBesideFailure, stepFailedWhileWaiting, } from "../../slices/admission/state-words.js";
9
+ import { readsByWaitBesideFailure, retryingAt, stepFailedWhileWaiting, } from "../../slices/admission/state-words.js";
9
10
  import { lastStarter } from "../../slices/agents/repo.js";
10
11
  import { defaultChannelId } from "../../slices/channels/model.js";
11
12
  import { channelById, projectChannels, setProjectChannel } from "../../slices/channels/repo.js";
12
13
  import { withProjectControl } from "../../slices/control/lock.js";
13
14
  import { stagesWithEta } from "../../slices/eta/view.js";
15
+ import { sampleProjectIds } from "../../slices/onboarding/sample.js";
14
16
  import { videoActivity } from "../../slices/rebuild/activity.js";
15
17
  import { projectHolds } from "../../slices/reviews/holds.js";
16
18
  import { adoptBaseline } from "../../slices/revisions/adopt.js";
@@ -18,7 +20,7 @@ import { currentRevisionId } from "../../slices/revisions/repo.js";
18
20
  import { limitWaitsByProject, listingWait } from "../../slices/run-cost/panel.js";
19
21
  import { outputsOf } from "../../slices/storage/repo.js";
20
22
  import { trashProject } from "../../slices/trash/service.js";
21
- import { setAsideProjects, uploadedAndSettled, uploadedProjects, } from "../../slices/uploads/repo.js";
23
+ import { setAsideProjects, settledProjects, uploadedProjects } from "../../slices/uploads/repo.js";
22
24
  import { settledStatus } from "../../slices/uploads/rules.js";
23
25
  import { idParam } from "./ids.js";
24
26
  import { onInvalid, problem } from "./problem.js";
@@ -35,7 +37,7 @@ export function projectRoutes(deps) {
35
37
  };
36
38
  const summarise = (project) => {
37
39
  const stages = stagesOf(deps.db, project.id);
38
- const status = settledStatus(derive(stages, project.paused), uploadedAndSettled(deps.db, project.id).has(project.id));
40
+ const status = settledStatus(derive(stages, project.paused), settledProjects(deps.db, project.id).has(project.id));
39
41
  return {
40
42
  ...project,
41
43
  status,
@@ -65,15 +67,20 @@ export function projectRoutes(deps) {
65
67
  const channels = projectChannels(deps.db);
66
68
  const uploads = uploadedProjects(deps.db);
67
69
  const aside = setAsideProjects(deps.db);
68
- const settled = uploadedAndSettled(deps.db);
70
+ const settled = settledProjects(deps.db);
69
71
  const holds = projectHolds(deps.db, { standings, settled });
70
72
  const waits = limitWaitsByProject(deps.db);
71
73
  const failures = stepFailuresByProject(deps.db);
74
+ const samples = new Set(Object.values(sampleProjectIds(deps.db)));
75
+ const facts = listingFacts(deps.db);
72
76
  const projects = listProjectHeads(deps.db).map((project) => {
73
77
  const stages = standings.get(project.id) ?? [];
74
78
  const waiting = waits.get(project.id);
75
79
  const holding = holds.holding.get(project.id);
76
80
  const status = settledStatus(derive(stages, project.paused), settled.has(project.id));
81
+ const made = facts.get(project.id);
82
+ const finished = status === "done" || status === "partial";
83
+ const retry = status === "running" ? retryingAt(stages) : undefined;
77
84
  return {
78
85
  ...project,
79
86
  status,
@@ -89,7 +96,18 @@ export function projectRoutes(deps) {
89
96
  ? { stepFailures: failures.get(project.id) ?? [] }
90
97
  : {}),
91
98
  ...(holding === undefined ? {} : { holding }),
99
+ ...(retry === undefined ? {} : { retryAt: retry }),
92
100
  ...(waiting === undefined ? {} : { limitWaits: waiting.map(listingWait) }),
101
+ ...(samples.has(project.id) ? { sample: true } : {}),
102
+ ...(finished && made?.finishedAt !== undefined ? { finishedAt: made.finishedAt } : {}),
103
+ ...(made?.durationSeconds === undefined
104
+ ? {}
105
+ : { durationSeconds: made.durationSeconds }),
106
+ ...(made?.outputs === undefined ? {} : { outputs: made.outputs }),
107
+ ...(status === "partial" && made?.failedSteps !== undefined
108
+ ? { failedSteps: made.failedSteps }
109
+ : {}),
110
+ ...(made?.outdated === true ? { outdated: true } : {}),
93
111
  };
94
112
  });
95
113
  return c.json({ projects });
@@ -47,7 +47,7 @@ export function promptRoutes(deps) {
47
47
  ? refused(c, { ok: false, reason: "not-found" }, "prompt")
48
48
  : problem(c, {
49
49
  status: 400,
50
- detail: "Only an Image prompt can be marked photorealistic. Open the prompt from the Image tab in Library → Prompts.",
50
+ detail: "Only an Image prompt can be marked photorealistic. Open the prompt under Image in Prompts.",
51
51
  });
52
52
  })
53
53
  // Every saved version, newest first (`slices/library/history.ts`).
@@ -103,7 +103,7 @@ export function refused(c, failure, noun) {
103
103
  case "not-found":
104
104
  return problem(c, {
105
105
  status: 404,
106
- detail: `This ${noun} no longer exists; it may have been deleted. Go back to the Library to pick another.`,
106
+ detail: `This ${noun} no longer exists; it may have been deleted. Go back to Prompts to pick another.`,
107
107
  });
108
108
  }
109
109
  }
@@ -0,0 +1,133 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { zValidator } from "@hono/zod-validator";
3
+ import { Hono } from "hono";
4
+ import { usesPronunciationGlossary } from "../../slices/admission/rules.js";
5
+ import { splitEndMatter } from "../../slices/article/split.js";
6
+ import { pronunciationRulesOf, readsPossessives } from "../../slices/narration/pronunciation.js";
7
+ import { heardNames, namesAhead, namesAheadNotice, namesNarrated, narratedNotice, } from "../../slices/narration/pronunciation-check.js";
8
+ import { wordsSchema } from "../../slices/rebuild/runtime-subtitles.js";
9
+ import { currentRevisionId } from "../../slices/revisions/repo.js";
10
+ import { getRevisionView, isReadyOutput, readyOutput } from "../../slices/revisions/view.js";
11
+ import { outputPath } from "../../slices/storage/layout.js";
12
+ import { projectTitle } from "../../slices/storage/repo.js";
13
+ import { projectIdParam } from "./ids.js";
14
+ import { onInvalid, problem } from "./problem.js";
15
+ function sourcesOf(config, view) {
16
+ const glossary = usesPronunciationGlossary(config) && view.articleMarkdown !== null
17
+ ? splitEndMatter(view.articleMarkdown).glossary
18
+ : "";
19
+ return {
20
+ glossary,
21
+ ...(config.libraryPronunciations === undefined ? {} : { list: config.libraryPronunciations }),
22
+ ...(config.audio?.shareGlossary === true && config.sharedGlossary !== undefined
23
+ ? { shared: config.sharedGlossary }
24
+ : {}),
25
+ ...(config.language === undefined ? {} : { language: config.language }),
26
+ rules: pronunciationRulesOf(config),
27
+ };
28
+ }
29
+ // Every segment of one kind of narration file, in spoken order, joined: the intro, the body and
30
+ // the outro are one piece of narration for counting.
31
+ function narrationText(deps, projectId, view, role) {
32
+ const order = (segment) => segment === "intro" ? 0 : segment === "outro" ? 2 : 1;
33
+ const parts = view.outputs
34
+ .filter((row) => isReadyOutput(row, { role }))
35
+ .toSorted((left, right) => order(left.output.meta.segment) - order(right.output.meta.segment))
36
+ .flatMap((row) => {
37
+ try {
38
+ return [readFileSync(outputPath(deps.paths, projectId, row.output.path), "utf8")];
39
+ }
40
+ catch {
41
+ // A narration file removed by hand has nothing to count.
42
+ return [];
43
+ }
44
+ });
45
+ return parts.length === 0 ? null : parts.join("\n\n");
46
+ }
47
+ function timingWords(deps, projectId, view) {
48
+ const timing = readyOutput(view, { workKey: "subtitles:timing", role: "subtitle_words" });
49
+ if (timing === undefined)
50
+ return null;
51
+ try {
52
+ return wordsSchema.parse(JSON.parse(readFileSync(outputPath(deps.paths, projectId, timing.output.path), "utf8"))).words;
53
+ }
54
+ catch {
55
+ // Timing that is missing or unreadable means there is nothing to say about how it was heard.
56
+ return null;
57
+ }
58
+ }
59
+ export function pronunciationCheckRoutes(deps) {
60
+ const viewOf = (projectId) => {
61
+ const revisionId = currentRevisionId(deps.db, projectId);
62
+ return revisionId === undefined ? undefined : getRevisionView(deps, projectId, revisionId);
63
+ };
64
+ const missing = "This project no longer exists. Go back to Projects to pick another.";
65
+ return (new Hono()
66
+ // Before the narration is paid for: the names this article says that nothing pronounces,
67
+ // so the voice will invent them. Read from the narration text where the run has prepared
68
+ // it, else from the article itself.
69
+ .get("/:projectId/pronunciations/gaps", zValidator("param", projectIdParam, onInvalid), (c) => {
70
+ const { projectId } = c.req.valid("param");
71
+ if (projectTitle(deps.db, projectId) === undefined)
72
+ return problem(c, { status: 404, detail: missing });
73
+ c.header("Cache-Control", "no-store");
74
+ const view = viewOf(projectId);
75
+ if (view === undefined)
76
+ return c.json({
77
+ revisionId: null,
78
+ ready: false,
79
+ narrated: false,
80
+ ahead: null,
81
+ notice: null,
82
+ });
83
+ const config = view.revision.config;
84
+ const written = narrationText(deps, projectId, view, "narration_txt");
85
+ const text = written ??
86
+ (view.articleMarkdown === null ? null : splitEndMatter(view.articleMarkdown).body);
87
+ if (text === null || text.trim() === "")
88
+ return c.json({
89
+ revisionId: view.revision.id,
90
+ ready: false,
91
+ narrated: false,
92
+ ahead: null,
93
+ notice: null,
94
+ });
95
+ const ahead = namesAhead(text, sourcesOf(config, view), readsPossessives(config));
96
+ return c.json({
97
+ revisionId: view.revision.id,
98
+ ready: true,
99
+ narrated: narrationText(deps, projectId, view, "tts_script") !== null,
100
+ ahead,
101
+ notice: namesAheadNotice(ahead),
102
+ });
103
+ })
104
+ // After the render, before the upload: what reached the voice as written, counted from the
105
+ // voice script itself, and how the finished narration was heard saying each name, from the
106
+ // confidence the subtitle timing already carries.
107
+ .get("/:projectId/pronunciations/heard", zValidator("param", projectIdParam, onInvalid), (c) => {
108
+ const { projectId } = c.req.valid("param");
109
+ if (projectTitle(deps.db, projectId) === undefined)
110
+ return problem(c, { status: 404, detail: missing });
111
+ c.header("Cache-Control", "no-store");
112
+ const view = viewOf(projectId);
113
+ if (view === undefined)
114
+ return c.json({
115
+ revisionId: null,
116
+ narrated: null,
117
+ heard: null,
118
+ notice: null,
119
+ });
120
+ const sources = sourcesOf(view.revision.config, view);
121
+ const script = narrationText(deps, projectId, view, "tts_script");
122
+ const written = narrationText(deps, projectId, view, "narration_txt");
123
+ const words = timingWords(deps, projectId, view);
124
+ const narrated = script === null ? null : namesNarrated(script, sources);
125
+ const heard = words === null ? null : heardNames(words, sources, written ?? script ?? undefined);
126
+ return c.json({
127
+ revisionId: view.revision.id,
128
+ narrated,
129
+ heard,
130
+ notice: narratedNotice(narrated, heard),
131
+ });
132
+ }));
133
+ }
@@ -3,7 +3,10 @@ import { Hono } from "hono";
3
3
  import { z } from "zod";
4
4
  import { aliasCountMax } from "../../kernel/ports/narration-aliases.js";
5
5
  import { listNarrationAliases, saveNarrationAliases, } from "../../slices/narration/aliases-library.js";
6
+ import { libraryPronunciationsMax } from "../../slices/narration/pronunciation.js";
7
+ import { readLibraryPronunciations, saveLibraryPronunciations, } from "../../slices/narration/pronunciations-library.js";
6
8
  import { collectSharedGlossary } from "../../slices/narration/shared-glossary.js";
9
+ import { actorOf } from "./actor.js";
7
10
  import { onInvalid, problem } from "./problem.js";
8
11
  // The pronunciations of every project's glossary, merged, for Edit project to copy into a
9
12
  // project that shares them. `except` leaves out the project being edited, whose own glossary
@@ -19,6 +22,29 @@ export function pronunciationRoutes(deps) {
19
22
  .regex(/^[0-9A-Za-z_-]*$/)
20
23
  .optional(),
21
24
  }), onInvalid), (c) => c.json(collectSharedGlossary(deps, c.req.valid("query").except)))
25
+ // Name pronunciations, the other half of the same page: one glossary of your own, whose
26
+ // names come before the glossary each article writes. Saved whole, like the aliases; the
27
+ // reply says what narration makes of it.
28
+ .get("/library", (c) => c.json(readLibraryPronunciations(deps.db)))
29
+ // `version` is the list the save was made from, as the read above gives it. Once a list
30
+ // exists a save without it, or from an older one, is refused with the list as it stands,
31
+ // so a second window or an agent can't quietly replace what somebody wrote.
32
+ .put("/library", zValidator("json", z.object({
33
+ text: z.string().max(libraryPronunciationsMax),
34
+ version: z.string().max(64).optional(),
35
+ }), onInvalid), (c) => {
36
+ const body = c.req.valid("json");
37
+ const result = saveLibraryPronunciations(deps, body.text, body.version, actorOf(c));
38
+ if (result.ok)
39
+ return c.json(result.list);
40
+ if (result.reason === "conflict")
41
+ return problem(c, {
42
+ status: 409,
43
+ detail: result.detail,
44
+ extensions: { current: result.current },
45
+ });
46
+ return problem(c, { status: 400, detail: result.detail });
47
+ })
22
48
  .get("/aliases", (c) => c.json({ aliases: listNarrationAliases(deps.db) }))
23
49
  // The editor saves the whole ordered list at once; each row is checked in the slice, so
24
50
  // the editor gets "Alias 3: …" sentences rather than schema paths.
@@ -5,11 +5,11 @@ import { z } from "zod";
5
5
  import { keyProbes } from "../../adapters/key-probes.js";
6
6
  import { catalogueModelInfo } from "../../catalog/model-info.js";
7
7
  import { videoModelsOf } from "../../catalog/schema.js";
8
- import { defaultSpeechVoice, detectSpeechCached } from "../../kernel/ports/system-speech.js";
8
+ import { defaultSpeechVoice, detectSpeechCached, speechInstall, } from "../../kernel/ports/system-speech.js";
9
9
  import { switchAllRetired, switchRetiredModel } from "../../slices/model-upkeep/switch.js";
10
10
  import { retiredModelUsage, usageKinds, usageSlots } from "../../slices/model-upkeep/usage.js";
11
11
  import { cliPathMaxLength, saveCliPath } from "../../slices/settings/cli-paths.js";
12
- import { dismissFirstRun, firstRunStatus } from "../../slices/settings/first-run.js";
12
+ import { firstRunStatus } from "../../slices/settings/first-run.js";
13
13
  import { checkProviderHealth } from "../../slices/settings/health.js";
14
14
  import { keyGuides } from "../../slices/settings/key-guides.js";
15
15
  import { testProviderKey } from "../../slices/settings/key-test.js";
@@ -63,16 +63,12 @@ export function providerRoutes(deps) {
63
63
  };
64
64
  const standingsOf = (id, models) => deps.modelChecks?.standings(id, models);
65
65
  return (new Hono()
66
- // The step lists Settings → Providers shows beside each key field.
66
+ // The step lists Settings → Keys & services shows beside each key field.
67
67
  .get("/key-guides", (c) => c.json({ guides: keyGuides }))
68
68
  // What the first launch found, and the providers Play picks by default.
69
69
  .get("/first-run", async (c) => {
70
70
  c.header("Cache-Control", "no-store");
71
71
  return c.json(await firstRunStatus({ db: deps.db, ...(deps.modelsFor === undefined ? {} : { modelsFor: deps.modelsFor }) }, await providerStatuses(readiness)));
72
- })
73
- .post("/first-run/dismiss", (c) => {
74
- dismissFirstRun(deps.db);
75
- return c.body(null, 204);
76
72
  })
77
73
  // "Check all": every CLI signed in, every key valid, every chosen model still offered and
78
74
  // answering for the key. `?provider=codex` checks that one only (its row's Check again,
@@ -146,9 +142,11 @@ export function providerRoutes(deps) {
146
142
  // What Settings draws its rails from and Play its dropdowns: every provider, with
147
143
  // the one fact that decides whether it is selectable.
148
144
  .get("/", async (c) => c.json({ providers: await providerStatuses(readiness) }))
149
- // The system voice's speech programs and their voices, for Settings → Voices' picker.
145
+ // The system voice's speech programs and their voices, for Settings → Voices' picker, and
146
+ // with none, how to install one from a terminal (the commands apart from the sentence).
150
147
  .get("/system-voice/voices", async (c) => {
151
- const found = await detectSpeechCached(deps.probe, deps.speechHost ?? process);
148
+ const host = deps.speechHost ?? process;
149
+ const found = await detectSpeechCached(deps.probe, host);
152
150
  return c.json({
153
151
  engines: found.engines.map((engine) => ({
154
152
  id: engine.id,
@@ -157,6 +155,7 @@ export function providerRoutes(deps) {
157
155
  defaultVoice: defaultSpeechVoice(engine)?.id ?? null,
158
156
  })),
159
157
  issue: found.issue ?? null,
158
+ install: found.engines.length === 0 ? (speechInstall(host) ?? null) : null,
160
159
  });
161
160
  })
162
161
  .get("/:id/models", zValidator("param", providerParam, onInvalid), async (c) => {
@@ -228,7 +227,7 @@ export function providerRoutes(deps) {
228
227
  if (candidate !== undefined && !candidate.success)
229
228
  return problem(c, {
230
229
  status: 400,
231
- 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.`,
230
+ 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 → Keys & services → ${providerById(id).displayName}, then choose Test again.`,
232
231
  });
233
232
  return c.json(await testProviderKey({ db: deps.db, clock: deps.clock, fetch: deps.fetch, probes: keyProbes }, id, candidate?.data.key));
234
233
  })
@@ -9,7 +9,7 @@ export function refusal(c, reason, rules) {
9
9
  export function revisionRequired(c) {
10
10
  return problem(c, {
11
11
  status: 409,
12
- detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use Choose what to remake (in the project's More menu).",
12
+ detail: "This change is now made in Edit settings: save it there, then use Choose what to make again (in the project's More menu).",
13
13
  extensions: { reason: "revision-required" },
14
14
  });
15
15
  }
@@ -8,11 +8,11 @@ const body = z.object({
8
8
  enabled: z.boolean(),
9
9
  httpsPort: z.number().int().min(1).max(65535).optional(),
10
10
  });
11
- // Settings → General → Remote access: Slopify's page on the person's other devices through
11
+ // Settings → Remote access: Slopify's page on the person's other devices through
12
12
  // Tailscale serve. Reading it is open to agents (get_remote_access); turning it on or off is
13
13
  // the person's alone, from this computer.
14
14
  export function remoteAccessRoutes(service) {
15
- const unavailable = "Remote access can't be set from this Slopify. Start it with npx @gentbajko/slopify and try Settings → General → Remote access again.";
15
+ const unavailable = "Remote access can't be set from this Slopify. Start it with npx @gentbajko/slopify and try Settings → Remote access again.";
16
16
  return new Hono()
17
17
  .get("/", async (c) => {
18
18
  if (service === undefined)
@@ -27,12 +27,12 @@ export function remoteAccessRoutes(service) {
27
27
  if (actorOf(c) !== "person")
28
28
  return problem(c, {
29
29
  status: 403,
30
- detail: "Remote access is the person's to turn on or off, in Settings → General → Remote access. Agents and the assistant can only read it (get_remote_access).",
30
+ detail: "Remote access is the person's to turn on or off, in Settings → Remote access. Agents and the assistant can only read it (get_remote_access).",
31
31
  });
32
32
  if (viaProxy(c.req.raw.headers))
33
33
  return problem(c, {
34
34
  status: 403,
35
- detail: "Remote access is turned on or off only on the computer Slopify runs on. Open Settings → General → Remote access there, at http://127.0.0.1 and Slopify's port.",
35
+ detail: "Remote access is turned on or off only on the computer Slopify runs on. Open Settings → Remote access there, at http://127.0.0.1 and Slopify's port.",
36
36
  });
37
37
  const { enabled, httpsPort } = c.req.valid("json");
38
38
  return c.json(await service.set(enabled, httpsPort));
@@ -5,12 +5,12 @@ export const hostOnlyWrites = [
5
5
  {
6
6
  method: "PUT|DELETE",
7
7
  path: new RegExp(`^/api/providers/${id}/key/?$`),
8
- what: "Provider keys are saved and removed (Settings → Providers)",
8
+ what: "Provider keys are saved and removed (Settings → Keys & services)",
9
9
  },
10
10
  {
11
11
  method: "PUT",
12
12
  path: new RegExp(`^/api/providers/${id}/path/?$`),
13
- what: "A CLI's path is set (Settings → Providers)",
13
+ what: "A CLI's path is set (Settings → Keys & services)",
14
14
  },
15
15
  {
16
16
  method: "PUT",
@@ -134,7 +134,7 @@ export function reviewRoutes(deps) {
134
134
  const lead = result.reason === "running"
135
135
  ? "This item, or work that depends on it, is still running. Wait for it to finish, or Pause the project, then use Redo again."
136
136
  : result.reason === "readiness"
137
- ? "Slopify can't make this item again yet. Fix what is listed in Settings → Providers or Edit project, then use Redo again."
137
+ ? "Slopify can't make this item again yet. Fix what is listed in Settings → Keys & services or Edit settings, then use Redo again."
138
138
  : result.reason === "conflict" || result.reason === "control-changed"
139
139
  ? "This project changed since the page loaded. Reload the project page, then use Redo again."
140
140
  : result.reason === "invalid-selection"
@@ -80,7 +80,7 @@ function unavailable(c, reason) {
80
80
  return problem(c, {
81
81
  status: 404,
82
82
  detail: reason === "missing-file"
83
- ? "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."
83
+ ? "This file was deleted or moved from the project folder. Use Choose what to make again (in the project's More menu) to make it again."
84
84
  : reason === "no-images"
85
85
  ? "There are no saved files of this kind to put in the zip yet. Wait for the step that makes them to finish, then download again."
86
86
  : "This file is no longer part of the project. Reload the page to see its current files.",
@@ -36,11 +36,11 @@ const details = {
36
36
  conflict: "This project changed while you were editing. Reload the page to see the latest, then make your change again.",
37
37
  "idempotency-conflict": "This change was already sent with different details. Reload the page, then try again.",
38
38
  "invalid-edit": "Some of your changes are not valid. Fix the highlighted fields and save again.",
39
- "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.",
39
+ "stale-preview": "What needs rebuilding changed since you opened this review. Cancel it and open Choose what to make again (in the project's More menu) again to see the latest.",
40
40
  "invalid-selection": "Some of the outputs you picked can no longer be rebuilt. Reload the page and choose again.",
41
- "review-required": 'This rebuild touches content you supplied yourself. Tick "Keep the provided content" for each item in the review, then choose Start rebuild.',
42
- "cost-ack-required": "Some costs for this rebuild are unknown. Tick the box confirming you understand, then choose Start rebuild.",
43
- readiness: "Slopify cannot run this rebuild yet. Fix the problems listed, then try again.",
41
+ "review-required": "This touches content you supplied yourself. Tick “Keep the provided content” for each item in the review, then press the button that makes them again.",
42
+ "cost-ack-required": "Some prices aren't known in advance. Tick “Start even though …” in the review, then press the button that makes them again.",
43
+ readiness: "This can't start yet. Fix the items listed, then try again.",
44
44
  };
45
45
  function refused(c, deps, projectId, value) {
46
46
  return refusal(c, value.reason, {