@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,6 +2,7 @@ import { z } from "zod";
2
2
  import { internalError } from "../../kernel/errors.js";
3
3
  import { redact } from "../../kernel/log.js";
4
4
  import { providerError } from "../../kernel/ports/model.js";
5
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
6
  import { httpFailure, noAudio, providerSaid, refreshList, unreadable, voiceFix, } from "../explain.js";
6
7
  import { failureText, httpProviderError, kindOfStatus, requireKey, safeJson, } from "../http-failure.js";
7
8
  import { modelAccessOf } from "../model-access.js";
@@ -134,7 +135,7 @@ function audioOf(text) {
134
135
  if (blocked)
135
136
  throw providerError({
136
137
  kind: "refusal",
137
- message: providerSaid("Google", "refused to speak this narration under its content rules", blocked, "Reword that part of the script or article, or choose another voice provider under Speakers (Edit project → Providers)."),
138
+ message: providerSaid("Google", "refused to speak this narration under its content rules", blocked, `Reword that part of the script or article, or choose another voice service under Speakers (${editSettings.narration}).`),
138
139
  });
139
140
  for (const candidate of parsed.data.candidates ?? [])
140
141
  for (const part of candidate.content?.parts ?? []) {
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { internalError } from "../../kernel/errors.js";
3
3
  import { redact } from "../../kernel/log.js";
4
4
  import { providerError } from "../../kernel/ports/model.js";
5
+ import { editSettings } from "../../kernel/ports/settings-places.js";
5
6
  import { httpFailure, noAudio, providerSaid, unreadable, voiceFix } from "../explain.js";
6
7
  import { accessField } from "../model-access.js";
7
8
  import { retryAfter } from "../retry-after.js";
@@ -25,7 +26,7 @@ export async function* inworldAsync(deps, request, key) {
25
26
  if (saved === undefined && request.text.length > 100_000) {
26
27
  throw providerError({
27
28
  kind: "unsupported",
28
- message: "This narration part is longer than the 100,000 characters Inworld takes at once (10,000 on On-Demand accounts). Set Chunking to Paragraph in the Providers section of Edit project, then use Try again.",
29
+ message: `This narration part is longer than the 100,000 characters Inworld takes at once (10,000 on On-Demand accounts). Set Chunking to Paragraph in ${editSettings.narration}, then use Try again.`,
29
30
  });
30
31
  }
31
32
  const headers = { Authorization: `Basic ${key}`, "Content-Type": "application/json" };
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { redact } from "../../kernel/log.js";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
5
  import { httpFailure, missingKey, noAudio, unreadable, voiceFix } from "../explain.js";
5
6
  import { accessField } from "../model-access.js";
6
7
  import { retryAfter } from "../retry-after.js";
@@ -70,7 +71,7 @@ async function* synthesizeRequest(deps, request, key) {
70
71
  if (request.text.trim().length === 0)
71
72
  throw providerError({
72
73
  kind: "unsupported",
73
- message: "Inworld was sent an empty piece of narration. Check the article has text in the Article section of Edit project, then use Try again.",
74
+ message: `Inworld was sent an empty piece of narration. Check the article has text in ${editSettings.article}, then use Try again.`,
74
75
  });
75
76
  if ((request.model ?? "inworld-tts-2") === "inworld-tts-2" && request.text.length > 4000) {
76
77
  yield* inworldAsync(deps, request, key);
@@ -79,7 +80,7 @@ async function* synthesizeRequest(deps, request, key) {
79
80
  if (request.text.length > 4000)
80
81
  throw providerError({
81
82
  kind: "unsupported",
82
- message: "This narration part is longer than the 4,000 characters Inworld's Flash model takes at once. In the Providers section of Edit project, set Chunking to Paragraph or a smaller character count, or switch to the Realtime TTS-2 model.",
83
+ message: `This narration part is longer than the 4,000 characters Inworld's Flash model takes at once. In ${editSettings.narration}, set Chunking to Paragraph or a smaller character count, or switch to the Realtime TTS-2 voice model.`,
83
84
  });
84
85
  request.signal.throwIfAborted();
85
86
  const response = await deps.fetch("https://api.inworld.ai/tts/v1/voice:stream", {
@@ -1,6 +1,7 @@
1
1
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { providerError } from "../../kernel/ports/model.js";
4
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
5
  import { defaultSpeechVoice, noSpeechFix, primaryLanguage, } from "../../kernel/ports/system-speech.js";
5
6
  export const systemVoiceId = "system-voice";
6
7
  // Windows speaks through System.Speech. Every input arrives in an environment variable, so no
@@ -48,7 +49,7 @@ export function systemTts(deps) {
48
49
  kind: "unavailable",
49
50
  message: found.engines.length === 0
50
51
  ? (found.issue ?? `No speech program was found on this computer, ${noSpeechFix}`)
51
- : `The speech program "${model}" chosen for this narration isn't on this computer any more. In the Edit tab, choose Edit project and pick another System voice model (${found.engines.map((one) => one.name).join(", ")}) under Providers, then use Continue the run.`,
52
+ : `The speech program "${model}" chosen for this narration isn't on this computer any more. Pick another voice model (${found.engines.map((one) => one.name).join(", ")}) in ${editSettings.narration}, then use Continue the run.`,
52
53
  });
53
54
  };
54
55
  return {
@@ -1,7 +1,14 @@
1
1
  import { thinkingModes } from "../kernel/ports/llm.js";
2
2
  import { providerError } from "../kernel/ports/model.js";
3
+ import { editSettings } from "../kernel/ports/settings-places.js";
3
4
  import { isLocalCliProvider, systemVoiceProvider } from "../slices/settings/model.js";
4
5
  import { editRefusal, referenceRefusal, takesReferenceImage } from "./schema.js";
6
+ // Where a project's settings choose the model of each family.
7
+ const modelPlace = {
8
+ llm: editSettings.writer,
9
+ tts: editSettings.narration,
10
+ image: editSettings.images,
11
+ };
5
12
  export function curateRegistry(registry, catalogue) {
6
13
  const requireModel = (provider, family, id) => {
7
14
  const models = catalogue.models(provider, family);
@@ -9,7 +16,7 @@ export function curateRegistry(registry, catalogue) {
9
16
  if (!model)
10
17
  throw providerError({
11
18
  kind: "unsupported",
12
- message: `The chosen ${provider} model is no longer in Slopify's model list. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.`,
19
+ message: `The chosen ${provider} model is no longer in Slopify's model list. Pick another in ${modelPlace[family]}, then use Continue the run.`,
13
20
  });
14
21
  return model;
15
22
  };
@@ -25,7 +32,7 @@ export function curateRegistry(registry, catalogue) {
25
32
  if (request.model.trim() === "")
26
33
  throw providerError({
27
34
  kind: "unsupported",
28
- message: "No model is chosen for this step. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
35
+ message: `No model is chosen for this step. Pick one in ${editSettings.writer}, then use Continue the run.`,
29
36
  });
30
37
  let models;
31
38
  try {
@@ -39,19 +46,19 @@ export function curateRegistry(registry, catalogue) {
39
46
  if (models !== undefined && selected === undefined)
40
47
  throw providerError({
41
48
  kind: "unsupported",
42
- message: "The chosen model is not available in the AI command-line tool on your computer. Update the tool, or pick another model: in the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
49
+ message: `The chosen model is not available in the AI command-line tool on your computer. Update the tool, or pick another model in ${editSettings.writer}, then use Continue the run.`,
43
50
  });
44
51
  if (request.thinking !== undefined &&
45
52
  selected !== undefined &&
46
53
  !selected.thinkingModes?.includes(request.thinking))
47
54
  throw providerError({
48
55
  kind: "unsupported",
49
- message: "The chosen model does not support this thinking setting. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
56
+ message: `The chosen model does not support this Thinking level. Change it in ${editSettings.writer}, then use Continue the run.`,
50
57
  });
51
58
  if (request.webSearch && !port.capabilities.webSearch)
52
59
  throw providerError({
53
60
  kind: "unsupported",
54
- message: "This command-line tool cannot search the web for research. Choose another text provider or turn Research off in the Edit tab under Edit project, then use Continue the run.",
61
+ message: `This command-line tool cannot search the web for research. Choose another service in ${editSettings.writer}, or set Research source to Off in ${editSettings.article}, then use Continue the run.`,
55
62
  });
56
63
  const thinkingConfig = id === "codex" && request.thinking !== undefined
57
64
  ? { effort: request.thinking === "off" ? "none" : request.thinking }
@@ -80,13 +87,13 @@ export function curateRegistry(registry, catalogue) {
80
87
  if (request.webSearch && !model.llm.webSearch)
81
88
  throw providerError({
82
89
  kind: "unsupported",
83
- message: "The chosen model cannot search the web for research. Choose another model or turn Research off in the Edit tab under Edit project, then use Continue the run.",
90
+ message: `The chosen model cannot search the web for research. Choose another model in ${editSettings.writer}, or set Research source to Off in ${editSettings.article}, then use Continue the run.`,
84
91
  });
85
92
  const thinkingConfig = request.thinking === undefined ? undefined : model.llm.thinking?.[request.thinking];
86
93
  if (request.thinking !== undefined && !thinkingConfig)
87
94
  throw providerError({
88
95
  kind: "unsupported",
89
- message: "The chosen model does not support this thinking setting. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
96
+ message: `The chosen model does not support this Thinking level. Change it in ${editSettings.writer}, then use Continue the run.`,
90
97
  });
91
98
  yield* port.complete({
92
99
  ...request,
@@ -107,14 +114,14 @@ export function curateRegistry(registry, catalogue) {
107
114
  throw providerError({
108
115
  kind: "unsupported",
109
116
  message: request.model === "codex-imagegen"
110
- ? "Image generation is not available in the Codex CLI on your computer. Update Codex CLI, or choose another image provider in the Edit tab under Edit project, then use Continue the run."
111
- : "The chosen Codex model for images is not in the Codex CLI's model list on your computer. Update Codex CLI, or pick another model for images: in the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
117
+ ? `Image generation is not available in the Codex CLI on your computer. Update Codex CLI, or choose another image service in ${editSettings.images}, then use Continue the run.`
118
+ : `The chosen Codex model for images is not in the Codex CLI's model list on your computer. Update Codex CLI, or pick another image model in ${editSettings.images}, then use Continue the run.`,
112
119
  });
113
120
  if (request.thinking !== undefined &&
114
121
  !selected.thinkingModes?.includes(request.thinking))
115
122
  throw providerError({
116
123
  kind: "unsupported",
117
- message: "The chosen Codex model does not support this effort for images. In the Edit tab, choose Edit project and change the images' Effort under Providers, then use Continue the run.",
124
+ message: `The chosen Codex model does not support this effort for images. Change Effort in ${editSettings.images}, then use Continue the run.`,
118
125
  });
119
126
  return port.generate(request);
120
127
  },
@@ -128,12 +135,12 @@ export function curateRegistry(registry, catalogue) {
128
135
  if (!("image" in model) || !model.image.aspectRatios.includes(request.aspect))
129
136
  throw providerError({
130
137
  kind: "unsupported",
131
- message: "The chosen image model cannot make images in this video's shape. In the Edit tab, choose Edit project and change it under Providers, then use Continue the run.",
138
+ message: `The chosen image model cannot make images in this video's shape. Pick another in ${editSettings.images}, then use Continue the run.`,
132
139
  });
133
140
  if (request.thinking !== undefined)
134
141
  throw providerError({
135
142
  kind: "unsupported",
136
- message: "The chosen image model has no effort setting. In the Edit tab, choose Edit project and set the images' Effort back to Model default under Providers, then use Continue the run.",
143
+ message: `The chosen image model has no effort setting. Set Effort back to Model default in ${editSettings.images}, then use Continue the run.`,
137
144
  });
138
145
  if (request.edit !== undefined && !takesReferenceImage(model))
139
146
  throw providerError({ kind: "unsupported", message: editRefusal });
@@ -164,7 +171,7 @@ export function curateRegistry(registry, catalogue) {
164
171
  if (request.text.length > model.tts.maxCharacters)
165
172
  throw providerError({
166
173
  kind: "unsupported",
167
- message: `A piece of narration is longer than this voice model's ${model.tts.maxCharacters}-character limit. Use More → make it again in its section on Audio so Slopify splits it into shorter pieces.`,
174
+ message: `A piece of narration is longer than this voice model's ${model.tts.maxCharacters}-character limit. Press Make the narration again in the project's Narration section, so Slopify splits it into shorter pieces.`,
168
175
  });
169
176
  return port.synthesize({ ...request, model: model.id });
170
177
  },
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { thinkingModes } from "../kernel/ports/llm.js";
3
+ import { createRows, editSettings } from "../kernel/ports/settings-places.js";
3
4
  import { providers } from "../slices/settings/model.js";
4
5
  const money = z.number().finite().nonnegative();
5
6
  const base = z.object({
@@ -105,7 +106,7 @@ export function takesReferenceImage(model) {
105
106
  return model.keywords.includes("reference");
106
107
  }
107
108
  // What the run says when the chosen image model cannot take the establishing image.
108
- export const referenceRefusal = "The chosen image model can't use an establishing image as a reference, so the images can't be drawn to match it. Choose an image model that can (Codex CLI, OpenAI, Google, or a fal.ai model listed with reference) under Images → Model on Play or in Edit project → Providers, or set Establishing image to Off in the Images section.";
109
+ export const referenceRefusal = `The chosen image model can't use an establishing image as a reference, so the images can't be drawn to match it. Choose an image model that can (Codex CLI, OpenAI, Google, or a fal.ai model listed with reference) in ${createRows.images} or ${editSettings.images}, or set Establishing image to Off there.`;
109
110
  // What a design's edit says when the chosen image model takes no input picture.
110
111
  export const editRefusal = "This image model can't edit a picture. Choose Codex CLI, OpenAI, Google, or FLUX.2 or Nano Banana 2 on fal.ai under Made with … · change.";
111
112
  // The image-to-video models a provider offers, as Animate images lists them.
@@ -1,3 +1,4 @@
1
+ import { createRows, editSettings } from "../kernel/ports/settings-places.js";
1
2
  import { usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../slices/admission/rules.js";
2
3
  import { isLocalCliProvider, isUncataloguedProvider } from "../slices/settings/model.js";
3
4
  import { usesAnimation } from "../slices/video/edit-settings.js";
@@ -32,7 +33,9 @@ export function modelFields(draft, catalogue) {
32
33
  },
33
34
  ];
34
35
  for (const { field, family, choice, needed } of checks) {
35
- if (!needed || !choice || isUncataloguedProvider(choice.provider))
36
+ // No model at all is admission's to say ("Choose a Service and a Model under …"): none was
37
+ // chosen, so none is "no longer" in the list.
38
+ if (!needed || !choice || choice.model === "" || isUncataloguedProvider(choice.provider))
36
39
  continue;
37
40
  const model = catalogue.models(choice.provider, family).find((m) => m.id === choice.model);
38
41
  if (!model)
@@ -87,7 +90,7 @@ export function modelFields(draft, catalogue) {
87
90
  if (offered.length === 0)
88
91
  fields.push({
89
92
  field: "videoEdit.animateModel",
90
- message: "This image provider can't animate images. Choose fal.ai or Replicate as the image provider (on Play's Images rail, or in Edit project → Providers), or turn Animate images off.",
93
+ message: `This image service can't animate images. Choose fal.ai or Replicate as the image service (in ${createRows.images}, or ${editSettings.images}), or turn Animate images off.`,
91
94
  });
92
95
  else if (model.trim() !== "" && !offered.some((one) => one.id === model))
93
96
  fields.push({
@@ -96,7 +96,7 @@ export function actionRoutes(deps) {
96
96
  "revision-required": "This page is out of date. Reload the page, then try again.",
97
97
  conflict: "This project changed since the page loaded. Reload the page to see the latest, then try again.",
98
98
  "idempotency-conflict": "This action was already sent with different details. Reload the page, then try again.",
99
- "rebuild-required": "This change needs some outputs to be made again. In the Edit tab, use Choose what to remake (in the project's More menu) to see what will be regenerated and what it costs.",
99
+ "rebuild-required": "This change needs some outputs to be made again. Use Choose what to make again (in the project's More menu) to see what will be made again and what it costs.",
100
100
  };
101
101
  return problem(c, {
102
102
  status: codes[result.reason],
@@ -127,13 +127,13 @@ export function actionRoutes(deps) {
127
127
  "no-revision": "The project's saved settings changed since the page loaded. Reload the page, then try again.",
128
128
  conflict: "This project changed since the page loaded. Reload the page to see the latest, then use Continue the run again.",
129
129
  "idempotency-conflict": "This action was already sent with different details. Reload the page, then try again.",
130
- "invalid-edit": "This section has settings that need fixing first. Open the Edit tab, choose Edit project and correct this section, then try again.",
130
+ "invalid-edit": "This section has settings that need fixing first. Correct this section in Edit settings, then try again.",
131
131
  "stale-preview": "What needs to be made changed since the page loaded. Reload the page, then use Continue the run again.",
132
- "invalid-selection": "This section has nothing generated to re-run because its content was supplied by you. To change it, open the Edit tab and choose Edit project.",
133
- "review-required": "Content you supplied or captions you edited by hand would be replaced. Change them in the Edit tab with Edit project, or use Choose what to remake (in the project's More menu) to confirm the replacement.",
134
- "cost-ack-required": "This re-run needs you to review its cost first. In the Edit tab, use Choose what to remake (in the project's More menu).",
132
+ "invalid-selection": "This section has nothing generated to make again because its content was supplied by you. To change it, use Edit settings.",
133
+ "review-required": "Content you supplied or captions you edited by hand would be replaced. Change them in Edit settings, or use Choose what to make again (in the project's More menu) to confirm the replacement.",
134
+ "cost-ack-required": "Making this again needs you to review its cost first. Use Choose what to make again (in the project's More menu).",
135
135
  readiness: "Slopify cannot run this yet. Check the provider, model, voice and any files you supplied, then use Continue the run.",
136
- running: "This section is still running. Wait for it to finish, or Pause the project, before re-running it.",
136
+ running: "This section is still running. Wait for it to finish, or Pause the project, before making it again.",
137
137
  "accepted-job": "This section has a finished provider job that Slopify has not collected yet.",
138
138
  "control-changed": "Another action on this project (such as Pause or Cancel) happened at the same time. Reload the page to see its current state, then try again.",
139
139
  };
@@ -143,7 +143,7 @@ export function actionRoutes(deps) {
143
143
  ...(result.intentRevisionId === undefined
144
144
  ? []
145
145
  : [
146
- "Your re-run request was saved. Use Continue the run to continue it instead of re-running again.",
146
+ "Your request to make it again was saved. Use Continue the run to go on with it instead of asking again.",
147
147
  ]),
148
148
  ].join(" ");
149
149
  return problem(c, {
@@ -39,6 +39,7 @@ import { problem, problemFromError } from "./problem.js";
39
39
  import { projectTemplateRoutes } from "./project-templates.js";
40
40
  import { projectRoutes } from "./projects.js";
41
41
  import { promptRoutes } from "./prompts.js";
42
+ import { pronunciationCheckRoutes } from "./pronunciation-check.js";
42
43
  import { pronunciationRoutes } from "./pronunciations.js";
43
44
  import { providerRoutes } from "./providers.js";
44
45
  import { remoteAccessRoutes } from "./remote-access.js";
@@ -103,6 +104,7 @@ function apiRoutes(deps, startedAt, runAllowed) {
103
104
  .route("/projects", openFolderRoutes(deps))
104
105
  .route("/projects", audioPreviewRoutes(deps))
105
106
  .route("/projects", narrationPeakRoutes(deps))
107
+ .route("/projects", pronunciationCheckRoutes(deps))
106
108
  .route("/projects", runCostRoutes(deps))
107
109
  .route("/projects", uploadedRoutes(deps))
108
110
  .route("/home", homeRoutes(deps))
@@ -156,7 +158,7 @@ export function createApp(deps) {
156
158
  return next();
157
159
  return problem(c, {
158
160
  status: 403,
159
- detail: "Slopify doesn't answer the public internet: this request came through Tailscale Funnel. On the computer Slopify runs on, run tailscale funnel status to see which port has Funnel, turn it off with tailscale funnel --https=<that port> off, and open Slopify on your other devices through Settings → General → Remote access instead.",
161
+ detail: "Slopify doesn't answer the public internet: this request came through Tailscale Funnel. On the computer Slopify runs on, run tailscale funnel status to see which port has Funnel, turn it off with tailscale funnel --https=<that port> off, and open Slopify on your other devices through Settings → Remote access instead.",
160
162
  });
161
163
  })
162
164
  // Only Slopify's own names, and changes only from its own page (`request-guard.ts`).
@@ -222,7 +224,7 @@ export function createApp(deps) {
222
224
  return next();
223
225
  return problem(c, {
224
226
  status: 409,
225
- detail: "This is the sample project, which is read-only so trying things on it never spends anything. Press Make my own copy at the top of the sample's page, then edit and rebuild the copy.",
227
+ detail: "This is a sample project, which is read-only so trying things on it never spends anything. Press Make my own copy at the top right of its page, then change and make the copy again.",
226
228
  extensions: { reason: "sample-read-only" },
227
229
  });
228
230
  })
@@ -236,7 +238,7 @@ export function createApp(deps) {
236
238
  return next();
237
239
  return problem(c, {
238
240
  status: 409,
239
- detail: `This is the studio sample of ${owner.name}. Change the template in its studio instead: Library → Templates → ${owner.name} → Open studio.`,
241
+ detail: `This is the sample project of the template ${owner.name}. Change the template in Edit template instead: Templates → ${owner.name} → Edit template.`,
240
242
  extensions: { reason: "template-sample" },
241
243
  });
242
244
  })
@@ -48,15 +48,17 @@ runAllowed) {
48
48
  reason: notWired,
49
49
  fix: null,
50
50
  preference: "auto",
51
+ ready: [],
51
52
  session: null,
52
53
  });
53
- const { choice, preference } = await assistant.status();
54
+ const { choice, preference, ready } = await assistant.status();
54
55
  return c.json({
55
56
  available: choice.ok,
56
57
  cli: choice.ok ? choice.cli : null,
57
58
  reason: choice.ok ? null : choice.reason,
58
59
  fix: choice.ok ? null : choice.fix,
59
60
  preference,
61
+ ready,
60
62
  session: assistant.session() ?? null,
61
63
  });
62
64
  })
@@ -3,7 +3,7 @@ import { Hono } from "hono";
3
3
  import { HTTPException } from "hono/http-exception";
4
4
  import { backupConfigInputSchema } from "../../slices/backups/model.js";
5
5
  import { onInvalid, problem } from "./problem.js";
6
- // Settings → Backups: the schedule, the folder, the last result and Back up now. Writing an
6
+ // Settings → Backup & storage: the schedule, the folder, the last result and Back up now. Writing an
7
7
  // archive takes minutes, so Back up now answers at once and the screen polls this view.
8
8
  export function backupRoutes(service) {
9
9
  const backups = () => {
@@ -72,8 +72,8 @@ function refused(c, result, about) {
72
72
  const detail = result.message ??
73
73
  (result.reason === "not-found"
74
74
  ? about === "episode"
75
- ? "This channel or episode summary no longer exists; it may have been deleted in another tab. Reload the channel's Episodes tab."
76
- : "This channel or video title no longer exists; it may have been deleted in another tab. Reload the channel's Existing videos tab."
75
+ ? "This channel or episode summary no longer exists; it may have been deleted in another tab. Reload the channel's Memory tab."
76
+ : "This channel or video title no longer exists; it may have been deleted in another tab. Reload the channel's Titles to avoid tab."
77
77
  : "This request isn't valid. Reload the page and try again.");
78
78
  return refusal(c, result.reason, { statuses: { "not-found": 404 }, otherwise: 400, detail });
79
79
  }
@@ -109,7 +109,7 @@ const details = {
109
109
  "has-templates": "This channel still has templates. Move them to another channel in its Templates tab, or delete them, then delete the channel.",
110
110
  "not-an-image": "This file is not a PNG or JPEG picture. Upload a PNG or JPEG file.",
111
111
  "too-many-images": "This cast member already has the most pictures it can keep. Delete one first.",
112
- "no-image-provider": "Slopify can't make pictures here yet. Set up an image provider in Settings → Providers, or upload a picture instead.",
112
+ "no-image-provider": "Slopify can't make pictures here yet. Set up an image service in Settings → Keys & services, or upload a picture instead.",
113
113
  };
114
114
  function refused(c, result) {
115
115
  return refusal(c, result.reason, {
@@ -1,6 +1,7 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { errorText } from "../../kernel/errors.js";
4
+ import { stageWords } from "../../kernel/pipeline.js";
4
5
  import { projectPaused } from "../../slices/admission/repo.js";
5
6
  import { changeCheckpoints, checkpointChangeSchema, readCheckpointStatus, replayCheckpointApproval, validateCheckpointApproval, } from "../../slices/checkpoints/change.js";
6
7
  import { listCheckpoints } from "../../slices/checkpoints/repo.js";
@@ -21,25 +22,26 @@ function refused(c, reason) {
21
22
  statuses: { "not-found": 404, "invalid-input": 400 },
22
23
  otherwise: 409,
23
24
  detail: reason === "not-found"
24
- ? "This checkpoint no longer exists; the project may have moved on or changed. Reload the page to see where it is now."
25
+ ? "This pause no longer exists; the project may have moved on or changed. Reload the page to see where it is now."
25
26
  : reason === "invalid-input"
26
- ? "This checkpoint answer could not be read. Reload the page and try again."
27
- : "This checkpoint changed since the page loaded, for example it was already approved. Reload the page to see its current state, then try again.",
27
+ ? "This answer to the pause could not be read. Reload the page and try again."
28
+ : "This pause changed since the page loaded, for example it was already approved. Reload the page to see its current state, then try again.",
28
29
  });
29
30
  }
30
31
  // Taking a checkpoint off that holds the run now would start the paid steps behind it without
31
32
  // the review: that goes through Approve, which checks what is approved is what was looked at.
32
33
  function heldThere(c, stage) {
34
+ const name = stageWords[stage];
33
35
  return problem(c, {
34
36
  status: 409,
35
- detail: `The run is waiting at the checkpoint before ${stage}, so it can't be taken off now: that would start the next steps without the review. Look over what is made, then press Approve on the checkpoint (agents: approve_checkpoint).`,
37
+ detail: `The run is waiting for your review before ${name}, so that pause can't be taken off now: that would start the next steps without the review. Look over what is made, then press Approve and make the ${name.toLowerCase()} (agents: approve_checkpoint).`,
36
38
  extensions: { reason: "held-at-checkpoint" },
37
39
  });
38
40
  }
39
41
  function alreadyApproved(c) {
40
42
  return problem(c, {
41
43
  status: 409,
42
- detail: "This checkpoint was already approved, so nothing changed. Reload the page (or call needs_attention) to see what waits now.",
44
+ detail: "This pause was already approved, so nothing changed. Reload the page (or call needs_attention) to see what waits now.",
43
45
  extensions: { reason: "already-approved" },
44
46
  });
45
47
  }
@@ -22,7 +22,7 @@ export const sampleRequestSchema = z
22
22
  const quoteBodySchema = z.object({ document: designDocumentSchema }).strict();
23
23
  const notFoundRefusal = {
24
24
  reason: "not-found",
25
- message: "This design template no longer exists. Open Library → Designs → Templates to choose another.",
25
+ message: "This design template no longer exists. Open Templates → Poster templates to choose another.",
26
26
  };
27
27
  export function designTemplateRoutes(deps) {
28
28
  const unavailable = (c) => problem(c, {
@@ -44,7 +44,7 @@ export function designTemplateRoutes(deps) {
44
44
  return template === undefined
45
45
  ? designRefused(c, {
46
46
  reason: "not-found",
47
- message: "This design template no longer exists. Open Library → Designs → Templates to choose another.",
47
+ message: "This design template no longer exists. Open Templates → Poster templates to choose another.",
48
48
  })
49
49
  : c.json({ ...template, sample: deps.samples?.state?.(template.id) ?? null });
50
50
  })
@@ -90,7 +90,7 @@ export function designTemplateRoutes(deps) {
90
90
  ? c.body(null, 204)
91
91
  : designRefused(c, {
92
92
  reason: "not-found",
93
- message: "This design template no longer exists. Open Library → Designs → Templates to choose another.",
93
+ message: "This design template no longer exists. Open Templates → Poster templates to choose another.",
94
94
  });
95
95
  })
96
96
  .post("/:id/quote", zValidator("param", idParam, onInvalid), async (c) => {
@@ -97,7 +97,7 @@ export function designRoutes(deps) {
97
97
  if (!result.ok)
98
98
  return designRefused(c, {
99
99
  reason: "not-found",
100
- message: "This design no longer exists. Open Library → Designs to choose another.",
100
+ message: "This design no longer exists. Go back to Designs to choose another.",
101
101
  });
102
102
  deps.service.emit?.({ type: "design.changed", designId: id });
103
103
  return c.body(null, 204);
@@ -51,11 +51,11 @@ function refused(c, failure) {
51
51
  case "invalid":
52
52
  return problem(c, {
53
53
  status: 400,
54
- detail: "This theme can't be saved yet. Fix the highlighted settings and try again.",
54
+ detail: "This look can't be saved yet. Fix the highlighted settings and try again.",
55
55
  extensions: { fields: failure.fields },
56
56
  });
57
57
  case "duplicate-name": {
58
- const message = "Another PDF theme already has this name. Choose a different name.";
58
+ const message = "Another PDF look already has this name. Choose a different name.";
59
59
  return problem(c, {
60
60
  status: 409,
61
61
  detail: message,
@@ -65,7 +65,7 @@ function refused(c, failure) {
65
65
  case "not-found":
66
66
  return problem(c, {
67
67
  status: 404,
68
- detail: "This PDF theme no longer exists; it may have been deleted in another tab. Go back to Library → PDF themes.",
68
+ detail: "This PDF look no longer exists; it may have been deleted in another tab. Go back to the PDF looks (Settings → Production defaults → Edit PDF looks).",
69
69
  });
70
70
  }
71
71
  }
@@ -3,7 +3,7 @@ export function draftProblem(c, result) {
3
3
  const { ok: _ok, ...extensions } = result;
4
4
  const details = {
5
5
  "not-found": "This draft no longer exists; it may have been started or deleted. Reload the page to start a new one.",
6
- "invalid-draft": "This saved draft could not be read. Reload the page and set the video up again in Play.",
6
+ "invalid-draft": "This saved draft could not be read. Reload the page and set the video up again on Create.",
7
7
  conflict: "This draft changed in another tab or window. Reload the page to see the latest, then make your change again.",
8
8
  "invalid-edit": "Some settings need fixing. Fix the highlighted fields, then try again.",
9
9
  "pending-start": "This video is already starting. Wait a moment, then find it in Projects.",
@@ -21,7 +21,7 @@ export function extensionAuth(db) {
21
21
  const refused = (c) => problem(c, {
22
22
  status: 401,
23
23
  title: "Unauthorized",
24
- detail: "The Slopify Studio extension isn't paired with this Slopify. Copy the pairing token from Slopify's Settings → YouTube Studio into the extension's options and press Pair.",
24
+ detail: "The Slopify Studio extension isn't paired with this Slopify. Copy the pairing token from Slopify's Settings → YouTube Studio and posting into the extension's options and press Pair.",
25
25
  });
26
26
  return { allowOrigin, allowed, refused };
27
27
  }
@@ -1,10 +1,12 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
+ import { transact } from "../../kernel/db/tx.js";
4
5
  import { snoozeDays } from "../../slices/autopilot/model.js";
5
6
  import { setWeeklyNotify, snoozeWarning, weeklyNotifyOf } from "../../slices/autopilot/repo.js";
6
7
  import { autopilotView } from "../../slices/autopilot/signals.js";
7
8
  import { weekSummary } from "../../slices/run-cost/week.js";
9
+ import { markNotUploaded } from "../../slices/studio/videos.js";
8
10
  import { notReadySentence, runnableTemplate } from "../../slices/template-studio/schedule-gate.js";
9
11
  import { markUploaded, setAside } from "../../slices/uploads/repo.js";
10
12
  import { idParam, idString } from "./ids.js";
@@ -65,7 +67,13 @@ export function uploadedRoutes(deps) {
65
67
  return c.json({ setAside: c.req.valid("json").setAside });
66
68
  })
67
69
  .put("/:id/uploaded", zValidator("param", idParam, onInvalid), zValidator("json", z.object({ uploaded: z.boolean() }).strict(), onInvalid), (c) => {
68
- const result = markUploaded(deps.db, c.req.valid("param").id, c.req.valid("json").uploaded, deps.clock.now().toISOString());
70
+ const { id } = c.req.valid("param");
71
+ const at = deps.clock.now().toISOString();
72
+ // The long video's YouTube link and the mark are one fact (`studio/videos.ts`):
73
+ // Mark not uploaded forgets the link too.
74
+ const result = c.req.valid("json").uploaded
75
+ ? markUploaded(deps.db, id, true, at)
76
+ : transact(deps.db, () => markNotUploaded(deps.db, id, at));
69
77
  if (!result.ok)
70
78
  return problem(c, {
71
79
  status: 404,
@@ -5,7 +5,7 @@ import { errorText } from "../../kernel/errors.js";
5
5
  import { providerFamilies } from "../../kernel/ports/model.js";
6
6
  import { autoModes } from "../../slices/model-checks/model.js";
7
7
  import { accountOf, markNoticesSeen, writeAuto } from "../../slices/model-checks/repo.js";
8
- import { dollarsText } from "../../slices/model-checks/rules.js";
8
+ import { costPhrase, dollarsText } from "../../slices/model-checks/rules.js";
9
9
  import { providerById, providerIds } from "../../slices/settings/model.js";
10
10
  import { onInvalid, problem } from "./problem.js";
11
11
  const providerParam = z.object({ id: z.enum(providerIds) });
@@ -50,8 +50,8 @@ function noAccount(c, id) {
50
50
  return problem(c, {
51
51
  status: 400,
52
52
  detail: auth === "key"
53
- ? `No ${displayName} key is saved, so its models can't be checked. Paste a key in Settings → Providers → ${displayName} and choose Save, then choose Check models in Settings → Models.`
54
- : `${displayName} is not set up, so its models can't be checked. Set it up in Settings → Providers, then choose Check models in Settings → Models.`,
53
+ ? `No ${displayName} key is saved, so its models can't be checked. Paste a key in Settings → Keys & services → ${displayName} and choose Save, then choose Check models in Settings → Models.`
54
+ : `${displayName} is not set up, so its models can't be checked. Set it up in Settings → Keys & services, then choose Check models in Settings → Models.`,
55
55
  });
56
56
  }
57
57
  // The provider-scoped half: its estimate and its Check models button.
@@ -98,7 +98,7 @@ export function providerCheckRoutes(deps) {
98
98
  const estimate = estimateOf(answer.plan);
99
99
  return problem(c, {
100
100
  status: 409,
101
- detail: `Checking ${String(estimate.count)} ${name} model${estimate.count === 1 ? "" : "s"} costs ${estimate.text} at list price, and nothing was sent yet. Choose Check models in Settings → Models again and confirm the estimate to go ahead.`,
101
+ detail: `Checking ${String(estimate.count)} ${name} model${estimate.count === 1 ? "" : "s"} costs ${costPhrase(estimate.totalDollars, estimate.unpriced)}, and nothing was sent yet. Choose Check models in Settings → Models again and confirm the estimate to go ahead.`,
102
102
  extensions: { estimate },
103
103
  });
104
104
  }