@gentbajko/slopify 2.5.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +24 -41
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +31 -0
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +44 -5
  20. package/dist/adapters/host-cli/transport.js +7 -5
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +1 -1
  23. package/dist/adapters/image/codex.js +87 -22
  24. package/dist/adapters/image/fal.js +13 -5
  25. package/dist/adapters/image/google.js +11 -8
  26. package/dist/adapters/image/openai.js +14 -7
  27. package/dist/adapters/image/reference.js +34 -0
  28. package/dist/adapters/image/replicate.js +7 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +85 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex.js +99 -12
  35. package/dist/adapters/llm/document-workspace.js +2 -2
  36. package/dist/adapters/llm/gemini-workspace.js +1 -1
  37. package/dist/adapters/llm/gemini.js +16 -3
  38. package/dist/adapters/llm/image-workspace.js +58 -0
  39. package/dist/adapters/llm/openrouter.js +11 -3
  40. package/dist/adapters/llm/run-cli.js +12 -7
  41. package/dist/adapters/tts/cartesia.js +10 -0
  42. package/dist/adapters/tts/elevenlabs.js +31 -9
  43. package/dist/adapters/tts/gemini.js +206 -0
  44. package/dist/adapters/tts/inworld-async.js +9 -5
  45. package/dist/adapters/tts/inworld.js +17 -3
  46. package/dist/adapters/tts/openai.js +3 -0
  47. package/dist/adapters/tts/pcm-mp3.js +62 -0
  48. package/dist/adapters/tts/system.js +147 -0
  49. package/dist/adapters/tts/voice-languages.js +80 -0
  50. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  55. package/dist/assets/fonts/noto/OFL.txt +93 -0
  56. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  57. package/dist/assets/models.yaml +38 -0
  58. package/dist/assets/sample/sample-audiobook.tar +0 -0
  59. package/dist/assets/sample/sample-podcast.tar +0 -0
  60. package/dist/assets/sample/sample-project.tar +0 -0
  61. package/dist/assets/style-preview/narration.mp3 +0 -0
  62. package/dist/assets/style-preview/tall-1.jpg +0 -0
  63. package/dist/assets/style-preview/tall-2.jpg +0 -0
  64. package/dist/assets/style-preview/tall-3.jpg +0 -0
  65. package/dist/assets/style-preview/wide-1.jpg +0 -0
  66. package/dist/assets/style-preview/wide-2.jpg +0 -0
  67. package/dist/assets/style-preview/wide-3.jpg +0 -0
  68. package/dist/catalog/merge.js +132 -0
  69. package/dist/catalog/registry.js +17 -14
  70. package/dist/catalog/schema.js +2 -0
  71. package/dist/catalog/store.js +147 -8
  72. package/dist/catalog/validate.js +2 -2
  73. package/dist/compose.yaml +71 -0
  74. package/dist/edge/autostart/docker-record.js +32 -0
  75. package/dist/edge/autostart/docker.js +118 -0
  76. package/dist/edge/autostart/index.js +78 -0
  77. package/dist/edge/autostart/launcher.js +176 -0
  78. package/dist/edge/autostart/model.js +3 -0
  79. package/dist/edge/autostart/native.js +210 -0
  80. package/dist/edge/autostart/prompt.js +72 -0
  81. package/dist/edge/autostart/service.js +90 -0
  82. package/dist/edge/cli-args.js +71 -0
  83. package/dist/edge/cli.js +64 -63
  84. package/dist/edge/docker-install/activation.js +56 -0
  85. package/dist/edge/docker-install/apply.js +458 -0
  86. package/dist/edge/docker-install/engine.js +362 -0
  87. package/dist/edge/docker-install/run.js +160 -0
  88. package/dist/edge/docker-install/state.js +146 -0
  89. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  90. package/dist/edge/docker.js +14 -22
  91. package/dist/edge/events/hub.js +3 -1
  92. package/dist/edge/host-cli.js +1 -1
  93. package/dist/edge/http/actions.js +38 -11
  94. package/dist/edge/http/app.js +60 -2
  95. package/dist/edge/http/auditions.js +70 -0
  96. package/dist/edge/http/autostart.js +37 -0
  97. package/dist/edge/http/byte-range.js +42 -0
  98. package/dist/edge/http/channel-memory.js +92 -0
  99. package/dist/edge/http/channels.js +128 -0
  100. package/dist/edge/http/diagnostics.js +1 -0
  101. package/dist/edge/http/draft-files.js +2 -4
  102. package/dist/edge/http/entries.js +22 -3
  103. package/dist/edge/http/files.js +11 -7
  104. package/dist/edge/http/folder-location.js +1 -1
  105. package/dist/edge/http/home.js +39 -0
  106. package/dist/edge/http/host-cli.js +29 -5
  107. package/dist/edge/http/narration-peaks.js +76 -0
  108. package/dist/edge/http/onboarding.js +212 -0
  109. package/dist/edge/http/open-folder.js +1 -1
  110. package/dist/edge/http/patch-notes.js +45 -0
  111. package/dist/edge/http/planning.js +4 -2
  112. package/dist/edge/http/project-create.js +87 -0
  113. package/dist/edge/http/project-templates.js +19 -2
  114. package/dist/edge/http/projects.js +37 -100
  115. package/dist/edge/http/prompts.js +40 -3
  116. package/dist/edge/http/pronunciations.js +23 -4
  117. package/dist/edge/http/providers.js +119 -3
  118. package/dist/edge/http/reviews.js +131 -0
  119. package/dist/edge/http/revision-files.js +3 -5
  120. package/dist/edge/http/revisions.js +1 -1
  121. package/dist/edge/http/run-cost.js +25 -0
  122. package/dist/edge/http/schedules.js +115 -6
  123. package/dist/edge/http/settings.js +84 -6
  124. package/dist/edge/http/storage-files.js +50 -0
  125. package/dist/edge/http/storage.js +43 -2
  126. package/dist/edge/http/studio.js +287 -0
  127. package/dist/edge/http/style-preview.js +61 -0
  128. package/dist/edge/http/subtitles.js +1 -1
  129. package/dist/edge/http/trash.js +69 -0
  130. package/dist/edge/http/tutorials.js +59 -0
  131. package/dist/edge/http/update.js +24 -3
  132. package/dist/edge/http/whats-new.js +15 -0
  133. package/dist/edge/http/youtube-edits.js +92 -0
  134. package/dist/edge/native-update.js +62 -0
  135. package/dist/extension/slopify-studio-chrome.zip +0 -0
  136. package/dist/extension/slopify-studio-firefox.zip +0 -0
  137. package/dist/host-cli/gemini-login.js +83 -0
  138. package/dist/host-cli/open-folder.js +10 -10
  139. package/dist/host-cli/runtime.js +9 -1
  140. package/dist/host-cli/status.js +4 -2
  141. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  142. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  143. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  144. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  145. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  146. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  147. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  148. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  149. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  150. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  151. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  152. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  153. package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
  154. package/dist/kernel/paths.js +27 -6
  155. package/dist/kernel/pipeline.js +4 -1
  156. package/dist/kernel/ports/host-cli.js +122 -14
  157. package/dist/kernel/ports/key-probe.js +1 -0
  158. package/dist/kernel/ports/languages.js +157 -0
  159. package/dist/kernel/ports/model.js +10 -5
  160. package/dist/kernel/ports/narration-aliases.js +60 -0
  161. package/dist/kernel/ports/plan-limits.js +36 -0
  162. package/dist/kernel/ports/system-speech.js +203 -0
  163. package/dist/kernel/runner/attempt.js +17 -8
  164. package/dist/kernel/runner/audition.js +36 -0
  165. package/dist/kernel/runner/graph.js +40 -5
  166. package/dist/kernel/runner/index.js +39 -9
  167. package/dist/kernel/runner/meter.js +1 -0
  168. package/dist/kernel/runner/providers.js +142 -7
  169. package/dist/kernel/runner/retry-policy.js +43 -0
  170. package/dist/kernel/runner/standalone.js +134 -0
  171. package/dist/kernel/runner/work-authority.js +28 -2
  172. package/dist/main.js +277 -16
  173. package/dist/model-catalog.js +2 -0
  174. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  175. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  176. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  177. package/dist/patch-notes/3.0.0.md +70 -0
  178. package/dist/patch-notes/3.0.1.md +105 -0
  179. package/dist/patch-notes/index.json +32 -0
  180. package/dist/slices/admission/model.js +27 -0
  181. package/dist/slices/admission/repo.js +24 -4
  182. package/dist/slices/admission/rules.js +80 -2
  183. package/dist/slices/admission/schema.js +58 -1
  184. package/dist/slices/admission/short-mode.js +47 -0
  185. package/dist/slices/admission/start.js +42 -6
  186. package/dist/slices/article/continuation.js +2 -2
  187. package/dist/slices/article/provided-entries.js +2 -2
  188. package/dist/slices/article/run.js +6 -2
  189. package/dist/slices/article/segments.js +3 -3
  190. package/dist/slices/backups/folder.js +14 -6
  191. package/dist/slices/batch/index.js +7 -2
  192. package/dist/slices/cancel/index.js +1 -1
  193. package/dist/slices/channels/cast-images.js +110 -0
  194. package/dist/slices/channels/cast-match.js +32 -0
  195. package/dist/slices/channels/images.js +19 -0
  196. package/dist/slices/channels/model.js +5 -0
  197. package/dist/slices/channels/rebrand.js +100 -0
  198. package/dist/slices/channels/repo.js +221 -0
  199. package/dist/slices/channels/runs.js +129 -0
  200. package/dist/slices/channels/schema.js +152 -0
  201. package/dist/slices/channels/service.js +180 -0
  202. package/dist/slices/channels/studio-csv.js +110 -0
  203. package/dist/slices/channels/videos.js +120 -0
  204. package/dist/slices/checkpoints/recovery.js +10 -0
  205. package/dist/slices/control/index.js +5 -2
  206. package/dist/slices/control/providers.js +4 -1
  207. package/dist/slices/document/fonts.js +1 -1
  208. package/dist/slices/document/render.js +1 -1
  209. package/dist/slices/episodes/related.js +90 -0
  210. package/dist/slices/episodes/repo.js +60 -0
  211. package/dist/slices/episodes/service.js +58 -0
  212. package/dist/slices/episodes/summarize.js +170 -0
  213. package/dist/slices/estimate/index.js +109 -28
  214. package/dist/slices/estimate/requests.js +68 -2
  215. package/dist/slices/eta/history.js +56 -0
  216. package/dist/slices/eta/model.js +63 -0
  217. package/dist/slices/eta/view.js +25 -0
  218. package/dist/slices/fixes/rules.js +73 -0
  219. package/dist/slices/fonts/coverage.js +82 -0
  220. package/dist/slices/fonts/sfnt.js +74 -0
  221. package/dist/slices/images/run.js +8 -5
  222. package/dist/slices/images/scale.js +84 -0
  223. package/dist/slices/library/diff.js +122 -0
  224. package/dist/slices/library/history.js +100 -0
  225. package/dist/slices/library/model.js +3 -0
  226. package/dist/slices/library/photorealistic.js +49 -0
  227. package/dist/slices/library/repo.js +19 -14
  228. package/dist/slices/library/save.js +62 -12
  229. package/dist/slices/library/slots.js +7 -1
  230. package/dist/slices/library/used-by.js +153 -0
  231. package/dist/slices/loudness/level-pieces.js +41 -0
  232. package/dist/slices/loudness/loudnorm.js +164 -0
  233. package/dist/slices/loudness/model.js +153 -0
  234. package/dist/slices/model-upkeep/model.js +10 -0
  235. package/dist/slices/model-upkeep/switch.js +217 -0
  236. package/dist/slices/model-upkeep/usage.js +185 -0
  237. package/dist/slices/narration/aliases-library.js +31 -0
  238. package/dist/slices/narration/aliases-schema.js +53 -0
  239. package/dist/slices/narration/aliases.js +49 -0
  240. package/dist/slices/narration/blocks.js +352 -0
  241. package/dist/slices/narration/chunk.js +19 -7
  242. package/dist/slices/narration/concat.js +2 -2
  243. package/dist/slices/narration/describe.js +45 -0
  244. package/dist/slices/narration/pauses-model.js +63 -0
  245. package/dist/slices/narration/pauses.js +269 -0
  246. package/dist/slices/narration/peaks-model.js +1 -0
  247. package/dist/slices/narration/peaks.js +94 -0
  248. package/dist/slices/narration/preparation.js +21 -3
  249. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  250. package/dist/slices/narration/pronunciation.js +72 -25
  251. package/dist/slices/narration/run.js +6 -6
  252. package/dist/slices/narration/shared-glossary.js +1 -1
  253. package/dist/slices/narration/steering.js +15 -4
  254. package/dist/slices/notifications/notifier.js +47 -3
  255. package/dist/slices/notifications/rules.js +48 -3
  256. package/dist/slices/onboarding/copy.js +143 -0
  257. package/dist/slices/onboarding/first-run.js +72 -0
  258. package/dist/slices/onboarding/full-video.js +81 -0
  259. package/dist/slices/onboarding/install.js +119 -0
  260. package/dist/slices/onboarding/model.js +85 -0
  261. package/dist/slices/onboarding/packs.js +491 -0
  262. package/dist/slices/onboarding/quick-short.js +144 -0
  263. package/dist/slices/onboarding/sample.js +132 -0
  264. package/dist/slices/onboarding/state.js +68 -0
  265. package/dist/slices/onboarding/template.js +73 -0
  266. package/dist/slices/patch-notes/library.js +73 -0
  267. package/dist/slices/patch-notes/seen.js +77 -0
  268. package/dist/slices/play-drafts/convert.js +127 -8
  269. package/dist/slices/play-drafts/readiness.js +4 -1
  270. package/dist/slices/play-drafts/repo.js +2 -0
  271. package/dist/slices/play-drafts/review-inputs.js +20 -3
  272. package/dist/slices/play-drafts/schema.js +89 -0
  273. package/dist/slices/play-drafts/service.js +4 -1
  274. package/dist/slices/play-drafts/start.js +43 -18
  275. package/dist/slices/project-templates/from-project.js +59 -6
  276. package/dist/slices/project-templates/next-chapter.js +94 -0
  277. package/dist/slices/project-templates/one-off.js +26 -0
  278. package/dist/slices/project-templates/repo.js +2 -1
  279. package/dist/slices/project-templates/schema.js +7 -1
  280. package/dist/slices/project-templates/service.js +15 -4
  281. package/dist/slices/project-templates/setup.js +18 -2
  282. package/dist/slices/rebuild/admission-repo.js +1 -1
  283. package/dist/slices/rebuild/model.js +6 -0
  284. package/dist/slices/rebuild/preview-details.js +46 -3
  285. package/dist/slices/rebuild/preview-plan.js +13 -6
  286. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  287. package/dist/slices/rebuild/recipe-audio.js +66 -12
  288. package/dist/slices/rebuild/recipe-build.js +8 -4
  289. package/dist/slices/rebuild/recipe-cast.js +18 -0
  290. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  291. package/dist/slices/rebuild/recipe-describe.js +93 -0
  292. package/dist/slices/rebuild/recipe-edit.js +13 -1
  293. package/dist/slices/rebuild/recipe-exports.js +67 -8
  294. package/dist/slices/rebuild/recipe-input-schema.js +35 -0
  295. package/dist/slices/rebuild/recipe-legacy.js +9 -0
  296. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  297. package/dist/slices/rebuild/recipe-model.js +25 -6
  298. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  299. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  300. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  301. package/dist/slices/rebuild/recipe-reference.js +15 -1
  302. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  303. package/dist/slices/rebuild/recipe-save.js +4 -1
  304. package/dist/slices/rebuild/recipe-shorts.js +23 -3
  305. package/dist/slices/rebuild/recipe-text.js +114 -19
  306. package/dist/slices/rebuild/recipe-validation.js +14 -2
  307. package/dist/slices/rebuild/recipe-visual.js +96 -33
  308. package/dist/slices/rebuild/recipe-voices.js +286 -0
  309. package/dist/slices/rebuild/recipe-work.js +77 -6
  310. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  311. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  312. package/dist/slices/rebuild/recovery-model.js +3 -0
  313. package/dist/slices/rebuild/recovery-selection.js +28 -2
  314. package/dist/slices/rebuild/recovery.js +35 -17
  315. package/dist/slices/rebuild/repo.js +3 -3
  316. package/dist/slices/rebuild/retry.js +18 -0
  317. package/dist/slices/rebuild/review-redo.js +125 -0
  318. package/dist/slices/rebuild/runtime-actions.js +5 -0
  319. package/dist/slices/rebuild/runtime-admission.js +4 -1
  320. package/dist/slices/rebuild/runtime-animate.js +4 -4
  321. package/dist/slices/rebuild/runtime-article.js +5 -5
  322. package/dist/slices/rebuild/runtime-document.js +4 -4
  323. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  324. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  325. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  326. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  327. package/dist/slices/rebuild/runtime-export.js +84 -19
  328. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  329. package/dist/slices/rebuild/runtime-image.js +21 -2
  330. package/dist/slices/rebuild/runtime-local.js +148 -24
  331. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  332. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  333. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  334. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  335. package/dist/slices/rebuild/runtime-piece-label.js +27 -0
  336. package/dist/slices/rebuild/runtime-provider.js +126 -18
  337. package/dist/slices/rebuild/runtime-publication.js +21 -5
  338. package/dist/slices/rebuild/runtime-review.js +267 -0
  339. package/dist/slices/rebuild/runtime-run.js +17 -11
  340. package/dist/slices/rebuild/runtime-shorts.js +44 -17
  341. package/dist/slices/rebuild/runtime-store.js +62 -11
  342. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  343. package/dist/slices/rebuild/runtime-voices.js +103 -0
  344. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  345. package/dist/slices/rebuild/service-readiness.js +3 -3
  346. package/dist/slices/rebuild/soften.js +49 -0
  347. package/dist/slices/rebuild/transition-repo.js +9 -2
  348. package/dist/slices/research/run.js +4 -4
  349. package/dist/slices/research/synthesis.js +2 -2
  350. package/dist/slices/reviews/model.js +105 -0
  351. package/dist/slices/reviews/outcome.js +22 -0
  352. package/dist/slices/reviews/repo.js +118 -0
  353. package/dist/slices/reviews/rules.js +63 -0
  354. package/dist/slices/reviews/verdict.js +71 -0
  355. package/dist/slices/reviews/view.js +30 -0
  356. package/dist/slices/revisions/adopt-content.js +5 -2
  357. package/dist/slices/revisions/adopt.js +3 -1
  358. package/dist/slices/revisions/downloads.js +7 -1
  359. package/dist/slices/revisions/image-plan.js +183 -0
  360. package/dist/slices/revisions/mutation-assets.js +10 -0
  361. package/dist/slices/revisions/mutation-cues.js +71 -0
  362. package/dist/slices/revisions/mutation-request.js +2 -1
  363. package/dist/slices/revisions/mutations.js +97 -3
  364. package/dist/slices/revisions/schema.js +11 -1
  365. package/dist/slices/run-cost/limits.js +128 -0
  366. package/dist/slices/run-cost/meter.js +47 -0
  367. package/dist/slices/run-cost/panel.js +256 -0
  368. package/dist/slices/run-cost/pricing.js +95 -0
  369. package/dist/slices/run-cost/week.js +108 -0
  370. package/dist/slices/schedules/agenda.js +222 -0
  371. package/dist/slices/schedules/repo.js +58 -18
  372. package/dist/slices/schedules/scheduler.js +68 -17
  373. package/dist/slices/schedules/schema.js +179 -1
  374. package/dist/slices/schedules/service.js +43 -4
  375. package/dist/slices/schedules/similar.js +128 -0
  376. package/dist/slices/schedules/topic-list.js +163 -0
  377. package/dist/slices/schedules/topics.js +523 -0
  378. package/dist/slices/settings/first-run.js +106 -0
  379. package/dist/slices/settings/health.js +247 -0
  380. package/dist/slices/settings/key-guides.js +138 -0
  381. package/dist/slices/settings/key-test.js +93 -0
  382. package/dist/slices/settings/keys.js +10 -5
  383. package/dist/slices/settings/model-reach.js +52 -0
  384. package/dist/slices/settings/model.js +55 -1
  385. package/dist/slices/settings/models.js +5 -1
  386. package/dist/slices/settings/playback.js +15 -0
  387. package/dist/slices/settings/readiness.js +14 -2
  388. package/dist/slices/settings/repo.js +17 -3
  389. package/dist/slices/settings/tutorial-schema.js +5 -0
  390. package/dist/slices/settings/voices.js +64 -4
  391. package/dist/slices/settings/whats-new.js +64 -0
  392. package/dist/slices/shorts/captions.js +9 -0
  393. package/dist/slices/shorts/pick.js +3 -3
  394. package/dist/slices/shorts/prompts.js +1 -1
  395. package/dist/slices/shorts/render.js +63 -13
  396. package/dist/slices/storage/asset-name.js +3 -0
  397. package/dist/slices/storage/backup-export.js +48 -9
  398. package/dist/slices/storage/backup-format.js +38 -1
  399. package/dist/slices/storage/backup-import.js +191 -19
  400. package/dist/slices/storage/delete-project.js +7 -4
  401. package/dist/slices/storage/documents.js +87 -0
  402. package/dist/slices/storage/downloads.js +33 -1
  403. package/dist/slices/storage/files-location.js +531 -0
  404. package/dist/slices/storage/layout.js +14 -4
  405. package/dist/slices/storage/model.js +11 -0
  406. package/dist/slices/storage/portable.js +94 -12
  407. package/dist/slices/storage/reconcile.js +14 -1
  408. package/dist/slices/storage/repo.js +4 -1
  409. package/dist/slices/storage/schema.js +11 -0
  410. package/dist/slices/storage/trim.js +146 -0
  411. package/dist/slices/studio/disclosure.js +74 -0
  412. package/dist/slices/studio/model.js +30 -0
  413. package/dist/slices/studio/pack.js +211 -0
  414. package/dist/slices/studio/queue.js +69 -0
  415. package/dist/slices/studio/settings.js +170 -0
  416. package/dist/slices/style-preview/images.js +61 -0
  417. package/dist/slices/style-preview/narration.js +33 -0
  418. package/dist/slices/style-preview/render.js +148 -0
  419. package/dist/slices/style-preview/schema.js +59 -0
  420. package/dist/slices/style-preview/service.js +92 -0
  421. package/dist/slices/style-preview/settings.js +139 -0
  422. package/dist/slices/subtitles/captions.js +42 -9
  423. package/dist/slices/subtitles/model.js +22 -0
  424. package/dist/slices/subtitles/prepare.js +27 -16
  425. package/dist/slices/subtitles/transcript.js +1 -1
  426. package/dist/slices/thumbnail/run.js +7 -7
  427. package/dist/slices/trash/model.js +25 -0
  428. package/dist/slices/trash/service.js +273 -0
  429. package/dist/slices/tutorials/anchors.js +57 -0
  430. package/dist/slices/tutorials/library.js +185 -0
  431. package/dist/slices/uploads/repo.js +20 -0
  432. package/dist/slices/video/ambient-bed-schema.js +15 -0
  433. package/dist/slices/video/ambient-bed.js +95 -0
  434. package/dist/slices/video/ambient-mix.js +72 -0
  435. package/dist/slices/video/audio-inputs.js +2 -2
  436. package/dist/slices/video/cards.js +5 -2
  437. package/dist/slices/video/edit-list.js +23 -2
  438. package/dist/slices/video/edit-settings.js +13 -3
  439. package/dist/slices/video/ffmpeg.js +63 -9
  440. package/dist/slices/video/figure-card.js +492 -0
  441. package/dist/slices/video/figure-spans.js +51 -0
  442. package/dist/slices/video/plan.js +125 -13
  443. package/dist/slices/video/run.js +14 -12
  444. package/dist/slices/video/slideshow.js +39 -4
  445. package/dist/slices/voices/attribution.js +69 -0
  446. package/dist/slices/voices/audio-files.js +87 -0
  447. package/dist/slices/voices/audition.js +25 -0
  448. package/dist/slices/voices/cast.js +74 -0
  449. package/dist/slices/voices/grouping.js +59 -0
  450. package/dist/slices/voices/join.js +35 -0
  451. package/dist/slices/voices/languages.js +25 -0
  452. package/dist/slices/voices/model.js +218 -0
  453. package/dist/slices/voices/palette.js +26 -0
  454. package/dist/slices/voices/panel.js +91 -0
  455. package/dist/slices/voices/portraits.js +55 -0
  456. package/dist/slices/voices/script.js +136 -0
  457. package/dist/slices/voices/timing.js +58 -0
  458. package/dist/slices/youtube/answer.js +1 -1
  459. package/dist/slices/youtube/chapters.js +126 -0
  460. package/dist/slices/youtube/edits-repo.js +84 -0
  461. package/dist/slices/youtube/edits.js +89 -0
  462. package/dist/slices/youtube/placeholders.js +98 -0
  463. package/dist/slices/youtube/transcript.js +11 -4
  464. package/dist/tutorials/AI-CLIs.md +146 -0
  465. package/dist/tutorials/Backups.md +121 -0
  466. package/dist/tutorials/Calendar.md +103 -0
  467. package/dist/tutorials/Cast-Library.md +112 -0
  468. package/dist/tutorials/Channels.md +148 -0
  469. package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
  470. package/dist/tutorials/Docker.md +171 -0
  471. package/dist/tutorials/Document-Themes.md +207 -0
  472. package/dist/tutorials/Editing-a-Project.md +204 -0
  473. package/dist/tutorials/FAQ.md +134 -0
  474. package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
  475. package/dist/tutorials/Home-and-Projects.md +174 -0
  476. package/dist/tutorials/Home.md +119 -0
  477. package/dist/tutorials/Install.md +154 -0
  478. package/dist/tutorials/Intros-and-Outros.md +86 -0
  479. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
  480. package/dist/tutorials/Library-Overview.md +75 -0
  481. package/dist/tutorials/Models.md +133 -0
  482. package/dist/tutorials/Multiple-Voices.md +161 -0
  483. package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
  484. package/dist/tutorials/Notifications.md +86 -0
  485. package/dist/tutorials/Other-Languages.md +110 -0
  486. package/dist/tutorials/PDF-Documents.md +100 -0
  487. package/dist/tutorials/Play-Images.md +135 -0
  488. package/dist/tutorials/Play-Narration.md +167 -0
  489. package/dist/tutorials/Play-Outputs.md +109 -0
  490. package/dist/tutorials/Play-Overview.md +216 -0
  491. package/dist/tutorials/Play-Title-and-Article.md +135 -0
  492. package/dist/tutorials/Play-Video-and-Style.md +143 -0
  493. package/dist/tutorials/Project-Page.md +228 -0
  494. package/dist/tutorials/Prompts.md +136 -0
  495. package/dist/tutorials/Providers-and-Keys.md +290 -0
  496. package/dist/tutorials/Publishing-to-YouTube.md +135 -0
  497. package/dist/tutorials/Recovery-and-Retries.md +141 -0
  498. package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
  499. package/dist/tutorials/Schedules.md +154 -0
  500. package/dist/tutorials/Settings-Reference.md +258 -0
  501. package/dist/tutorials/Shorts.md +116 -0
  502. package/dist/tutorials/Start-at-Login.md +126 -0
  503. package/dist/tutorials/Studio-Extension.md +146 -0
  504. package/dist/tutorials/Templates.md +146 -0
  505. package/dist/tutorials/Trash.md +87 -0
  506. package/dist/tutorials/Troubleshooting.md +149 -0
  507. package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
  508. package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
  509. package/dist/tutorials/Video-Editing.md +135 -0
  510. package/dist/tutorials/Where-Your-Files-Live.md +149 -0
  511. package/dist/tutorials/YouTube-Description.md +132 -0
  512. package/dist/tutorials/Your-First-Short.md +125 -0
  513. package/dist/tutorials/_Footer.md +1 -0
  514. package/dist/tutorials/_Sidebar.md +69 -0
  515. package/dist/updater/service.js +66 -21
  516. package/dist/web/assets/index-DBLSfqA-.css +1 -0
  517. package/dist/web/assets/index-DFdIhmm0.js +307 -0
  518. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  519. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  520. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  521. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  522. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  523. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  524. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  525. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  526. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  527. package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
  528. package/dist/web/index.html +2 -2
  529. package/package.json +5 -5
  530. package/dist/edge/docker-launch.js +0 -81
  531. package/dist/edge/docker-projects/activation.js +0 -35
  532. package/dist/edge/docker-projects/claims.js +0 -85
  533. package/dist/edge/docker-projects/committed.js +0 -40
  534. package/dist/edge/docker-projects/engine.js +0 -461
  535. package/dist/edge/docker-projects/install.js +0 -254
  536. package/dist/edge/docker-projects/prune.js +0 -66
  537. package/dist/edge/docker-projects/recover.js +0 -100
  538. package/dist/edge/docker-projects/state.js +0 -225
  539. package/dist/web/assets/index-CktpxlJd.js +0 -171
  540. package/dist/web/assets/index-DB-vOnGU.css +0 -1
  541. package/scripts/docker-run.sh +0 -3
  542. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -0,0 +1,122 @@
1
+ // A word-level diff for History's side-by-side view. Browser-safe: the Library screen diffs the
2
+ // two versions it already holds. Words and the whitespace between them are the units, so a
3
+ // changed word is marked on its own and line breaks survive for the view to show.
4
+ // Past this many changed words the two texts are too different for a word diff to help, and
5
+ // the Myers trace would grow with it: the view shows the one removed and the other added.
6
+ const maxEdits = 4000;
7
+ export function tokens(text) {
8
+ return text.match(/\s+|[^\s]+/gu) ?? [];
9
+ }
10
+ // The parts of both texts in order. `same` parts belong to both sides, `removed` to the older
11
+ // text only and `added` to the newer only; neighbours of one kind are joined.
12
+ export function wordDiff(before, after) {
13
+ const a = tokens(before);
14
+ const b = tokens(after);
15
+ // The common head and tail are cut first: most edits touch a small part of a long prompt.
16
+ let head = 0;
17
+ while (head < a.length && head < b.length && a[head] === b[head])
18
+ head += 1;
19
+ let tail = 0;
20
+ while (tail < a.length - head &&
21
+ tail < b.length - head &&
22
+ a[a.length - 1 - tail] === b[b.length - 1 - tail])
23
+ tail += 1;
24
+ const parts = [];
25
+ push(parts, "same", a.slice(0, head).join(""));
26
+ const middleA = a.slice(head, a.length - tail);
27
+ const middleB = b.slice(head, b.length - tail);
28
+ for (const part of myers(middleA, middleB))
29
+ push(parts, part.op, part.text);
30
+ push(parts, "same", a.slice(a.length - tail).join(""));
31
+ return parts;
32
+ }
33
+ // One side of the side-by-side view: the older text keeps what was removed, the newer what
34
+ // was added.
35
+ export function sideOf(parts, side) {
36
+ const hidden = side === "before" ? "added" : "removed";
37
+ return parts.filter((part) => part.op !== hidden);
38
+ }
39
+ export function diffCounts(parts) {
40
+ const words = (op) => parts
41
+ .filter((part) => part.op === op)
42
+ .reduce((sum, part) => sum + tokens(part.text).filter((one) => one.trim() !== "").length, 0);
43
+ return { added: words("added"), removed: words("removed") };
44
+ }
45
+ function push(parts, op, text) {
46
+ if (text === "")
47
+ return;
48
+ const last = parts.at(-1);
49
+ if (last !== undefined && last.op === op)
50
+ parts[parts.length - 1] = { op, text: last.text + text };
51
+ else
52
+ parts.push({ op, text });
53
+ }
54
+ // Myers' O((N+M)D) shortest edit script, walked back from the end through the saved frontiers.
55
+ function myers(a, b) {
56
+ if (a.length === 0 && b.length === 0)
57
+ return [];
58
+ if (a.length === 0)
59
+ return [{ op: "added", text: b.join("") }];
60
+ if (b.length === 0)
61
+ return [{ op: "removed", text: a.join("") }];
62
+ const n = a.length;
63
+ const m = b.length;
64
+ const max = Math.min(n + m, maxEdits);
65
+ const offset = max + 1;
66
+ let v = new Int32Array(2 * max + 3);
67
+ const trace = [];
68
+ let found = false;
69
+ for (let d = 0; d <= max && !found; d += 1) {
70
+ trace.push(v.slice());
71
+ const next = v.slice();
72
+ for (let k = -d; k <= d; k += 2) {
73
+ const down = k === -d || (k !== d && (v[offset + k - 1] ?? 0) < (v[offset + k + 1] ?? 0));
74
+ let x = down ? (v[offset + k + 1] ?? 0) : (v[offset + k - 1] ?? 0) + 1;
75
+ let y = x - k;
76
+ while (x < n && y < m && a[x] === b[y]) {
77
+ x += 1;
78
+ y += 1;
79
+ }
80
+ next[offset + k] = x;
81
+ if (x >= n && y >= m) {
82
+ found = true;
83
+ break;
84
+ }
85
+ }
86
+ v = next;
87
+ }
88
+ if (!found)
89
+ return [
90
+ { op: "removed", text: a.join("") },
91
+ { op: "added", text: b.join("") },
92
+ ];
93
+ // Backtrack: each frontier in `trace` is the state before step d.
94
+ const script = [];
95
+ let x = n;
96
+ let y = m;
97
+ for (let d = trace.length - 1; d >= 0; d -= 1) {
98
+ const before = trace[d] ?? new Int32Array(0);
99
+ const k = x - y;
100
+ const down = k === -d || (k !== d && (before[offset + k - 1] ?? 0) < (before[offset + k + 1] ?? 0));
101
+ const previousK = down ? k + 1 : k - 1;
102
+ const previousX = before[offset + previousK] ?? 0;
103
+ const previousY = previousX - previousK;
104
+ while (x > previousX && y > previousY) {
105
+ script.push({ op: "same", text: a[x - 1] ?? "" });
106
+ x -= 1;
107
+ y -= 1;
108
+ }
109
+ if (d > 0) {
110
+ if (down)
111
+ script.push({ op: "added", text: b[y - 1] ?? "" });
112
+ else
113
+ script.push({ op: "removed", text: a[x - 1] ?? "" });
114
+ x = previousX;
115
+ y = previousY;
116
+ }
117
+ }
118
+ const parts = [];
119
+ for (const part of script.reverse())
120
+ push(parts, part.op, part.text);
121
+ return parts;
122
+ }
@@ -0,0 +1,100 @@
1
+ // Prompt history: every save of a Library prompt or intro/outro keeps a version, and History
2
+ // lists, compares and restores them. A version is the whole saved row - name, body, kind and
3
+ // mode - so restoring one puts back exactly what was saved then.
4
+ import { z } from "zod";
5
+ import { entryModes } from "./model.js";
6
+ export const libraryItemKinds = ["prompt", "entry"];
7
+ // Saves come from this one-user app, so every version is the user's. The column exists so a
8
+ // version written by something else (an import, a future automatic update) can say so.
9
+ export const versionAuthor = "you";
10
+ const versionRow = z.object({
11
+ version: z.number().int().positive(),
12
+ kind: z.string(),
13
+ mode: z.enum(entryModes).nullable(),
14
+ name: z.string(),
15
+ body: z.string(),
16
+ author: z.string(),
17
+ restored_from: z.number().int().nullable(),
18
+ created_at: z.string(),
19
+ });
20
+ function kindOf(item) {
21
+ return "kind" in item ? item.kind : item.category;
22
+ }
23
+ function modeOf(item) {
24
+ return "mode" in item ? item.mode : null;
25
+ }
26
+ // Newest first: History opens on what is saved now.
27
+ export function listVersions(db, itemKind, itemId) {
28
+ return db
29
+ .prepare("SELECT version,kind,mode,name,body,author,restored_from,created_at FROM library_versions WHERE item_kind=? AND item_id=? ORDER BY version DESC")
30
+ .all(itemKind, itemId)
31
+ .map((row) => toVersion(versionRow.parse(row)));
32
+ }
33
+ export function versionOf(db, itemKind, itemId, version) {
34
+ const row = db
35
+ .prepare("SELECT version,kind,mode,name,body,author,restored_from,created_at FROM library_versions WHERE item_kind=? AND item_id=? AND version=?")
36
+ .get(itemKind, itemId, version);
37
+ return row === undefined ? undefined : toVersion(versionRow.parse(row));
38
+ }
39
+ // Records the row as saved. A save that changed nothing (the editor's Save pressed twice, a
40
+ // restore of the version already current) adds no version, so History lists real changes only.
41
+ // A row with no versions at all - one a backup or a portable import wrote straight into the
42
+ // table - gets its saved text as version 1 before the new one, so History never starts from
43
+ // a version the user never saw.
44
+ export function recordVersion(db, itemKind, item, options = {}) {
45
+ const latest = listVersions(db, itemKind, item.id)[0];
46
+ let next = (latest?.version ?? 0) + 1;
47
+ if (latest === undefined && options.previous !== undefined && !same(options.previous, item)) {
48
+ insert(db, itemKind, options.previous, 1, null, options.previous.updatedAt);
49
+ next = 2;
50
+ }
51
+ if (latest !== undefined && sameAsVersion(latest, item))
52
+ return;
53
+ insert(db, itemKind, item, next, options.restoredFrom ?? null, item.updatedAt);
54
+ }
55
+ export function deleteVersions(db, itemKind, itemId) {
56
+ db.prepare("DELETE FROM library_versions WHERE item_kind=? AND item_id=?").run(itemKind, itemId);
57
+ }
58
+ // History for a row with no versions yet (written by an import): its saved text as version 1,
59
+ // said rather than stored, since reading must not write.
60
+ export function versionsOrCurrent(db, itemKind, item) {
61
+ const versions = listVersions(db, itemKind, item.id);
62
+ if (versions.length > 0)
63
+ return versions;
64
+ return [
65
+ {
66
+ version: 1,
67
+ kind: kindOf(item),
68
+ mode: modeOf(item),
69
+ name: item.name,
70
+ body: item.body,
71
+ author: versionAuthor,
72
+ restoredFrom: null,
73
+ createdAt: item.updatedAt,
74
+ },
75
+ ];
76
+ }
77
+ function insert(db, itemKind, item, version, restoredFrom, createdAt) {
78
+ db.prepare("INSERT INTO library_versions (item_kind,item_id,version,kind,mode,name,body,author,restored_from,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)").run(itemKind, item.id, version, kindOf(item), modeOf(item), item.name, item.body, versionAuthor, restoredFrom, createdAt);
79
+ }
80
+ function same(a, b) {
81
+ return (a.name === b.name && a.body === b.body && kindOf(a) === kindOf(b) && modeOf(a) === modeOf(b));
82
+ }
83
+ function sameAsVersion(version, item) {
84
+ return (version.name === item.name &&
85
+ version.body === item.body &&
86
+ version.kind === kindOf(item) &&
87
+ version.mode === modeOf(item));
88
+ }
89
+ function toVersion(row) {
90
+ return {
91
+ version: row.version,
92
+ kind: row.kind,
93
+ mode: row.mode,
94
+ name: row.name,
95
+ body: row.body,
96
+ author: row.author,
97
+ restoredFrom: row.restored_from,
98
+ createdAt: row.created_at,
99
+ };
100
+ }
@@ -9,6 +9,9 @@ export const promptKinds = [
9
9
  "narration",
10
10
  "description",
11
11
  "shorts",
12
+ "review",
13
+ // Speaker turns for a multi-voice run (`slices/voices/script.ts`), in place of an article.
14
+ "script",
12
15
  ];
13
16
  export const entryCategories = ["intro", "outro"];
14
17
  // The name is what the Play pickers show, so it shares admission's title bound.
@@ -0,0 +1,49 @@
1
+ import { readSetting, writeSetting } from "../settings/repo.js";
2
+ import { promptById, promptByName } from "./repo.js";
3
+ // An Image prompt's "Draws photorealistic pictures" tick (Library → Prompts → the prompt).
4
+ // Pictures an image model draws in such a style can pass for real scenes, which is YouTube's
5
+ // third AI use case (`slices/studio/disclosure.ts`); a stylised, painterly or illustrated
6
+ // style can't, so the tick is off unless the person sets it. One row of the key/value
7
+ // `settings` table holds the ticked prompts' ids, so the flag needs no column of its own, keeps
8
+ // through edits and renames, and travels with a backup. A prompt in the trash keeps its tick
9
+ // for when it is restored.
10
+ export const photorealisticPromptsKey = "library.photorealisticPrompts";
11
+ export function photorealisticPromptIds(db) {
12
+ const stored = readSetting(db, photorealisticPromptsKey);
13
+ if (stored === undefined)
14
+ return new Set();
15
+ try {
16
+ const value = JSON.parse(stored);
17
+ return new Set(Array.isArray(value) ? value.filter((one) => typeof one === "string") : []);
18
+ }
19
+ catch {
20
+ return new Set();
21
+ }
22
+ }
23
+ // The prompts as the Library lists them, the ticked ones marked.
24
+ export function withPhotorealistic(db, prompts) {
25
+ const ticked = photorealisticPromptIds(db);
26
+ return prompts.map((prompt) => ticked.has(prompt.id) ? { ...prompt, photorealistic: true } : prompt);
27
+ }
28
+ export function setPromptPhotorealistic(db, id, on) {
29
+ const prompt = promptById(db, id);
30
+ if (prompt === undefined)
31
+ return { ok: false, reason: "not-found" };
32
+ if (prompt.kind !== "image")
33
+ return { ok: false, reason: "not-an-image-prompt" };
34
+ const others = [...photorealisticPromptIds(db)].filter((one) => one !== id);
35
+ const next = on ? [...others, id].sort() : others;
36
+ if (next.length === 0)
37
+ db.prepare("DELETE FROM settings WHERE key = ?").run(photorealisticPromptsKey);
38
+ else
39
+ writeSetting(db, photorealisticPromptsKey, JSON.stringify(next));
40
+ return { ok: true };
41
+ }
42
+ // Whether the Image prompt a project names draws photorealistic pictures. A name the Library
43
+ // no longer has (deleted or renamed since) is not ticked.
44
+ export function namesPhotorealisticPrompt(db, name) {
45
+ if (name === undefined || name.trim() === "")
46
+ return false;
47
+ const prompt = promptByName(db, "image", name.trim());
48
+ return prompt !== undefined && photorealisticPromptIds(db).has(prompt.id);
49
+ }
@@ -23,6 +23,8 @@ const slotsColumn = z.array(z.string());
23
23
  // `lower(name)` and not `name COLLATE NOCASE`: it is the expression the unique indexes
24
24
  // are built on, so a lookup by name reads the index instead of the table.
25
25
  const byName = "lower(name) = lower(?)";
26
+ // A row in the trash is invisible to every read and write here; `slices/trash` owns it.
27
+ const live = "deleted_at IS NULL";
26
28
  // The lists on 04 Prompts and the pickers on Play sort by name.
27
29
  const byNameOrder = "ORDER BY lower(name)";
28
30
  export function insertPrompt(db, prompt) {
@@ -32,27 +34,30 @@ export function insertPrompt(db, prompt) {
32
34
  // column. Returns false when no row has that id.
33
35
  export function replacePrompt(db, prompt) {
34
36
  const result = db
35
- .prepare("UPDATE prompts SET kind = ?, name = ?, body = ?, slots = ?, updated_at = ? WHERE id = ?")
37
+ .prepare(`UPDATE prompts SET kind = ?, name = ?, body = ?, slots = ?, updated_at = ? WHERE id = ? AND ${live}`)
36
38
  .run(prompt.kind, prompt.name, prompt.body, JSON.stringify(prompt.slots), prompt.updatedAt, prompt.id);
37
39
  return Number(result.changes) > 0;
38
40
  }
39
- // No foreign key points at this row, so a project that used the template keeps
40
- // its own rendered text and nothing cascades.
41
- export function deletePrompt(db, id) {
42
- return Number(db.prepare("DELETE FROM prompts WHERE id = ?").run(id).changes) > 0;
41
+ // Delete moves the row to the trash (Settings → Trash): it keeps its versions and leaves
42
+ // every list and lookup here. No foreign key points at it, so a project that used the
43
+ // template keeps its own rendered text either way. `slices/trash` removes it for good.
44
+ export function trashPrompt(db, id, at) {
45
+ return (Number(db.prepare(`UPDATE prompts SET deleted_at = ? WHERE id = ? AND ${live}`).run(at, id).changes) > 0);
43
46
  }
44
47
  export function listPrompts(db) {
45
48
  return db
46
- .prepare(`SELECT * FROM prompts ${byNameOrder}`)
49
+ .prepare(`SELECT * FROM prompts WHERE ${live} ${byNameOrder}`)
47
50
  .all()
48
51
  .map((row) => toPrompt(promptRow.parse(row)));
49
52
  }
50
53
  export function promptById(db, id) {
51
- const row = db.prepare("SELECT * FROM prompts WHERE id = ?").get(id);
54
+ const row = db.prepare(`SELECT * FROM prompts WHERE id = ? AND ${live}`).get(id);
52
55
  return row === undefined ? undefined : toPrompt(promptRow.parse(row));
53
56
  }
54
57
  export function promptByName(db, kind, name) {
55
- const row = db.prepare(`SELECT * FROM prompts WHERE kind = ? AND ${byName}`).get(kind, name);
58
+ const row = db
59
+ .prepare(`SELECT * FROM prompts WHERE kind = ? AND ${byName} AND ${live}`)
60
+ .get(kind, name);
56
61
  return row === undefined ? undefined : toPrompt(promptRow.parse(row));
57
62
  }
58
63
  export function insertEntry(db, entry) {
@@ -60,26 +65,26 @@ export function insertEntry(db, entry) {
60
65
  }
61
66
  export function replaceEntry(db, entry) {
62
67
  const result = db
63
- .prepare("UPDATE entries SET category = ?, mode = ?, name = ?, body = ?, slots = ?, updated_at = ? WHERE id = ?")
68
+ .prepare(`UPDATE entries SET category = ?, mode = ?, name = ?, body = ?, slots = ?, updated_at = ? WHERE id = ? AND ${live}`)
64
69
  .run(entry.category, entry.mode, entry.name, entry.body, JSON.stringify(entry.slots), entry.updatedAt, entry.id);
65
70
  return Number(result.changes) > 0;
66
71
  }
67
- export function deleteEntry(db, id) {
68
- return Number(db.prepare("DELETE FROM entries WHERE id = ?").run(id).changes) > 0;
72
+ export function trashEntry(db, id, at) {
73
+ return (Number(db.prepare(`UPDATE entries SET deleted_at = ? WHERE id = ? AND ${live}`).run(at, id).changes) > 0);
69
74
  }
70
75
  export function listEntries(db) {
71
76
  return db
72
- .prepare(`SELECT * FROM entries ${byNameOrder}`)
77
+ .prepare(`SELECT * FROM entries WHERE ${live} ${byNameOrder}`)
73
78
  .all()
74
79
  .map((row) => toEntry(entryRow.parse(row)));
75
80
  }
76
81
  export function entryById(db, id) {
77
- const row = db.prepare("SELECT * FROM entries WHERE id = ?").get(id);
82
+ const row = db.prepare(`SELECT * FROM entries WHERE id = ? AND ${live}`).get(id);
78
83
  return row === undefined ? undefined : toEntry(entryRow.parse(row));
79
84
  }
80
85
  export function entryByName(db, category, name) {
81
86
  const row = db
82
- .prepare(`SELECT * FROM entries WHERE category = ? AND ${byName}`)
87
+ .prepare(`SELECT * FROM entries WHERE category = ? AND ${byName} AND ${live}`)
83
88
  .get(category, name);
84
89
  return row === undefined ? undefined : toEntry(entryRow.parse(row));
85
90
  }
@@ -1,31 +1,57 @@
1
1
  // Creating, editing and deleting a template. One rule set covers prompts
2
2
  // and entries, so both go through the same three outcomes.
3
3
  import { isUniqueConstraint } from "../../kernel/db/index.js";
4
+ import { transact } from "../../kernel/db/tx.js";
4
5
  import { detectSlots } from "../admission/substitute.js";
6
+ import { recordVersion, versionOf } from "./history.js";
5
7
  import { lintEntry, lintPrompt } from "./lint.js";
6
- import { deleteEntry, deletePrompt, insertEntry, insertPrompt, replaceEntry, replacePrompt, } from "./repo.js";
8
+ import { entryCategories, promptKinds } from "./model.js";
9
+ import { entryById, insertEntry, insertPrompt, promptById, replaceEntry, replacePrompt, trashEntry, trashPrompt, } from "./repo.js";
7
10
  export function createPrompt(deps, draft) {
8
11
  const fields = lintPrompt(draft);
9
12
  if (fields.length > 0) {
10
13
  return { ok: false, reason: "invalid", fields };
11
14
  }
12
15
  const prompt = { id: deps.ids.next(), ...promptOf(deps, draft) };
13
- return written(() => {
16
+ return written(deps.db, () => {
14
17
  insertPrompt(deps.db, prompt);
18
+ recordVersion(deps.db, "prompt", prompt);
15
19
  return true;
16
20
  }, prompt);
17
21
  }
18
- // A save overwrites; there is no version history, so the row is replaced whole.
19
- export function updatePrompt(deps, id, draft) {
22
+ // A save replaces the row whole and keeps what was saved as a new version (`history.ts`).
23
+ export function updatePrompt(deps, id, draft, restoredFrom) {
20
24
  const fields = lintPrompt(draft);
21
25
  if (fields.length > 0) {
22
26
  return { ok: false, reason: "invalid", fields };
23
27
  }
24
28
  const prompt = { id, ...promptOf(deps, draft) };
25
- return written(() => replacePrompt(deps.db, prompt), prompt);
29
+ return written(deps.db, () => {
30
+ const previous = promptById(deps.db, id);
31
+ if (!replacePrompt(deps.db, prompt))
32
+ return false;
33
+ recordVersion(deps.db, "prompt", prompt, {
34
+ previous,
35
+ ...(restoredFrom === undefined ? {} : { restoredFrom }),
36
+ });
37
+ return true;
38
+ }, prompt);
39
+ }
40
+ // Restore saves an old version again, as a new version that says where it came from, so the
41
+ // versions after it stay in History.
42
+ export function restorePrompt(deps, id, version) {
43
+ const saved = versionOf(deps.db, "prompt", id, version);
44
+ const kind = promptKinds.find((one) => one === saved?.kind);
45
+ if (saved === undefined || kind === undefined || promptById(deps.db, id) === undefined)
46
+ return { ok: false, reason: "not-found" };
47
+ return updatePrompt(deps, id, { kind, name: saved.name, body: saved.body }, version);
26
48
  }
49
+ // Delete moves the prompt to the trash with its history; Settings → Trash puts it back or
50
+ // removes it for good (`slices/trash`).
27
51
  export function removePrompt(deps, id) {
28
- return deletePrompt(deps.db, id) ? { ok: true, value: null } : { ok: false, reason: "not-found" };
52
+ return trashPrompt(deps.db, id, deps.clock.now().toISOString())
53
+ ? { ok: true, value: null }
54
+ : { ok: false, reason: "not-found" };
29
55
  }
30
56
  export function createEntry(deps, draft) {
31
57
  const fields = lintEntry(draft);
@@ -33,21 +59,43 @@ export function createEntry(deps, draft) {
33
59
  return { ok: false, reason: "invalid", fields };
34
60
  }
35
61
  const entry = { id: deps.ids.next(), ...entryOf(deps, draft) };
36
- return written(() => {
62
+ return written(deps.db, () => {
37
63
  insertEntry(deps.db, entry);
64
+ recordVersion(deps.db, "entry", entry);
38
65
  return true;
39
66
  }, entry);
40
67
  }
41
- export function updateEntry(deps, id, draft) {
68
+ export function updateEntry(deps, id, draft, restoredFrom) {
42
69
  const fields = lintEntry(draft);
43
70
  if (fields.length > 0) {
44
71
  return { ok: false, reason: "invalid", fields };
45
72
  }
46
73
  const entry = { id, ...entryOf(deps, draft) };
47
- return written(() => replaceEntry(deps.db, entry), entry);
74
+ return written(deps.db, () => {
75
+ const previous = entryById(deps.db, id);
76
+ if (!replaceEntry(deps.db, entry))
77
+ return false;
78
+ recordVersion(deps.db, "entry", entry, {
79
+ previous,
80
+ ...(restoredFrom === undefined ? {} : { restoredFrom }),
81
+ });
82
+ return true;
83
+ }, entry);
84
+ }
85
+ export function restoreEntry(deps, id, version) {
86
+ const saved = versionOf(deps.db, "entry", id, version);
87
+ const category = entryCategories.find((one) => one === saved?.kind);
88
+ if (saved === undefined ||
89
+ category === undefined ||
90
+ saved.mode === null ||
91
+ entryById(deps.db, id) === undefined)
92
+ return { ok: false, reason: "not-found" };
93
+ return updateEntry(deps, id, { category, mode: saved.mode, name: saved.name, body: saved.body }, version);
48
94
  }
49
95
  export function removeEntry(deps, id) {
50
- return deleteEntry(deps.db, id) ? { ok: true, value: null } : { ok: false, reason: "not-found" };
96
+ return trashEntry(deps.db, id, deps.clock.now().toISOString())
97
+ ? { ok: true, value: null }
98
+ : { ok: false, reason: "not-found" };
51
99
  }
52
100
  // The stored `slots` is recomputed from the body on every save, so the column can never
53
101
  // describe a body that is no longer there.
@@ -74,9 +122,11 @@ function entryOf(deps, draft) {
74
122
  // lower(name))`. A read-then-write check would answer from a row a second writer could
75
123
  // delete between the two statements, so the index decides and the raw SQLite error never
76
124
  // leaves this module.
77
- function written(write, value) {
125
+ // The row and its version are written together: a save either lands with its version or not
126
+ // at all.
127
+ function written(db, write, value) {
78
128
  try {
79
- return write() ? { ok: true, value } : { ok: false, reason: "not-found" };
129
+ return transact(db, write) ? { ok: true, value } : { ok: false, reason: "not-found" };
80
130
  }
81
131
  catch (error) {
82
132
  if (isUniqueConstraint(error)) {
@@ -4,6 +4,9 @@
4
4
  // is the one that runs.
5
5
  import { normaliseDraft, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
6
6
  import { collectFields, render } from "../admission/substitute.js";
7
+ import { reviewPromptKey } from "../reviews/model.js";
8
+ import { activeReviewStages } from "../reviews/rules.js";
9
+ import { usesScriptPrompt } from "../voices/model.js";
7
10
  import { snapshotEntry, snapshotPrompt } from "./snapshot.js";
8
11
  export function pickTemplates(db, input, snapshot) {
9
12
  const draft = normaliseDraft(input);
@@ -14,7 +17,7 @@ export function pickTemplates(db, input, snapshot) {
14
17
  const text = [];
15
18
  const image = [];
16
19
  if (sources.article === "generate") {
17
- body(db, "article", draft.articlePrompt, "articlePrompt", missing, text, "article", snapshot);
20
+ body(db, usesScriptPrompt(draft) ? "script" : "article", draft.articlePrompt, "articlePrompt", missing, text, "article", snapshot);
18
21
  }
19
22
  const intro = pickEntry(db, "intro", draft.intro, missing, snapshot);
20
23
  if (usesNarrationPreparation(draft))
@@ -30,6 +33,9 @@ export function pickTemplates(db, input, snapshot) {
30
33
  body(db, "shorts", draft.shorts?.prompt, "shorts.prompt", missing, text, "shorts", snapshot);
31
34
  body(db, "image", draft.shorts?.imagePrompt, "shorts.imagePrompt", missing, image, "shortsImage", snapshot);
32
35
  }
36
+ // Each reviewed stage's Review prompt; none picked is the stage's built-in one.
37
+ for (const stage of activeReviewStages(draft))
38
+ body(db, "review", draft.reviews?.stages[stage]?.prompt, `reviews.stages.${stage}.prompt`, missing, text, reviewPromptKey(stage), snapshot);
33
39
  // The establishing image's prompt: an image prompt like the others, its keywords filled the
34
40
  // same way.
35
41
  if (usesReference(draft) && draft.reference?.source === "prompt")