@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.
- package/dist/adapters/account-limits.js +1 -1
- package/dist/adapters/alignment/audio.js +1 -1
- package/dist/adapters/alignment/cache.js +1 -1
- package/dist/adapters/alignment/multilingual.js +2 -1
- package/dist/adapters/alignment/runner.js +1 -1
- package/dist/adapters/alignment/worker.js +1 -1
- package/dist/adapters/explain.js +10 -9
- package/dist/adapters/host-cli/index.js +2 -1
- package/dist/adapters/host-cli/transport.js +2 -1
- package/dist/adapters/image/bytes.js +2 -1
- package/dist/adapters/image/codex.js +6 -5
- package/dist/adapters/image/fal.js +4 -3
- package/dist/adapters/image/google.js +3 -2
- package/dist/adapters/image/models.js +2 -2
- package/dist/adapters/image/openai.js +2 -1
- package/dist/adapters/image/replicate.js +3 -2
- package/dist/adapters/image/video.js +2 -1
- package/dist/adapters/key-probes.js +1 -1
- package/dist/adapters/llm/antigravity-models.js +1 -1
- package/dist/adapters/llm/antigravity.js +7 -6
- package/dist/adapters/llm/assistant-cli.js +1 -1
- package/dist/adapters/llm/claude-code-models.js +1 -1
- package/dist/adapters/llm/claude-code.js +3 -2
- package/dist/adapters/llm/codex-models.js +1 -1
- package/dist/adapters/llm/document-workspace.js +3 -2
- package/dist/adapters/llm/image-workspace.js +3 -2
- package/dist/adapters/llm/openrouter.js +2 -1
- package/dist/adapters/llm/run-cli.js +3 -2
- package/dist/adapters/tts/gemini.js +2 -1
- package/dist/adapters/tts/inworld-async.js +2 -1
- package/dist/adapters/tts/inworld.js +3 -2
- package/dist/adapters/tts/system.js +2 -1
- package/dist/catalog/registry.js +20 -13
- package/dist/catalog/schema.js +2 -1
- package/dist/catalog/validate.js +5 -2
- package/dist/edge/http/actions.js +7 -7
- package/dist/edge/http/app.js +5 -3
- package/dist/edge/http/assistant.js +3 -1
- package/dist/edge/http/backups.js +1 -1
- package/dist/edge/http/channel-memory.js +2 -2
- package/dist/edge/http/channels.js +1 -1
- package/dist/edge/http/checkpoints.js +7 -5
- package/dist/edge/http/design-templates.js +3 -3
- package/dist/edge/http/designs.js +1 -1
- package/dist/edge/http/document-themes.js +3 -3
- package/dist/edge/http/draft-problem.js +1 -1
- package/dist/edge/http/extension-auth.js +1 -1
- package/dist/edge/http/home.js +9 -1
- package/dist/edge/http/model-checks.js +4 -4
- package/dist/edge/http/onboarding.js +29 -12
- package/dist/edge/http/outputs.js +3 -2
- package/dist/edge/http/patch-notes.js +2 -2
- package/dist/edge/http/planning.js +1 -1
- package/dist/edge/http/project-create.js +1 -1
- package/dist/edge/http/project-templates.js +2 -2
- package/dist/edge/http/projects.js +22 -4
- package/dist/edge/http/prompts.js +2 -2
- package/dist/edge/http/pronunciation-check.js +133 -0
- package/dist/edge/http/pronunciations.js +26 -0
- package/dist/edge/http/providers.js +9 -10
- package/dist/edge/http/refusal.js +1 -1
- package/dist/edge/http/remote-access.js +4 -4
- package/dist/edge/http/remote-guard.js +2 -2
- package/dist/edge/http/reviews.js +1 -1
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +4 -4
- package/dist/edge/http/schedules.js +13 -11
- package/dist/edge/http/settings.js +6 -2
- package/dist/edge/http/social.js +5 -4
- package/dist/edge/http/staging.js +1 -1
- package/dist/edge/http/storage.js +12 -4
- package/dist/edge/http/studio.js +71 -19
- package/dist/edge/http/style-preview.js +2 -2
- package/dist/edge/http/template-studio.js +12 -9
- package/dist/edge/http/trash.js +1 -1
- package/dist/edge/http/youtube-edits.js +1 -1
- package/dist/edge/mcp/excluded-routes.js +4 -3
- package/dist/edge/mcp/instructions.js +3 -3
- package/dist/edge/mcp/library-tools.js +8 -5
- package/dist/edge/mcp/remake-tools.js +7 -4
- package/dist/edge/mcp/server.js +1 -1
- package/dist/edge/mcp/tools-channels.js +9 -9
- package/dist/edge/mcp/tools-designs.js +9 -9
- package/dist/edge/mcp/tools-library.js +31 -7
- package/dist/edge/mcp/tools-planning.js +18 -18
- package/dist/edge/mcp/tools-projects.js +10 -10
- package/dist/edge/mcp/tools-publishing.js +9 -9
- package/dist/edge/mcp/tools-runs.js +9 -9
- package/dist/edge/mcp/tools-schedules.js +6 -6
- package/dist/edge/mcp/tools-setup.js +20 -14
- package/dist/edge/mcp/tools-social.js +5 -4
- package/dist/edge/mcp/tools-storage.js +3 -3
- package/dist/edge/mcp/tools-template-studio.js +18 -18
- package/dist/edge/remote-access/model.js +1 -1
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
- package/dist/kernel/pipeline.js +12 -0
- package/dist/kernel/ports/settings-places.js +31 -0
- package/dist/kernel/ports/system-speech.js +31 -3
- package/dist/kernel/runner/attempt.js +11 -3
- package/dist/kernel/runner/providers.js +3 -2
- package/dist/kernel/runner/standalone.js +2 -2
- package/dist/main.js +5 -3
- package/dist/model-catalog.js +20 -0
- package/dist/patch-notes/4.0.0.md +13 -13
- package/dist/patch-notes/4.1.0.md +42 -0
- package/dist/patch-notes/4.2.0.md +28 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/admission/listing-facts.js +95 -0
- package/dist/slices/admission/repo.js +9 -6
- package/dist/slices/admission/rules.js +7 -7
- package/dist/slices/admission/schema.js +2 -0
- package/dist/slices/admission/start.js +7 -0
- package/dist/slices/admission/state-words.js +34 -5
- package/dist/slices/assistant/cost.js +1 -1
- package/dist/slices/assistant/gate.js +1 -0
- package/dist/slices/assistant/model.js +10 -3
- package/dist/slices/assistant/turn.js +5 -3
- package/dist/slices/autopilot/warnings.js +1 -1
- package/dist/slices/backups/service.js +5 -5
- package/dist/slices/channels/cast-images.js +1 -1
- package/dist/slices/channels/schema.js +1 -1
- package/dist/slices/channels/videos.js +1 -1
- package/dist/slices/control/providers.js +2 -2
- package/dist/slices/designs/budget.js +1 -1
- package/dist/slices/designs/calls.js +2 -2
- package/dist/slices/designs/check-context.js +4 -4
- package/dist/slices/designs/fields.js +4 -4
- package/dist/slices/designs/gate.js +7 -5
- package/dist/slices/designs/render-input.js +1 -1
- package/dist/slices/designs/requests.js +1 -1
- package/dist/slices/designs/runner.js +1 -1
- package/dist/slices/designs/sample.js +2 -1
- package/dist/slices/designs/service.js +1 -1
- package/dist/slices/designs/template-checks.js +2 -2
- package/dist/slices/designs/template-make.js +3 -3
- package/dist/slices/designs/templates.js +3 -3
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/model.js +7 -4
- package/dist/slices/document/render.js +2 -1
- package/dist/slices/document/theme-schema.js +1 -1
- package/dist/slices/document/theme.js +49 -3
- package/dist/slices/episodes/summarize.js +2 -2
- package/dist/slices/estimate/index.js +28 -22
- package/dist/slices/estimate/requests.js +4 -4
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/fonts/catalog.js +2 -1
- package/dist/slices/fonts/coverage.js +2 -1
- package/dist/slices/images/appearance.js +2 -1
- package/dist/slices/images/scenes.js +2 -1
- package/dist/slices/keywords/digest.js +2 -1
- package/dist/slices/keywords/rules.js +2 -2
- package/dist/slices/keywords/specs.js +5 -5
- package/dist/slices/keywords/values.js +1 -1
- package/dist/slices/library/transfer.js +3 -3
- package/dist/slices/loudness/loudnorm.js +2 -1
- package/dist/slices/loudness/model.js +3 -2
- package/dist/slices/loudness/sentence-level.js +2 -1
- package/dist/slices/model-checks/rules.js +13 -0
- package/dist/slices/model-upkeep/model.js +1 -1
- package/dist/slices/model-upkeep/switch.js +3 -3
- package/dist/slices/model-upkeep/usage.js +13 -2
- package/dist/slices/narration/breaths.js +2 -1
- package/dist/slices/narration/clean-pieces.js +2 -1
- package/dist/slices/narration/pauses-model.js +3 -2
- package/dist/slices/narration/pronunciation-check.js +226 -0
- package/dist/slices/narration/pronunciation.js +7 -2
- package/dist/slices/narration/pronunciations-library.js +94 -0
- package/dist/slices/notifications/subject.js +2 -2
- package/dist/slices/onboarding/copy.js +19 -2
- package/dist/slices/onboarding/first-run.js +13 -38
- package/dist/slices/onboarding/install.js +2 -1
- package/dist/slices/onboarding/model.js +26 -21
- package/dist/slices/onboarding/preferred-model.js +14 -0
- package/dist/slices/onboarding/quick-short.js +103 -64
- package/dist/slices/onboarding/sample.js +4 -1
- package/dist/slices/onboarding/state.js +7 -0
- package/dist/slices/onboarding/template.js +6 -5
- package/dist/slices/outputs/add.js +3 -2
- package/dist/slices/outputs/service.js +2 -2
- package/dist/slices/patch-notes/library.js +1 -1
- package/dist/slices/play-drafts/readiness.js +3 -3
- package/dist/slices/play-drafts/review-inputs.js +1 -1
- package/dist/slices/play-drafts/service.js +17 -7
- package/dist/slices/project-templates/next-chapter.js +2 -1
- package/dist/slices/project-templates/provider-defaults.js +22 -0
- package/dist/slices/project-templates/setup-sections.js +26 -0
- package/dist/slices/project-templates/setup.js +6 -1
- package/dist/slices/rebuild/preview-details.js +5 -5
- package/dist/slices/rebuild/recipe-preparation.js +1 -1
- package/dist/slices/rebuild/recipe-save.js +2 -2
- package/dist/slices/rebuild/recipe-text.js +16 -4
- package/dist/slices/rebuild/recipe-validation.js +5 -4
- package/dist/slices/rebuild/recipe-voices.js +3 -2
- package/dist/slices/rebuild/recipe-work.js +4 -4
- package/dist/slices/rebuild/recovery.js +6 -6
- package/dist/slices/rebuild/remake-outdated.js +5 -5
- package/dist/slices/rebuild/review-redo.js +1 -1
- package/dist/slices/rebuild/runtime-animate.js +2 -2
- package/dist/slices/rebuild/runtime-article.js +4 -3
- package/dist/slices/rebuild/runtime-document.js +5 -4
- package/dist/slices/rebuild/runtime-export-bed.js +2 -1
- package/dist/slices/rebuild/runtime-export-edit.js +2 -1
- package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
- package/dist/slices/rebuild/runtime-export-short.js +1 -1
- package/dist/slices/rebuild/runtime-export.js +4 -3
- package/dist/slices/rebuild/runtime-image.js +4 -3
- package/dist/slices/rebuild/runtime-local.js +3 -2
- package/dist/slices/rebuild/runtime-narration-text.js +3 -2
- package/dist/slices/rebuild/runtime-provider.js +7 -6
- package/dist/slices/rebuild/runtime-review.js +6 -5
- package/dist/slices/rebuild/runtime-shorts.js +6 -5
- package/dist/slices/rebuild/runtime-store.js +17 -2
- package/dist/slices/rebuild/runtime-subtitles.js +13 -12
- package/dist/slices/rebuild/runtime-voices.js +2 -1
- package/dist/slices/rebuild/runtime-youtube.js +3 -2
- package/dist/slices/rebuild/service-readiness.js +11 -3
- package/dist/slices/research/synthesis.js +3 -2
- package/dist/slices/reviews/external-model.js +1 -1
- package/dist/slices/reviews/holds.js +5 -4
- package/dist/slices/reviews/model.js +8 -5
- package/dist/slices/reviews/rules.js +3 -3
- package/dist/slices/reviews/verdict.js +2 -1
- package/dist/slices/revisions/image-plan.js +1 -1
- package/dist/slices/revisions/mutation-assets.js +2 -1
- package/dist/slices/revisions/mutations.js +3 -2
- package/dist/slices/schedules/agenda.js +7 -5
- package/dist/slices/schedules/design-check.js +4 -4
- package/dist/slices/schedules/design-run.js +3 -3
- package/dist/slices/schedules/repo.js +6 -5
- package/dist/slices/schedules/schema.js +3 -0
- package/dist/slices/schedules/topics.js +5 -5
- package/dist/slices/settings/first-run.js +6 -32
- package/dist/slices/settings/health.js +30 -15
- package/dist/slices/settings/key-test.js +1 -1
- package/dist/slices/settings/model.js +4 -2
- package/dist/slices/settings/playback.js +2 -2
- package/dist/slices/shorts/keep.js +2 -1
- package/dist/slices/shorts/pick.js +4 -3
- package/dist/slices/shorts/prompts.js +2 -1
- package/dist/slices/shorts/render.js +3 -2
- package/dist/slices/social/media.js +5 -4
- package/dist/slices/social/pack.js +1 -1
- package/dist/slices/social/post-prompt.js +2 -1
- package/dist/slices/social/reshape.js +2 -2
- package/dist/slices/social/serve.js +1 -1
- package/dist/slices/social/service.js +3 -2
- package/dist/slices/social/state.js +1 -1
- package/dist/slices/storage/backup-import.js +2 -2
- package/dist/slices/storage/portable.js +1 -1
- package/dist/slices/storage/trim.js +23 -13
- package/dist/slices/studio/calendar.js +3 -1
- package/dist/slices/studio/disclosure.js +4 -4
- package/dist/slices/studio/model.js +1 -1
- package/dist/slices/studio/pack.js +9 -8
- package/dist/slices/studio/plan.js +1 -1
- package/dist/slices/studio/settings.js +6 -6
- package/dist/slices/studio/videos.js +23 -4
- package/dist/slices/style-preview/render.js +3 -3
- package/dist/slices/style-preview/schema.js +1 -1
- package/dist/slices/subtitles/captions.js +1 -1
- package/dist/slices/template-studio/anatomy-view.js +46 -3
- package/dist/slices/template-studio/bakeoff.js +3 -3
- package/dist/slices/template-studio/budget.js +6 -3
- package/dist/slices/template-studio/cards.js +78 -0
- package/dist/slices/template-studio/checks.js +99 -50
- package/dist/slices/template-studio/for-all.js +5 -5
- package/dist/slices/template-studio/free-video.js +1 -1
- package/dist/slices/template-studio/free.js +5 -5
- package/dist/slices/template-studio/model.js +19 -15
- package/dist/slices/template-studio/pack-import.js +14 -14
- package/dist/slices/template-studio/pack.js +9 -7
- package/dist/slices/template-studio/paid.js +12 -5
- package/dist/slices/template-studio/quote.js +18 -11
- package/dist/slices/template-studio/ready.js +7 -7
- package/dist/slices/template-studio/runner.js +1 -1
- package/dist/slices/template-studio/save.js +12 -12
- package/dist/slices/template-studio/say-it-as.js +3 -3
- package/dist/slices/template-studio/schedule-gate.js +1 -1
- package/dist/slices/template-studio/schedule-price.js +3 -1
- package/dist/slices/template-studio/schedule-sample.js +5 -1
- package/dist/slices/template-studio/schema.js +25 -0
- package/dist/slices/template-studio/service.js +3 -1
- package/dist/slices/template-studio/status.js +26 -9
- package/dist/slices/template-studio/topic.js +3 -3
- package/dist/slices/thumbnail/by-llm.js +1 -1
- package/dist/slices/uploads/repo.js +37 -1
- package/dist/slices/uploads/rules.js +3 -2
- package/dist/slices/video/edit-list.js +2 -2
- package/dist/slices/video/edit-settings.js +1 -1
- package/dist/slices/video/encoding.js +14 -5
- package/dist/slices/video/ffmpeg.js +2 -2
- package/dist/slices/video/plan.js +2 -1
- package/dist/slices/video/slideshow.js +1 -1
- package/dist/slices/voices/attribution.js +3 -2
- package/dist/slices/voices/model.js +9 -8
- package/dist/slices/voices/script.js +2 -1
- package/dist/slices/youtube/answer.js +2 -1
- package/dist/slices/youtube/edits-repo.js +1 -1
- package/dist/tutorials/AI-CLIs.md +25 -25
- package/dist/tutorials/Agents-MCP.md +32 -32
- package/dist/tutorials/Assistant.md +5 -5
- package/dist/tutorials/Backups.md +7 -7
- package/dist/tutorials/Calendar.md +17 -25
- package/dist/tutorials/Cast-Library.md +19 -19
- package/dist/tutorials/Channels.md +40 -35
- package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
- package/dist/tutorials/Create-Article.md +114 -0
- package/dist/tutorials/Create-Extras.md +112 -0
- package/dist/tutorials/Create-Images.md +165 -0
- package/dist/tutorials/Create-Narration.md +178 -0
- package/dist/tutorials/Create-Overview.md +244 -0
- package/dist/tutorials/Create-Video-and-Style.md +144 -0
- package/dist/tutorials/Docker.md +3 -3
- package/dist/tutorials/Document-Themes.md +32 -32
- package/dist/tutorials/Editing-a-Project.md +95 -94
- package/dist/tutorials/FAQ.md +18 -18
- package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
- package/dist/tutorials/Home-and-Projects.md +101 -76
- package/dist/tutorials/Home.md +33 -33
- package/dist/tutorials/Install.md +13 -13
- package/dist/tutorials/Intros-and-Outros.md +33 -34
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
- package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
- package/dist/tutorials/Models.md +27 -27
- package/dist/tutorials/Multiple-Voices.md +41 -40
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +101 -19
- package/dist/tutorials/Notifications.md +21 -17
- package/dist/tutorials/Other-Languages.md +15 -15
- package/dist/tutorials/PDF-Documents.md +38 -29
- package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
- package/dist/tutorials/Project-Page.md +118 -120
- package/dist/tutorials/Prompts-and-Templates.md +84 -0
- package/dist/tutorials/Prompts.md +45 -45
- package/dist/tutorials/Providers-and-Keys.md +105 -98
- package/dist/tutorials/Publishing-to-YouTube.md +31 -31
- package/dist/tutorials/Recovery-and-Retries.md +31 -31
- package/dist/tutorials/Remote-Access.md +2 -2
- package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
- package/dist/tutorials/Schedules.md +27 -28
- package/dist/tutorials/Settings-Reference.md +183 -204
- package/dist/tutorials/Shorts.md +34 -33
- package/dist/tutorials/Start-at-Login.md +3 -3
- package/dist/tutorials/Studio-Extension.md +22 -24
- package/dist/tutorials/Template-Studio.md +45 -42
- package/dist/tutorials/Templates.md +50 -79
- package/dist/tutorials/Trash.md +4 -4
- package/dist/tutorials/Troubleshooting.md +44 -44
- package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
- package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
- package/dist/tutorials/Video-Editing.md +34 -34
- package/dist/tutorials/Where-Your-Files-Live.md +10 -10
- package/dist/tutorials/YouTube-Description.md +26 -26
- package/dist/tutorials/Your-First-Short.md +35 -35
- package/dist/tutorials/_Sidebar.md +15 -15
- package/dist/web/assets/index-BNftKQrA.css +1 -0
- package/dist/web/assets/index-CQASqwjT.js +460 -0
- package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
- package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
- package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/tutorials/Library-Overview.md +0 -76
- package/dist/tutorials/Play-Images.md +0 -165
- package/dist/tutorials/Play-Narration.md +0 -167
- package/dist/tutorials/Play-Outputs.md +0 -109
- package/dist/tutorials/Play-Overview.md +0 -227
- package/dist/tutorials/Play-Title-and-Article.md +0 -136
- package/dist/tutorials/Play-Video-and-Style.md +0 -143
- package/dist/web/assets/index-CkAI_2E7.css +0 -1
- package/dist/web/assets/index-DcD0cdfo.js +0 -334
- package/dist/web/assets/pdf-TODRwF31.js +0 -54
- package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
- package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Slopify 4.1.0
|
|
2
|
+
|
|
3
|
+
Released 7 November 2026.
|
|
4
|
+
|
|
5
|
+
This release is about making Slopify easy to use without reading anything first: every button says what it does, each screen shows more and wastes less space, and the same thing has the same name everywhere.
|
|
6
|
+
|
|
7
|
+
## Finding your way around
|
|
8
|
+
|
|
9
|
+
- **A new rail.** The left rail is narrow, with a word under every icon: Create, Home, Projects, Prompts, Templates, Calendar, Channels (once you have two or more) and Settings, then Search, Assistant, Help and Support Slopify at the foot. On a phone the bottom bar has Create in the middle.
|
|
10
|
+
- **Library is split in two.** **Prompts** holds your prompts and intros and outros, with every kind listed down the left. **Templates** holds video and poster templates. PDF looks are under Settings → Production defaults and Create's PDF choice, Pronunciation is under Settings → Voices, and What worked on YouTube is on the channel page. Old addresses still open the right page.
|
|
11
|
+
- The channel picker reads **Showing: All channels** in the header of Home, Projects and Calendar, and only appears with two or more channels.
|
|
12
|
+
|
|
13
|
+
## Your first video
|
|
14
|
+
|
|
15
|
+
- The welcome is a checklist of what a short needs (Article, Narration, Images), each with its state and one button to fix it. The usage notice is a single line at the bottom of the window instead of a dialog, and start at login is asked once.
|
|
16
|
+
- **Create is one screen.** Pick what you're making, type the topic (the box has the cursor already) and press **Make the video**. Each part of the run is one sentence with Change, and anything missing is named once, with Fix. On a phone the Make button stays pinned within reach.
|
|
17
|
+
- **New prompt** opens beside Create and picks the prompt when you save, so you never leave the page. If you skipped the starter templates, Create offers them.
|
|
18
|
+
- **Everything this run will do** shows the whole run in plain sentences before you start.
|
|
19
|
+
|
|
20
|
+
## A finished project
|
|
21
|
+
|
|
22
|
+
- The video, its downloads, the chapters, the shorts and every stage's status are on the first screen. Each section (Article, Narration, Images, Video, Shorts, YouTube, PDF, All files) leads with what it made and a named download.
|
|
23
|
+
- **Edit settings** uses Create's sections and names. **Versions** replaces History.
|
|
24
|
+
- Every action is a visible button: Mark uploaded, Rename, Save as template and Delete. Paid work says so on its button ("Make this image again").
|
|
25
|
+
|
|
26
|
+
## Lists and settings
|
|
27
|
+
|
|
28
|
+
- **Home** shows your finished videos first, with Watch and Download, then what needs you and what's running, step by step.
|
|
29
|
+
- **Projects** rows show a picture, the length, what was made and when, with Open and Download.
|
|
30
|
+
- **Templates** are cards that say what each one makes, with **Make a video** and **Edit template**. Edit template's sections match Create's.
|
|
31
|
+
- **Prompts** searches every kind at once. The editor reads like text, **Insert a keyword** shows exactly what goes in, and every kind has examples to start from.
|
|
32
|
+
- **Settings** is one list of pages. **Keys & services** opens on three lines (who writes, who reads, who draws), each Ready or with a button to set it up. Trash has its own page, and Projects and Prompts show **Trash (n)** when something is in it.
|
|
33
|
+
|
|
34
|
+
## One word per thing
|
|
35
|
+
|
|
36
|
+
- Captions (not subtitles), Narration, Voice, Images, Make … again (not Remake or Regenerate), Copy for YouTube Studio, Pauses and checks, PDF looks, and US spelling throughout.
|
|
37
|
+
- Info buttons are gone where a short line under the setting says it better. A status dot always has its word next to it, and each area has at most one lime button.
|
|
38
|
+
|
|
39
|
+
## Fixes
|
|
40
|
+
|
|
41
|
+
- Typing the topic the moment Create opens no longer drops the starter template's writer and image service.
|
|
42
|
+
- Make the video is ready about a second after you stop typing, instead of three or four.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Slopify 4.2.0
|
|
2
|
+
|
|
3
|
+
Released 8 November 2026.
|
|
4
|
+
|
|
5
|
+
This release is about names: deciding once how each one is said, and finding out — before you pay for a narration, and before you upload — whether the video said them right.
|
|
6
|
+
|
|
7
|
+
## One list decides how a name is said
|
|
8
|
+
|
|
9
|
+
- **Settings → Voices → Name pronunciations** takes a Pronunciation Glossary of your own, one `Term: /IPA/` per line, and its names come before the glossary each article writes. A name listed there sounds the same in every video, however that video's article spelled it out, so `Gygax` never drifts to `Gigax` between episodes. The page says how many names the list pronounces and names any line narration has to skip, by number and reason.
|
|
10
|
+
- **Each project keeps the copy it started with,** like the narration aliases, so editing the list never changes a project behind its back. **Edit settings → Narration → Use pronunciations → Copy the list again** brings the new list in, and only the narration where a changed name appears is made again.
|
|
11
|
+
- **The order, where two disagree:** an alias (it changes the words), then your Name pronunciations, then the article's own Pronunciation Glossary, then other projects' pronunciations while they are shared.
|
|
12
|
+
- **"Use the article's pronunciation glossary" is now "Use pronunciations",** because the one switch covers both your list and the article's glossary. Inworld TTS-2 and TTS-2 Flash read IPA, as before; for any other voice an alias is still the way to fix a word.
|
|
13
|
+
- **One save never replaces another.** Each save carries the version of the list it was made from. When a second window or an agent saved meanwhile, the save is refused with the list as it stands: the page names what changed, keeps what you typed, and offers **Save mine anyway** or **Load theirs instead**.
|
|
14
|
+
|
|
15
|
+
## Does this video say the names right?
|
|
16
|
+
|
|
17
|
+
Two checks on the project page, over files it already has. Neither blocks the run or the upload, neither costs anything, and neither changes a thing.
|
|
18
|
+
|
|
19
|
+
- **Before the narration is paid for,** the **Narration** section says in one line which names this article uses that nothing pronounces — not its own Pronunciation Glossary, not your Name pronunciations list, not the pooled copy of the other projects' glossaries — and **Every mention** opens the table: each name, how often it is said as written and how often as a possessive, proper names first. It is the cheapest moment to settle a name, because adding it to the list costs nothing and the voice has not been paid yet.
|
|
20
|
+
- **It catches the names a glossary looks like it covers and doesn't.** A two-word term is never matched by one of its words said alone, so an article that lists `Belwar Dissengulp` and then says `Belwar` on its own is named. So are possessives and hyphenated names in a project narrated under the old pronunciation rules, which never wrapped either: a project is judged by the rules it was voiced under, not by today's.
|
|
21
|
+
- **After the render, before the upload,** the same line sits on the **YouTube** section and says two things. What the voice was actually sent, counted from the voice script itself — a name with no IPA in front of it there was guessed, whatever the glossary says. And how the finished narration was heard saying each name, read from the per-word confidence the subtitle timing already wrote when the captions were made.
|
|
22
|
+
- **The spread is the signal, not a low score.** A rare name is heard poorly even when it is said perfectly, so only a name whose own mentions disagree with each other is flagged: eight mentions or more, and a spread of 0.30 or more between its tenth and ninetieth percentile. Nothing is listened to again and nothing is re-aligned.
|
|
23
|
+
- **Ordinary English words are left out of both.** A one-word name of four letters or more, written with a capital, that the same text also writes in lower case is a word there and not a name — so a pooled `Hall` stays quiet in a video that also says "hall", while a god called `Torm` does not.
|
|
24
|
+
|
|
25
|
+
## For agents
|
|
26
|
+
|
|
27
|
+
- `get_library_pronunciations` and `set_library_pronunciations` read and write the list, the save carrying `{text, version}`; a 409 names what the list is now and hands it back to save from.
|
|
28
|
+
- `check_pronunciations` answers before the narration, `check_narration_names` after the render.
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"id": "4.2.0",
|
|
4
|
+
"title": "Slopify 4.2.0",
|
|
5
|
+
"version": "4.2.0",
|
|
6
|
+
"date": "2026-11-08"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "4.1.0",
|
|
10
|
+
"title": "Slopify 4.1.0",
|
|
11
|
+
"version": "4.1.0",
|
|
12
|
+
"date": "2026-11-07"
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"id": "4.0.0",
|
|
4
16
|
"title": "Slopify 4.0.0",
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { stageKinds } from "../../kernel/pipeline.js";
|
|
3
|
+
const outputRow = z.object({
|
|
4
|
+
project_id: z.string(),
|
|
5
|
+
role: z.string(),
|
|
6
|
+
count: z.number(),
|
|
7
|
+
duration_ms: z.number().nullable(),
|
|
8
|
+
});
|
|
9
|
+
const stageRow = z.object({
|
|
10
|
+
project_id: z.string(),
|
|
11
|
+
kind: z.enum(stageKinds),
|
|
12
|
+
state: z.string(),
|
|
13
|
+
finished_at: z.string().nullable(),
|
|
14
|
+
});
|
|
15
|
+
const outdatedRow = z.object({ project_id: z.string() });
|
|
16
|
+
// The roles a row names. A sound-only run's length comes from its export.
|
|
17
|
+
const namedRoles = [
|
|
18
|
+
"video",
|
|
19
|
+
"short_video",
|
|
20
|
+
"document_pdf",
|
|
21
|
+
"audio_mp3",
|
|
22
|
+
"audio_m4b",
|
|
23
|
+
"audio_export",
|
|
24
|
+
];
|
|
25
|
+
const audioRoles = new Set(["audio_mp3", "audio_m4b", "audio_export"]);
|
|
26
|
+
export function listingFacts(db) {
|
|
27
|
+
const gathered = new Map();
|
|
28
|
+
const of = (projectId) => {
|
|
29
|
+
let one = gathered.get(projectId);
|
|
30
|
+
if (one === undefined) {
|
|
31
|
+
one = {
|
|
32
|
+
outputs: { video: false, shorts: 0, pdf: false, audio: false },
|
|
33
|
+
videoMs: null,
|
|
34
|
+
audioMs: null,
|
|
35
|
+
finishedAt: null,
|
|
36
|
+
failed: [],
|
|
37
|
+
};
|
|
38
|
+
gathered.set(projectId, one);
|
|
39
|
+
}
|
|
40
|
+
return one;
|
|
41
|
+
};
|
|
42
|
+
const roles = namedRoles.map(() => "?").join(",");
|
|
43
|
+
for (const row of db
|
|
44
|
+
.prepare(`SELECT project_id, role, COUNT(*) AS count, MAX(duration_ms) AS duration_ms FROM outputs WHERE role IN (${roles}) GROUP BY project_id, role`)
|
|
45
|
+
.all(...namedRoles)) {
|
|
46
|
+
const parsed = outputRow.parse(row);
|
|
47
|
+
const one = of(parsed.project_id);
|
|
48
|
+
if (parsed.role === "video") {
|
|
49
|
+
one.outputs.video = true;
|
|
50
|
+
one.videoMs = parsed.duration_ms;
|
|
51
|
+
}
|
|
52
|
+
else if (parsed.role === "short_video")
|
|
53
|
+
one.outputs.shorts = parsed.count;
|
|
54
|
+
else if (parsed.role === "document_pdf")
|
|
55
|
+
one.outputs.pdf = true;
|
|
56
|
+
else if (audioRoles.has(parsed.role)) {
|
|
57
|
+
one.outputs.audio = true;
|
|
58
|
+
if (parsed.duration_ms !== null)
|
|
59
|
+
one.audioMs = Math.max(one.audioMs ?? 0, parsed.duration_ms);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const row of db
|
|
63
|
+
.prepare("SELECT project_id, kind, state, finished_at FROM stages WHERE state IN ('done','failed','provided') ORDER BY rowid")
|
|
64
|
+
.all()) {
|
|
65
|
+
const parsed = stageRow.parse(row);
|
|
66
|
+
const one = of(parsed.project_id);
|
|
67
|
+
if (parsed.finished_at !== null &&
|
|
68
|
+
(one.finishedAt === null || parsed.finished_at > one.finishedAt))
|
|
69
|
+
one.finishedAt = parsed.finished_at;
|
|
70
|
+
if (parsed.state === "failed")
|
|
71
|
+
one.failed.push(parsed.kind);
|
|
72
|
+
}
|
|
73
|
+
const outdated = new Set(db
|
|
74
|
+
.prepare("SELECT DISTINCT revision_outputs.project_id FROM revision_outputs JOIN project_heads ON project_heads.project_id = revision_outputs.project_id AND project_heads.revision_id = revision_outputs.revision_id WHERE revision_outputs.selected = 1 AND revision_outputs.state = 'outdated'")
|
|
75
|
+
.all()
|
|
76
|
+
.map((row) => outdatedRow.parse(row).project_id));
|
|
77
|
+
const facts = new Map();
|
|
78
|
+
for (const [projectId, one] of gathered) {
|
|
79
|
+
const made = one.outputs;
|
|
80
|
+
const ms = one.videoMs ?? one.audioMs;
|
|
81
|
+
facts.set(projectId, {
|
|
82
|
+
...(one.finishedAt === null ? {} : { finishedAt: one.finishedAt }),
|
|
83
|
+
...(ms === null || ms <= 0 ? {} : { durationSeconds: Math.round(ms / 1000) }),
|
|
84
|
+
...(made.video || made.shorts > 0 || made.pdf || made.audio
|
|
85
|
+
? { outputs: { ...made } }
|
|
86
|
+
: {}),
|
|
87
|
+
...(one.failed.length === 0 ? {} : { failedSteps: one.failed }),
|
|
88
|
+
...(outdated.has(projectId) ? { outdated: true } : {}),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
for (const projectId of outdated)
|
|
92
|
+
if (!facts.has(projectId))
|
|
93
|
+
facts.set(projectId, { outdated: true });
|
|
94
|
+
return facts;
|
|
95
|
+
}
|
|
@@ -21,6 +21,7 @@ const standingRow = z.object({
|
|
|
21
21
|
progress_current: z.number().nullable(),
|
|
22
22
|
progress_total: z.number().nullable(),
|
|
23
23
|
source: z.enum(stageSources),
|
|
24
|
+
retry_at: z.string().nullable(),
|
|
24
25
|
admitted: z.number(),
|
|
25
26
|
parked: z.number(),
|
|
26
27
|
});
|
|
@@ -83,14 +84,15 @@ export function listProjects(db) {
|
|
|
83
84
|
.all()
|
|
84
85
|
.map((row) => toProject(projectRow.parse(row)));
|
|
85
86
|
}
|
|
86
|
-
// What 07 Projects needs of every project's stages and no more: the state each one is in
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
87
|
+
// What 07 Projects needs of every project's stages and no more: the state each one is in,
|
|
88
|
+
// how far through itself a running one is, and when one waiting to run again by itself tries
|
|
89
|
+
// (without it, `derive` reads that run as waiting its turn while its own page says Running).
|
|
90
|
+
// One statement for the whole list rather than `stagesOf` per row, and a few columns rather
|
|
91
|
+
// than the whole stage table, because the screen shows a status word and a meter and reads
|
|
92
|
+
// nothing else from a stage. One project's alone when `projectId` is given.
|
|
91
93
|
export function stageStandingsByProject(db, projectId) {
|
|
92
94
|
const grouped = new Map();
|
|
93
|
-
const select = `SELECT project_id, kind, state, progress_current, progress_total, source, ${admittedInItsState("stages")} AS admitted, ${parkedOnReview("stages")} AS parked FROM stages${projectId === undefined ? "" : " WHERE project_id=?"} ORDER BY rowid`;
|
|
95
|
+
const select = `SELECT project_id, kind, state, progress_current, progress_total, source, retry_at, ${admittedInItsState("stages")} AS admitted, ${parkedOnReview("stages")} AS parked FROM stages${projectId === undefined ? "" : " WHERE project_id=?"} ORDER BY rowid`;
|
|
94
96
|
const rows = projectId === undefined ? db.prepare(select).all() : db.prepare(select).all(projectId);
|
|
95
97
|
for (const row of rows) {
|
|
96
98
|
const parsed = standingRow.parse(row);
|
|
@@ -100,6 +102,7 @@ export function stageStandingsByProject(db, projectId) {
|
|
|
100
102
|
progressCurrent: parsed.progress_current,
|
|
101
103
|
progressTotal: parsed.progress_total,
|
|
102
104
|
source: parsed.source,
|
|
105
|
+
...(parsed.retry_at === null ? {} : { retryAt: parsed.retry_at }),
|
|
103
106
|
...(parsed.admitted === 1 ? { admitted: true } : {}),
|
|
104
107
|
...(parsed.parked === 1 ? { parked: true } : {}),
|
|
105
108
|
};
|
|
@@ -88,7 +88,7 @@ export function admit(input) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
if (needsLlmFor(draft, input.keywordUses) && !chosen(draft.llm)) {
|
|
91
|
-
fields.push({ field: "llm", message: "Choose a
|
|
91
|
+
fields.push({ field: "llm", message: "Choose a Service and a Model under Written by." });
|
|
92
92
|
}
|
|
93
93
|
if (sources.article === "generate" && blank(draft.articlePrompt)) {
|
|
94
94
|
fields.push({ field: "articlePrompt", message: "Pick an article prompt." });
|
|
@@ -98,7 +98,7 @@ export function admit(input) {
|
|
|
98
98
|
fields.push(...voicesFields(draft));
|
|
99
99
|
if (sources.audio === "generate") {
|
|
100
100
|
if (voiced === undefined || !chosen(voiced)) {
|
|
101
|
-
fields.push({ field: "audio", message: "Choose a
|
|
101
|
+
fields.push({ field: "audio", message: "Choose a Service and a Model under Read by." });
|
|
102
102
|
}
|
|
103
103
|
else if (voiced.voice.trim() === "") {
|
|
104
104
|
fields.push({ field: "audio.voice", message: "Choose a voice for the narration." });
|
|
@@ -113,7 +113,7 @@ export function admit(input) {
|
|
|
113
113
|
usesShorts(draft) ||
|
|
114
114
|
usesAnimation(draft)) &&
|
|
115
115
|
!chosen(draft.images)) {
|
|
116
|
-
fields.push({ field: "images", message: "Choose
|
|
116
|
+
fields.push({ field: "images", message: "Choose a Service and a Model under Images by." });
|
|
117
117
|
}
|
|
118
118
|
if ((sources.thumbnail === "from_prompt" || sources.thumbnail === "prompt_by_llm") &&
|
|
119
119
|
blank(draft.thumbnailPrompt)) {
|
|
@@ -122,7 +122,7 @@ export function admit(input) {
|
|
|
122
122
|
if (sources.audio === "off" && draft.subtitles !== undefined && draft.subtitles.mode !== "off") {
|
|
123
123
|
fields.push({
|
|
124
124
|
field: "subtitles.mode",
|
|
125
|
-
message: "
|
|
125
|
+
message: "Captions need narration. Turn narration on, or turn captions off.",
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
fields.push(...youtubeDescriptionFields(draft));
|
|
@@ -381,7 +381,7 @@ export function voicesFields(draft) {
|
|
|
381
381
|
!draft.voices.speakers.some((speaker) => speaker.voice.provider === "inworld" && speaker.voice.model === "inworld-tts-2"))
|
|
382
382
|
fields.push({
|
|
383
383
|
field: "narrationPrompt",
|
|
384
|
-
message: "Narration preparation adds delivery cues only to speakers on Inworld's TTS-2 model, and no speaker uses it. Choose that model for a speaker under Speakers, or
|
|
384
|
+
message: "Narration preparation adds delivery cues only to speakers on Inworld's TTS-2 model, and no speaker uses it. Choose that model for a speaker under Speakers, or set Narration preparation to Off under Narration → More narration settings.",
|
|
385
385
|
});
|
|
386
386
|
return fields;
|
|
387
387
|
}
|
|
@@ -489,8 +489,8 @@ export function ambientBedFields(draft, staged) {
|
|
|
489
489
|
function chosen(choice) {
|
|
490
490
|
return choice !== undefined && choice.provider.trim() !== "" && choice.model.trim() !== "";
|
|
491
491
|
}
|
|
492
|
-
// The aliases a run narrates with: its copied
|
|
493
|
-
// on for generated audio, otherwise none. Any voice provider: an alias is plain text.
|
|
492
|
+
// The aliases a run narrates with: its copied Pronunciation list while Use your pronunciation
|
|
493
|
+
// list is on for generated audio, otherwise none. Any voice provider: an alias is plain text.
|
|
494
494
|
export function narrationAliasesOf(draft) {
|
|
495
495
|
return draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
|
|
496
496
|
? (draft.narrationAliases ?? [])
|
|
@@ -7,6 +7,7 @@ import { documentSettingsSchema } from "../document/theme-schema.js";
|
|
|
7
7
|
import { keywordFactsSchema } from "../keywords/schema.js";
|
|
8
8
|
import { narrationAliasesSchema } from "../narration/aliases-schema.js";
|
|
9
9
|
import { chunkModes } from "../narration/chunk.js";
|
|
10
|
+
import { libraryPronunciationsMax } from "../narration/pronunciation.js";
|
|
10
11
|
import { reviewModes, reviewStages } from "../reviews/model.js";
|
|
11
12
|
import { socialSettingsSchema } from "../social/schema.js";
|
|
12
13
|
import { subtitleConfigSchema } from "../subtitles/model.js";
|
|
@@ -72,6 +73,7 @@ export const runDraftSchema = z.object({
|
|
|
72
73
|
})
|
|
73
74
|
.optional(),
|
|
74
75
|
narrationAliases: narrationAliasesSchema.optional(),
|
|
76
|
+
libraryPronunciations: z.string().max(libraryPronunciationsMax).optional(),
|
|
75
77
|
pronunciationRules: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
76
78
|
sharedGlossary: z
|
|
77
79
|
.array(z.object({
|
|
@@ -8,6 +8,7 @@ import { keywordFactsFor } from "../keywords/facts.js";
|
|
|
8
8
|
import { usesFacts } from "../keywords/rules.js";
|
|
9
9
|
import { listNarrationAliases } from "../narration/aliases-library.js";
|
|
10
10
|
import { currentPronunciationRules } from "../narration/pronunciation.js";
|
|
11
|
+
import { readLibraryPronunciations } from "../narration/pronunciations-library.js";
|
|
11
12
|
import { collectSharedGlossary } from "../narration/shared-glossary.js";
|
|
12
13
|
import { admitInitialRevision } from "../rebuild/runtime-admission.js";
|
|
13
14
|
import { adoptBaseline } from "../revisions/adopt.js";
|
|
@@ -117,6 +118,11 @@ projectId) {
|
|
|
117
118
|
const aliases = draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
|
|
118
119
|
? listNarrationAliases(deps.db)
|
|
119
120
|
: [];
|
|
121
|
+
// Settings → Voices → Pronunciation's own glossary as it is now: the names this channel
|
|
122
|
+
// always says the same way, ahead of whatever this article's glossary spells out.
|
|
123
|
+
const pronunciations = usesPronunciationGlossary(draft)
|
|
124
|
+
? readLibraryPronunciations(deps.db).text
|
|
125
|
+
: "";
|
|
120
126
|
// The title may name keywords too ("History: {{Topic}}"), filled like a prompt's, so a
|
|
121
127
|
// template or Play keeps the pattern and each project gets its own title.
|
|
122
128
|
const title = render(draft.title, draft.values).trim() || draft.title;
|
|
@@ -142,6 +148,7 @@ projectId) {
|
|
|
142
148
|
rendered,
|
|
143
149
|
...(shared.length === 0 ? {} : { sharedGlossary: shared }),
|
|
144
150
|
...(aliases.length === 0 ? {} : { narrationAliases: aliases }),
|
|
151
|
+
...(pronunciations === "" ? {} : { libraryPronunciations: pronunciations }),
|
|
145
152
|
pronunciationRules: currentPronunciationRules,
|
|
146
153
|
...(earlier === undefined ? {} : { earlierEpisodes: earlier }),
|
|
147
154
|
...(facts === undefined ? {} : { keywordFacts: facts }),
|
|
@@ -28,8 +28,13 @@ export const stageStateWords = {
|
|
|
28
28
|
export const heldWord = "Waiting for your review";
|
|
29
29
|
export const waitingForAgentWord = "Waiting for the agent";
|
|
30
30
|
export const stoppedWord = "Stopped";
|
|
31
|
-
|
|
31
|
+
// Made ahead by its schedule (Make ahead): everything but the video is made, and the video is
|
|
32
|
+
// put together on the run's day.
|
|
33
|
+
export const preparedWord = "Waiting for its day";
|
|
32
34
|
export const keptAsIsWord = "Kept as is";
|
|
35
|
+
// A running run whose only movement is a step that failed on something time can fix (a rate
|
|
36
|
+
// limit, a dropped connection) and runs again by itself at a set time.
|
|
37
|
+
export const retryingWord = "Waiting to try again";
|
|
33
38
|
export const heldStateWords = [
|
|
34
39
|
heldWord,
|
|
35
40
|
waitingForAgentWord,
|
|
@@ -38,15 +43,25 @@ export const heldStateWords = [
|
|
|
38
43
|
keptAsIsWord,
|
|
39
44
|
];
|
|
40
45
|
// A run that ran and is held: a waiting project with some of its run done (`progress` is the
|
|
41
|
-
// run's share done, `progressOf`) or a step failed,
|
|
42
|
-
// before it
|
|
46
|
+
// run's share done, `progressOf`) or a step failed, one a checkpoint holds (it reached it with
|
|
47
|
+
// every step before it supplied), or one stopped or parked on an agent's review before it got
|
|
48
|
+
// going. One with none done that waits for none of these is Queued, waiting its turn.
|
|
43
49
|
export function heldRun(project) {
|
|
44
50
|
return (project.status === "pending" &&
|
|
45
51
|
(project.progress > 0 ||
|
|
46
52
|
project.stepFailed === true ||
|
|
53
|
+
(project.holding?.length ?? 0) > 0 ||
|
|
47
54
|
project.stopped === true ||
|
|
48
55
|
project.waitingForAgent === true));
|
|
49
56
|
}
|
|
57
|
+
// When the run tries again by itself: the soonest wait of a step that failed on something time
|
|
58
|
+
// can fix, while no step runs. Undefined while one runs (the run reads Running) or none waits.
|
|
59
|
+
export function retryingAt(stages) {
|
|
60
|
+
if (stages.some((stage) => stage.state === "running"))
|
|
61
|
+
return undefined;
|
|
62
|
+
const waits = stages.flatMap((stage) => stage.state === "pending" && typeof stage.retryAt === "string" ? [stage.retryAt] : []);
|
|
63
|
+
return waits.toSorted()[0];
|
|
64
|
+
}
|
|
50
65
|
// Whether the person is the one it waits for (to approve, or to Continue the run): not when it
|
|
51
66
|
// is Kept as is, waits for an agent or for its scheduled day.
|
|
52
67
|
function heldForPerson(project) {
|
|
@@ -78,9 +93,18 @@ export function runningNow(project) {
|
|
|
78
93
|
export function queuedRun(project) {
|
|
79
94
|
return project.status === "pending" && !heldRun(project);
|
|
80
95
|
}
|
|
81
|
-
//
|
|
96
|
+
// A settled project already reads "done" from the projects route (`settledProjects`). A
|
|
97
|
+
// running run that only waits to try a step again says so.
|
|
82
98
|
export function projectStateWord(project) {
|
|
83
|
-
|
|
99
|
+
if (heldRun(project))
|
|
100
|
+
return heldStateWord(project);
|
|
101
|
+
if (retryingRun(project))
|
|
102
|
+
return retryingWord;
|
|
103
|
+
return projectStateWords[project.status];
|
|
104
|
+
}
|
|
105
|
+
// Running, and the one thing it does is wait to try a failed step again at a set time.
|
|
106
|
+
export function retryingRun(project) {
|
|
107
|
+
return project.status === "running" && project.retryAt !== undefined;
|
|
84
108
|
}
|
|
85
109
|
// A run that ran and now waits for the person, held or paused: Home's Needs you, the Projects
|
|
86
110
|
// "Needs you" filter and its waiting-first sort.
|
|
@@ -108,3 +132,8 @@ export function stepFailedWhileWaiting(status, stages) {
|
|
|
108
132
|
export function failedRun(project) {
|
|
109
133
|
return project.status === "failed" || project.stepFailed === true;
|
|
110
134
|
}
|
|
135
|
+
// Everything a run asks of the person, the one set Home's Needs you lists and the Projects
|
|
136
|
+
// "Needs you" filter holds: a decision (approve, Continue the run, resume) or a failure to fix.
|
|
137
|
+
export function needsYou(project) {
|
|
138
|
+
return waitsForPerson(project) || failedRun(project);
|
|
139
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
// The cost sentence on an ask card, from the estimate the preview (Choose what to
|
|
2
|
+
// The cost sentence on an ask card, from the estimate the preview (Choose what to make again) or
|
|
3
3
|
// Play's review already showed for this exact work.
|
|
4
4
|
const money = (value) => `$${value.toFixed(2)}`;
|
|
5
5
|
function range(low, high) {
|
|
@@ -37,7 +37,7 @@ export const turnStates = ["running", "done", "failed", "stopped"];
|
|
|
37
37
|
const providersFix = {
|
|
38
38
|
kind: "settings",
|
|
39
39
|
section: "providers",
|
|
40
|
-
label: "Open Settings →
|
|
40
|
+
label: "Open Settings → Keys & services",
|
|
41
41
|
};
|
|
42
42
|
const signIn = {
|
|
43
43
|
"claude-code": "claude auth login",
|
|
@@ -46,13 +46,20 @@ const signIn = {
|
|
|
46
46
|
function blocked(cli, state) {
|
|
47
47
|
const name = assistantCliNames[cli];
|
|
48
48
|
if (!state.installed)
|
|
49
|
-
return `The assistant can't start: ${name} isn't installed on this computer. Install it, then set it up in Settings →
|
|
49
|
+
return `The assistant can't start: ${name} isn't installed on this computer. Install it, then set it up in Settings → Keys & services.`;
|
|
50
50
|
if (state.issue !== undefined)
|
|
51
51
|
return `The assistant can't start: ${state.issue}`;
|
|
52
52
|
if (state.login === "signed-out")
|
|
53
53
|
return `The assistant can't start: ${name} is signed out. Run "${signIn[cli]}" in a terminal, then send your message again.`;
|
|
54
54
|
return undefined;
|
|
55
55
|
}
|
|
56
|
+
// The CLIs that could answer now, in Auto's order: the panel offers "Answer with" only when
|
|
57
|
+
// there are two to choose between.
|
|
58
|
+
export function readyClis(states, container) {
|
|
59
|
+
if (container)
|
|
60
|
+
return [];
|
|
61
|
+
return assistantClis.filter((cli) => blocked(cli, states[cli]) === undefined);
|
|
62
|
+
}
|
|
56
63
|
// Auto takes Claude Code when it is ready, else Codex. A sign-in that couldn't be read is not
|
|
57
64
|
// a refusal: the CLI says so itself if it really is signed out.
|
|
58
65
|
export function assistantCliOf(states, preference, container) {
|
|
@@ -73,7 +80,7 @@ export function assistantCliOf(states, preference, container) {
|
|
|
73
80
|
return { ok: true, cli };
|
|
74
81
|
return {
|
|
75
82
|
ok: false,
|
|
76
|
-
reason: "The assistant runs on Claude Code or Codex, and neither is ready on this computer. Set up Claude Code or Codex in Settings →
|
|
83
|
+
reason: "The assistant runs on Claude Code or Codex, and neither is ready on this computer. Set up Claude Code or Codex in Settings → Keys & services.",
|
|
77
84
|
fix: providersFix,
|
|
78
85
|
};
|
|
79
86
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { causedBy } from "../../kernel/errors.js";
|
|
2
2
|
import { redact } from "../../kernel/log.js";
|
|
3
3
|
import { asksOf, consumeGrant, decideAsk, openAsk } from "./asks.js";
|
|
4
|
-
import { assistantCliNames, assistantCliOf, } from "./model.js";
|
|
4
|
+
import { assistantCliNames, assistantCliOf, readyClis, } from "./model.js";
|
|
5
5
|
import { screenNames } from "./names.js";
|
|
6
6
|
import { assistantSystemPrompt, turnPrompt } from "./prompt.js";
|
|
7
7
|
import { addMessage, cliPreferenceOf, createSession, latestSessionId, messagesOf, sessionExists, transcriptOf, } from "./repo.js";
|
|
@@ -182,7 +182,7 @@ export function createAssistant(deps) {
|
|
|
182
182
|
if (self.stoppedBy === undefined) {
|
|
183
183
|
const detail = redact(causedBy(error));
|
|
184
184
|
deps.log.write("warn", "assistant.turn", { detail });
|
|
185
|
-
say(sessionId, turnId, "error", `Slopify couldn't run the assistant's CLI: ${detail}. Check it in Settings →
|
|
185
|
+
say(sessionId, turnId, "error", `Slopify couldn't run the assistant's CLI: ${detail}. Check it in Settings → Keys & services, then send your message again.`, { kind: "cli" });
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
if (self.stoppedBy !== undefined) {
|
|
@@ -264,9 +264,11 @@ export function createAssistant(deps) {
|
|
|
264
264
|
tokens: deps.tokens,
|
|
265
265
|
status: async () => {
|
|
266
266
|
const preference = cliPreferenceOf(deps.db);
|
|
267
|
+
const states = await deps.clis();
|
|
267
268
|
return {
|
|
268
269
|
preference,
|
|
269
|
-
choice: assistantCliOf(
|
|
270
|
+
choice: assistantCliOf(states, preference, deps.container),
|
|
271
|
+
ready: readyClis(states, deps.container),
|
|
270
272
|
};
|
|
271
273
|
},
|
|
272
274
|
session: (id) => {
|
|
@@ -51,7 +51,7 @@ export function warningsOf(signals, snoozes, now) {
|
|
|
51
51
|
found.push({
|
|
52
52
|
key: `template-not-ready:${schedule.id}`,
|
|
53
53
|
severity: "problem",
|
|
54
|
-
text: `“${schedule.name}” skips its runs because its template “${schedule.notReadyTemplate}” has no version
|
|
54
|
+
text: `“${schedule.name}” skips its runs because its template “${schedule.notReadyTemplate}” has no version approved for schedules. Open it from Templates with Edit template, then choose More → Approve for schedules.`,
|
|
55
55
|
fix: { kind: "templates", label: "Open Templates" },
|
|
56
56
|
});
|
|
57
57
|
if (schedule.topicsError !== null && schedule.generation !== "off")
|
|
@@ -60,7 +60,7 @@ export function createBackupService(deps) {
|
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
62
|
deps.log.write("warn", "backups.prune", { detail: String(error) });
|
|
63
|
-
detail = `The backup was saved, but older backups in ${folder} could not be removed (${errnoCode(error) ?? "unknown error"}), so more than ${settings.keep} are kept. Check Slopify can delete files in that folder, or pick another Folder in Settings →
|
|
63
|
+
detail = `The backup was saved, but older backups in ${folder} could not be removed (${errnoCode(error) ?? "unknown error"}), so more than ${settings.keep} are kept. Check Slopify can delete files in that folder, or pick another Folder in Settings → Backup & storage.`;
|
|
64
64
|
}
|
|
65
65
|
record({
|
|
66
66
|
...attempt,
|
|
@@ -273,17 +273,17 @@ function named(projects) {
|
|
|
273
273
|
.join(", ");
|
|
274
274
|
return projects.length > 3 ? `${titles} and ${projects.length - 3} more` : titles;
|
|
275
275
|
}
|
|
276
|
-
// What failed, why, and what to change - the sentence Settings →
|
|
276
|
+
// What failed, why, and what to change - the sentence Settings → Backup & storage shows.
|
|
277
277
|
export function explain(error, folder, retries, signal) {
|
|
278
|
-
const fix = "Pick another Folder in Settings →
|
|
278
|
+
const fix = "Pick another Folder in Settings → Backup & storage, or leave it empty to use the default";
|
|
279
279
|
if (signal?.aborted === true && error === signal.reason)
|
|
280
280
|
return "The backup was not saved: Slopify stopped while writing it, and the unfinished file was removed. It runs again after Slopify starts.";
|
|
281
281
|
if (error instanceof NoSpaceError)
|
|
282
|
-
return `The backup was not saved: it needs about ${bytes(error.needed)}, but the disk holding ${folder} has ${bytes(error.free)} free. Free up space there, lower "Keep last" in Settings →
|
|
282
|
+
return `The backup was not saved: it needs about ${bytes(error.needed)}, but the disk holding ${folder} has ${bytes(error.free)} free. Free up space there, lower "Keep last" in Settings → Backup & storage, or pick another Folder.`;
|
|
283
283
|
switch (errnoCode(error)) {
|
|
284
284
|
case "ENOSPC":
|
|
285
285
|
case "EDQUOT":
|
|
286
|
-
return `The backup was not saved: the disk holding ${folder} ran out of space. Free up space there, lower "Keep last" in Settings →
|
|
286
|
+
return `The backup was not saved: the disk holding ${folder} ran out of space. Free up space there, lower "Keep last" in Settings → Backup & storage, or pick another Folder.`;
|
|
287
287
|
case "EACCES":
|
|
288
288
|
case "EPERM":
|
|
289
289
|
case "EROFS":
|
|
@@ -61,7 +61,7 @@ export function generateCastImage(deps, memberId, input) {
|
|
|
61
61
|
}, (error) => {
|
|
62
62
|
const reason = errorText(error);
|
|
63
63
|
deps.log?.write("warn", "channels.cast-image.failed", { detail: reason.slice(0, 500) });
|
|
64
|
-
fail(deps, id, `The picture couldn't be made: ${reason.slice(0, 500)} Check the image
|
|
64
|
+
fail(deps, id, `The picture couldn't be made: ${reason.slice(0, 500)} Check the image service in Settings → Keys & services, then press Make the picture again.`);
|
|
65
65
|
});
|
|
66
66
|
return { ok: true, value: imageRow(deps, id) };
|
|
67
67
|
}
|
|
@@ -10,7 +10,7 @@ import { castKinds } from "./model.js";
|
|
|
10
10
|
const id = z.uuid();
|
|
11
11
|
const colour = z
|
|
12
12
|
.string()
|
|
13
|
-
.regex(/^#[0-9a-fA-F]{6}$/, "Use a
|
|
13
|
+
.regex(/^#[0-9a-fA-F]{6}$/, "Use a color like #FFD700.")
|
|
14
14
|
.transform((value) => value.toUpperCase());
|
|
15
15
|
const optionalText = (max) => z.string().trim().max(max).optional();
|
|
16
16
|
const fontId = z
|
|
@@ -17,7 +17,7 @@ const importSchema = z
|
|
|
17
17
|
// The preview's "Keep only titles containing…" text, remembered for the next import.
|
|
18
18
|
filter: z
|
|
19
19
|
.string()
|
|
20
|
-
.max(importFilterMax, `The filter is too long to remember. Shorten "Keep only titles containing…" on the channel's
|
|
20
|
+
.max(importFilterMax, `The filter is too long to remember. Shorten "Keep only titles containing…" on the channel's Titles to avoid tab to at most ${importFilterMax} characters.`)
|
|
21
21
|
.optional(),
|
|
22
22
|
})
|
|
23
23
|
.strict();
|
|
@@ -24,8 +24,8 @@ export function validateLocalProviderChanges(changes, current) {
|
|
|
24
24
|
"Install this command-line tool on your computer and sign in to it, then try again.")
|
|
25
25
|
: provider.readiness.kind === "local"
|
|
26
26
|
? (provider.readiness.issue ??
|
|
27
|
-
"No speech program was found on this computer. Install
|
|
28
|
-
: "Add this provider's API key in Settings →
|
|
27
|
+
"No speech program was found on this computer. Install the free program eSpeak NG, or choose a voice service with a key in Settings → Keys & services.")
|
|
28
|
+
: "Add this provider's API key in Settings → Keys & services first.",
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -14,7 +14,7 @@ export function dailySampleBudget(db, log) {
|
|
|
14
14
|
export function budgetSentence(total, budget) {
|
|
15
15
|
return `Waiting: this sample would take today's sample spending to $${total.toFixed(2)}, over the $${budget.toFixed(2)} a day samples may spend. Press Make sample to make it anyway; the limit starts again at midnight.`;
|
|
16
16
|
}
|
|
17
|
-
// Settings →
|
|
17
|
+
// Settings → Production defaults shows it beside the budget: what design samples spent since local
|
|
18
18
|
// midnight and what is left of today's budget.
|
|
19
19
|
export function designBudgetRoom(db, now, timeZone) {
|
|
20
20
|
const budget = dailySampleBudget(db);
|
|
@@ -11,7 +11,7 @@ import { settleStep, stepById } from "./steps-repo.js";
|
|
|
11
11
|
// The background half of the runner: each "making" step's provider call, at most two at once,
|
|
12
12
|
// settled into its step (and made the design's current version when it lands).
|
|
13
13
|
export const maxConcurrentDesignCalls = 2;
|
|
14
|
-
export const noProviderMessage = "No image
|
|
14
|
+
export const noProviderMessage = "No image service is set up. Add one in Settings → Keys & services, then press the button again.";
|
|
15
15
|
export function createDesignCalls(deps) {
|
|
16
16
|
let running = 0;
|
|
17
17
|
const waiting = [];
|
|
@@ -119,6 +119,6 @@ function failedSentence(error, reason) {
|
|
|
119
119
|
if (kind === "unsupported")
|
|
120
120
|
return `The picture couldn't be made: ${reason}${reason.includes("Made with") ? "" : " Choose another image model under Made with … · change."}`;
|
|
121
121
|
if (kind === "auth" || kind === "missing_key")
|
|
122
|
-
return `The picture couldn't be made: ${reason}${/Settings/.test(reason) ? "" : " Check the provider's key in Settings →
|
|
122
|
+
return `The picture couldn't be made: ${reason}${/Settings/.test(reason) ? "" : " Check the provider's key in Settings → Keys & services, then press Try again."}`;
|
|
123
123
|
return `The picture couldn't be made: ${reason} Press Try again, or choose another image model under Made with … · change.`;
|
|
124
124
|
}
|