@gentbajko/slopify 4.0.0 → 4.1.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 +3 -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/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 +7 -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/index.json +6 -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/state-words.js +34 -5
- package/dist/slices/assistant/cost.js +1 -1
- 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.js +3 -2
- 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-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 +94 -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 +14 -14
- 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 +182 -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-Cpa58woP.js +459 -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
|
@@ -13,6 +13,17 @@ export { slotLabels, usageKinds, usageSlots } from "./model.js";
|
|
|
13
13
|
function familyOf(slot) {
|
|
14
14
|
return slot === "llm" ? "llm" : slot === "audio" ? "tts" : "image";
|
|
15
15
|
}
|
|
16
|
+
// A Create draft's name as Create's header says it: its title once every keyword in it is
|
|
17
|
+
// filled, else "Untitled draft". Never a raw {{Topic}}.
|
|
18
|
+
function draftName(title, values) {
|
|
19
|
+
const filled = title
|
|
20
|
+
.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (keyword, name) => {
|
|
21
|
+
const value = values[name]?.trim() ?? "";
|
|
22
|
+
return value === "" ? keyword : value;
|
|
23
|
+
})
|
|
24
|
+
.trim();
|
|
25
|
+
return filled === "" || filled.includes("{{") ? "Untitled draft" : filled;
|
|
26
|
+
}
|
|
16
27
|
// The model choices a Play form (a draft or a template) makes.
|
|
17
28
|
export function formChoices(form) {
|
|
18
29
|
const edit = form.videoEdit;
|
|
@@ -155,7 +166,7 @@ export function choiceSites(db) {
|
|
|
155
166
|
choices: formChoices(template.document.form),
|
|
156
167
|
blocked: head === row.template_version
|
|
157
168
|
? null
|
|
158
|
-
: `This schedule runs an older version of the template "${template.name}". In
|
|
169
|
+
: `This schedule runs an older version of the template "${template.name}". In Calendar → Schedules, choose Edit, pick the template again so it runs the latest version, then save.`,
|
|
159
170
|
});
|
|
160
171
|
}
|
|
161
172
|
for (const row of draftRows(db)) {
|
|
@@ -167,7 +178,7 @@ export function choiceSites(db) {
|
|
|
167
178
|
sites.push({
|
|
168
179
|
kind: "draft",
|
|
169
180
|
id: row.id,
|
|
170
|
-
name: row.title.
|
|
181
|
+
name: draftName(row.title, document.form.values),
|
|
171
182
|
choices: formChoices(document.form),
|
|
172
183
|
blocked: null,
|
|
173
184
|
});
|
|
@@ -2,6 +2,7 @@ import { mkdirSync, rmSync } from "node:fs";
|
|
|
2
2
|
import { open, stat } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { errorText } from "../../kernel/errors.js";
|
|
5
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
5
6
|
import { runFfmpeg } from "../video/ffmpeg.js";
|
|
6
7
|
import { breathRate, scanBreaths, softenChunk } from "./breaths-detect.js";
|
|
7
8
|
// Soften breaths, made in the join (`breaths-model.ts` says why there). Each narration piece is
|
|
@@ -39,7 +40,7 @@ export async function softenBreaths(run, files, directory) {
|
|
|
39
40
|
catch (error) {
|
|
40
41
|
if (run.signal.aborted)
|
|
41
42
|
throw error;
|
|
42
|
-
throw new Error(`Soften breaths could not process narration piece ${String(index + 1)} (${errorText(error)}), so the narration can't be joined together. Use Try again; if it fails again, turn off Soften breaths in
|
|
43
|
+
throw new Error(`Soften breaths could not process narration piece ${String(index + 1)} (${errorText(error)}), so the narration can't be joined together. Use Try again; if it fails again, turn off Soften breaths in ${editSettings.sound} and use Save changes, then use Download diagnostics in Settings and report it.`, { cause: error });
|
|
43
44
|
}
|
|
44
45
|
finally {
|
|
45
46
|
rmSync(raw, { force: true });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { errorText } from "../../kernel/errors.js";
|
|
4
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
4
5
|
import { runFfmpeg } from "../video/ffmpeg.js";
|
|
5
6
|
import { softenBreaths } from "./breaths.js";
|
|
6
7
|
import { deEsser } from "./breaths-model.js";
|
|
@@ -37,7 +38,7 @@ export async function deEssPieces(run, files, directory) {
|
|
|
37
38
|
catch (error) {
|
|
38
39
|
if (run.signal.aborted)
|
|
39
40
|
throw error;
|
|
40
|
-
throw new Error(`Soften S sounds could not process narration piece ${String(index + 1)} (${errorText(error)}), so the narration can't be joined together. Use Try again; if it fails again, turn off Soften S sounds in
|
|
41
|
+
throw new Error(`Soften S sounds could not process narration piece ${String(index + 1)} (${errorText(error)}), so the narration can't be joined together. Use Try again; if it fails again, turn off Soften S sounds in ${editSettings.sound} and use Save changes, then use Download diagnostics in Settings and report it.`, { cause: error });
|
|
41
42
|
}
|
|
42
43
|
written.push(output);
|
|
43
44
|
}
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
// A paragraph's minimum is separate and 0 (the sentence minimum only) unless set: the pause
|
|
10
10
|
// between paragraphs is mostly the narration prompt's and the voice's to make.
|
|
11
11
|
//
|
|
12
|
-
// Browser-safe:
|
|
12
|
+
// Browser-safe: Create and Edit settings read these limits. A run without either has neither on
|
|
13
13
|
// its config, which is what every project saved before them was, and the joins keep their
|
|
14
14
|
// fingerprints then.
|
|
15
|
+
import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
|
|
15
16
|
// ceiling: a quarter second reads brisk, 0.35 to 0.45 natural or documentary, 0.5 to 0.8 an
|
|
16
17
|
// audiobook's relaxed pace, and above 0.8 sleep content; beyond 2 s it stops sounding like a
|
|
17
18
|
// pause between sentences.
|
|
@@ -33,7 +34,7 @@ export function pauseProblem(value, name) {
|
|
|
33
34
|
value <= pauseSecondsMax &&
|
|
34
35
|
Math.abs(steps - Math.round(steps)) < 1e-6
|
|
35
36
|
? undefined
|
|
36
|
-
: `The pause between ${name} must be between 0 and ${String(pauseSecondsMax)} seconds, in steps of ${String(pauseSecondsStep)}. Change it under
|
|
37
|
+
: `The pause between ${name} must be between 0 and ${String(pauseSecondsMax)} seconds, in steps of ${String(pauseSecondsStep)}. Change it under ${createRows.video}, or ${editSettings.sound}.`;
|
|
37
38
|
}
|
|
38
39
|
export function pauseFields(config) {
|
|
39
40
|
const fields = [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { remark } from "remark";
|
|
2
2
|
import remarkGfm from "remark-gfm";
|
|
3
3
|
import { internalError } from "../../kernel/errors.js";
|
|
4
|
+
import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
|
|
4
5
|
import { remembered } from "../../kernel/remembered.js";
|
|
5
6
|
export const currentPronunciationRules = 2;
|
|
6
7
|
export function pronunciationRulesOf(config) {
|
|
@@ -154,7 +155,7 @@ function parseGlossary(markdown, language, rules = currentPronunciationRules) {
|
|
|
154
155
|
export function skippedGlossaryNotice(skipped, unpronounced = 0) {
|
|
155
156
|
if (skipped.length === 0 && unpronounced === 0)
|
|
156
157
|
return null;
|
|
157
|
-
const where =
|
|
158
|
+
const where = `in the Pronunciation Glossary at the end of the article in ${editSettings.article}`;
|
|
158
159
|
const blank = unpronounced === 0
|
|
159
160
|
? ""
|
|
160
161
|
: `${unpronounced === 1 ? "1 name has" : `${unpronounced} names have`} no pronunciation given and ${unpronounced === 1 ? "is" : "are"} read as written. To give ${unpronounced === 1 ? "it one" : "them one"}, fill in its IPA ${where}.`;
|
|
@@ -179,7 +180,7 @@ export function skippedSpeakerPronunciationsNotice(rows) {
|
|
|
179
180
|
return null;
|
|
180
181
|
const parts = rows.map((row) => `${row.speaker}: ${row.skipped.map((one) => `entry ${one.row}: ${one.reason}`).join("; ")}`);
|
|
181
182
|
return (`Speaker pronunciations: some entries are skipped and read as ordinary text (${parts.join(". ")}). ` +
|
|
182
|
-
|
|
183
|
+
`The rest are used. To use them, fix those entries under Speakers → Pronunciations for that speaker (${createRows.narration}, or ${editSettings.narration}).`);
|
|
183
184
|
}
|
|
184
185
|
// Remembered (`kernel/remembered.ts`): every plan rebuild matches the whole narration again.
|
|
185
186
|
export const pronunciationMatches = remembered(1024, (source, entries, possessives) => `${possessives === true ? "p" : "-"}\u0000${source}\u0000${JSON.stringify(entries)}`, matchPronunciations);
|
|
@@ -4,7 +4,7 @@ import { stepFailedWhileWaiting } from "../admission/state-words.js";
|
|
|
4
4
|
import { lastStarter } from "../agents/repo.js";
|
|
5
5
|
import { projectHolds } from "../reviews/holds.js";
|
|
6
6
|
import { isTemplateSample } from "../template-studio/repo.js";
|
|
7
|
-
import {
|
|
7
|
+
import { settledProjects } from "../uploads/repo.js";
|
|
8
8
|
import { settledStatus } from "../uploads/rules.js";
|
|
9
9
|
import { atItsDay, failuresOf } from "./rules.js";
|
|
10
10
|
// What a notification says about a project, read at the moment it goes out, by the rule the
|
|
@@ -14,7 +14,7 @@ export function noticeSubject(db, projectId) {
|
|
|
14
14
|
if (project === undefined || isTemplateSample(db, projectId))
|
|
15
15
|
return undefined;
|
|
16
16
|
const stages = stagesOf(db, projectId);
|
|
17
|
-
const settled =
|
|
17
|
+
const settled = settledProjects(db, projectId);
|
|
18
18
|
const holds = projectHolds(db, { projectId, standings: new Map([[projectId, stages]]), settled });
|
|
19
19
|
const status = settledStatus(derive(stages, project.paused), settled.has(projectId));
|
|
20
20
|
const failures = failuresOf(stages);
|
|
@@ -12,7 +12,7 @@ import { projectDir } from "../storage/layout.js";
|
|
|
12
12
|
// ceiling: one pass per layer of the step graph (text, narration, timing, picks, prompts,
|
|
13
13
|
// images, renders); far more than any plan has.
|
|
14
14
|
const rebindPasses = 40;
|
|
15
|
-
export function copyProject(deps, sourceId, title) {
|
|
15
|
+
export function copyProject(deps, sourceId, title, { renamed = false } = {}) {
|
|
16
16
|
const source = projectDir(deps.paths, sourceId);
|
|
17
17
|
const tables = projectTables.map((table) => [table, projectRows(deps.db, table, sourceId)]);
|
|
18
18
|
const ids = new Map([[sourceId, deps.ids.next()]]);
|
|
@@ -34,6 +34,9 @@ export function copyProject(deps, sourceId, title) {
|
|
|
34
34
|
const projectId = ids.get(sourceId) ?? sourceId;
|
|
35
35
|
const target = projectDir(deps.paths, projectId);
|
|
36
36
|
const at = deps.clock.now().toISOString();
|
|
37
|
+
const named = (row) => renamed && typeof row.config === "string"
|
|
38
|
+
? { ...row, config: renameConfig(row.config, title) }
|
|
39
|
+
: row;
|
|
37
40
|
try {
|
|
38
41
|
for (const file of listFiles(source)) {
|
|
39
42
|
if (file === projectMarker)
|
|
@@ -52,7 +55,11 @@ export function copyProject(deps, sourceId, title) {
|
|
|
52
55
|
column,
|
|
53
56
|
typeof value === "string" ? remap(value) : value,
|
|
54
57
|
]));
|
|
55
|
-
insert(deps.db, table, table === "projects"
|
|
58
|
+
insert(deps.db, table, table === "projects"
|
|
59
|
+
? { ...named(copied), title, created_at: at, updated_at: at }
|
|
60
|
+
: table === "project_revisions"
|
|
61
|
+
? named(copied)
|
|
62
|
+
: copied);
|
|
56
63
|
}
|
|
57
64
|
rebind(deps, projectId);
|
|
58
65
|
});
|
|
@@ -63,6 +70,16 @@ export function copyProject(deps, sourceId, title) {
|
|
|
63
70
|
}
|
|
64
71
|
return { projectId };
|
|
65
72
|
}
|
|
73
|
+
// A stored config (JSON) under a new title, keeping what it was made about.
|
|
74
|
+
function renameConfig(config, title) {
|
|
75
|
+
const parsed = JSON.parse(config);
|
|
76
|
+
if (typeof parsed !== "object" || parsed === null || !("title" in parsed))
|
|
77
|
+
return config;
|
|
78
|
+
const before = parsed;
|
|
79
|
+
if (typeof before.title !== "string" || before.title === title)
|
|
80
|
+
return config;
|
|
81
|
+
return JSON.stringify({ ...before, title, subjectTitle: before.subjectTitle ?? before.title });
|
|
82
|
+
}
|
|
66
83
|
// Re-stamps the copy's finished steps with the fingerprints its own ids give them.
|
|
67
84
|
function rebind(deps, projectId) {
|
|
68
85
|
const revisionId = currentRevisionId(deps.db, projectId);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { readinessIsUsable } from "../../kernel/ports/model.js";
|
|
2
1
|
import { templateById } from "../project-templates/repo.js";
|
|
2
|
+
import { listVoices } from "../settings/repo.js";
|
|
3
3
|
import { notTemplateSample } from "../template-studio/repo.js";
|
|
4
|
-
import { starterPacks } from "./packs.js";
|
|
4
|
+
import { starterPacks, starterSet } from "./packs.js";
|
|
5
|
+
import { shortReadiness } from "./quick-short.js";
|
|
5
6
|
import { sampleProjectIds } from "./sample.js";
|
|
6
7
|
import { firstRunDismissed, readPackRecords } from "./state.js";
|
|
7
8
|
// The first-run screen shows on a fresh install and never again once it is skipped or a real
|
|
8
9
|
// project (anything but the samples and the template studio's hidden projects) exists. Reading it writes nothing: a real project that
|
|
9
10
|
// exists before the screen was recorded as done is reported as `settle`, and the client
|
|
10
11
|
// records it with POST /dismiss, so deleting every project later does not bring it back.
|
|
11
|
-
export function firstRunView(db, statuses
|
|
12
|
+
export function firstRunView(db, statuses,
|
|
13
|
+
// How to install the computer's voice here, for the checklist's voice line.
|
|
14
|
+
install) {
|
|
12
15
|
const samples = sampleProjectIds(db);
|
|
13
16
|
const real = db
|
|
14
17
|
.prepare(`SELECT 1 FROM projects WHERE id IS NOT ? AND id IS NOT ? AND id IS NOT ? AND ${notTemplateSample()} LIMIT 1`)
|
|
@@ -18,10 +21,15 @@ export function firstRunView(db, statuses) {
|
|
|
18
21
|
return {
|
|
19
22
|
show: !real && !dismissed,
|
|
20
23
|
settle: real && !dismissed,
|
|
21
|
-
voice
|
|
24
|
+
// Every starter pack suggests the same voice, so the starter set's stands for all of them.
|
|
25
|
+
needs: shortReadiness({
|
|
26
|
+
statuses,
|
|
27
|
+
voices: listVoices(db),
|
|
28
|
+
suggested: starterSet.voice,
|
|
29
|
+
install,
|
|
30
|
+
}),
|
|
22
31
|
sampleProjectId: samples.library,
|
|
23
32
|
samples,
|
|
24
|
-
clis: detectedClis(statuses),
|
|
25
33
|
// A pack counts as added only with its template: the first short adds a pack's prompts and
|
|
26
34
|
// voice but no template, and Add pack must still be able to add that template later (or
|
|
27
35
|
// again, after it was deleted).
|
|
@@ -38,36 +46,3 @@ export function firstRunView(db, statuses) {
|
|
|
38
46
|
}),
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
|
-
// Who would narrate a first short: a keyed voice provider when one has a key, else the
|
|
42
|
-
// computer's own voice when a speech program was found.
|
|
43
|
-
export function firstVoice(statuses) {
|
|
44
|
-
const keyed = statuses.find((row) => row.family === "tts" && row.readiness.kind === "keyed" && readinessIsUsable(row.readiness));
|
|
45
|
-
const system = statuses.find((row) => row.readiness.kind === "local")?.readiness;
|
|
46
|
-
const local = system?.kind === "local" ? system : undefined;
|
|
47
|
-
return {
|
|
48
|
-
keyed: keyed?.displayName ?? null,
|
|
49
|
-
system: {
|
|
50
|
-
available: local?.available === true,
|
|
51
|
-
engine: local?.engine ?? null,
|
|
52
|
-
issue: local?.issue ?? null,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// What this machine can make without a key: each agent CLI Slopify found, and whether it
|
|
57
|
-
// answers (installed, a supported version, signed in as far as the probe can tell).
|
|
58
|
-
export function detectedClis(statuses) {
|
|
59
|
-
return ["claude-code", "codex", "gemini"].map((id) => {
|
|
60
|
-
const status = statuses.find((row) => row.id === id);
|
|
61
|
-
const readiness = status?.readiness;
|
|
62
|
-
const cli = readiness?.kind === "cli" ? readiness : undefined;
|
|
63
|
-
return {
|
|
64
|
-
id,
|
|
65
|
-
name: status?.displayName ?? id,
|
|
66
|
-
installed: cli?.installed === true,
|
|
67
|
-
ready: cli?.installed === true && cli.issue === undefined,
|
|
68
|
-
version: cli?.version ?? null,
|
|
69
|
-
issue: cli?.issue ?? null,
|
|
70
|
-
draws: id === "codex",
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
}
|
|
@@ -5,6 +5,7 @@ import { nameMax } from "../library/model.js";
|
|
|
5
5
|
import { insertPrompt, promptById, promptByName } from "../library/repo.js";
|
|
6
6
|
import { templateById, templateSummaries } from "../project-templates/repo.js";
|
|
7
7
|
import { createTemplate } from "../project-templates/service.js";
|
|
8
|
+
import { readProviderDefaults } from "../settings/first-run.js";
|
|
8
9
|
import { insertVoice, listVoices } from "../settings/repo.js";
|
|
9
10
|
import { packById } from "./packs.js";
|
|
10
11
|
import { readPackRecords, writePackRecord } from "./state.js";
|
|
@@ -46,7 +47,7 @@ export function installPack(deps, packId, options = { template: true }) {
|
|
|
46
47
|
const created = createTemplate(deps, {
|
|
47
48
|
id,
|
|
48
49
|
name: freeTemplateName(deps, `${pack.name} starter`),
|
|
49
|
-
document: packTemplate(pack, names, voice.voiceId),
|
|
50
|
+
document: packTemplate(pack, names, voice.voiceId, readProviderDefaults(deps.db)),
|
|
50
51
|
});
|
|
51
52
|
if (!created.ok)
|
|
52
53
|
throw new Error(internalError(`the ${pack.name} pack's template was refused: ${created.reason}`));
|
|
@@ -10,6 +10,25 @@ export const sampleProjectsSchema = z.object({
|
|
|
10
10
|
audiobook: z.string().nullable(),
|
|
11
11
|
podcast: z.string().nullable(),
|
|
12
12
|
});
|
|
13
|
+
// What a 60-second short needs that nothing on this computer does yet, with what fixes it. The
|
|
14
|
+
// first-run checklist and the refusal of "Make a 60-second short" say the same gaps.
|
|
15
|
+
export const shortGapSchema = z.object({
|
|
16
|
+
need: z.enum(["text", "voice", "images"]),
|
|
17
|
+
// What is missing and what fixes it, in a sentence or two.
|
|
18
|
+
message: z.string(),
|
|
19
|
+
// A key for that part, or (the voice) a voice saved for the voice service that has a key.
|
|
20
|
+
fix: z.enum(["key", "saved-voice"]),
|
|
21
|
+
// The voice only: how to install the computer's own free voice from a terminal, where that
|
|
22
|
+
// can be done.
|
|
23
|
+
install: z.object({ text: z.string(), commands: z.array(z.string()) }).optional(),
|
|
24
|
+
});
|
|
25
|
+
// Who does one part of a first short, by name ("Claude Code CLI", "OpenAI", "eSpeak NG"), and
|
|
26
|
+
// how: with the person's own sign-in to a tool on this computer, a saved key, or (the voice)
|
|
27
|
+
// the computer's own speech. Null while nothing can.
|
|
28
|
+
const shortPartSchema = z.object({
|
|
29
|
+
by: z.string().nullable(),
|
|
30
|
+
how: z.enum(["sign-in", "key", "computer"]).nullable(),
|
|
31
|
+
});
|
|
13
32
|
export const firstRunViewSchema = z
|
|
14
33
|
.object({
|
|
15
34
|
show: z.boolean(),
|
|
@@ -17,31 +36,17 @@ export const firstRunViewSchema = z
|
|
|
17
36
|
// with POST /dismiss, so deleting every project later does not bring the screen back.
|
|
18
37
|
// Reading this view writes nothing.
|
|
19
38
|
settle: z.boolean(),
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
engine: z.string().nullable(),
|
|
28
|
-
// Why none can be used, with the fix.
|
|
29
|
-
issue: z.string().nullable(),
|
|
30
|
-
}),
|
|
39
|
+
// The three things a short needs (the script, a voice and the images), each with who would
|
|
40
|
+
// do it, and the gaps, as "Make a 60-second short" would refuse them.
|
|
41
|
+
needs: z.object({
|
|
42
|
+
text: shortPartSchema,
|
|
43
|
+
voice: shortPartSchema,
|
|
44
|
+
images: shortPartSchema,
|
|
45
|
+
gaps: z.array(shortGapSchema),
|
|
31
46
|
}),
|
|
32
47
|
// The Library of Alexandria's project, as before the demos; `samples` has all three.
|
|
33
48
|
sampleProjectId: z.string().nullable(),
|
|
34
49
|
samples: sampleProjectsSchema,
|
|
35
|
-
clis: z.array(z.object({
|
|
36
|
-
id: z.enum(["claude-code", "codex", "gemini"]),
|
|
37
|
-
name: z.string(),
|
|
38
|
-
installed: z.boolean(),
|
|
39
|
-
ready: z.boolean(),
|
|
40
|
-
version: z.string().nullable(),
|
|
41
|
-
issue: z.string().nullable(),
|
|
42
|
-
// Codex draws the images too.
|
|
43
|
-
draws: z.boolean(),
|
|
44
|
-
})),
|
|
45
50
|
packs: z.array(z.object({
|
|
46
51
|
id: z.string(),
|
|
47
52
|
name: z.string(),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The model a service starts with when Slopify picks it for the person (a first short, the
|
|
2
|
+
// writer and voice Create fills in): a fast, capable default rather than the first listed,
|
|
3
|
+
// which can be the slowest or the smallest. One rule, so a service names the same model
|
|
4
|
+
// wherever it was picked for the person.
|
|
5
|
+
const preferred = {
|
|
6
|
+
"claude-code": /sonnet/i,
|
|
7
|
+
"openai-tts": /^gpt-4o-mini-tts$/,
|
|
8
|
+
};
|
|
9
|
+
export function isPreferredModel(provider, model) {
|
|
10
|
+
return preferred[provider]?.test(model) === true;
|
|
11
|
+
}
|
|
12
|
+
export function preferredModel(provider, models) {
|
|
13
|
+
return models.find((model) => isPreferredModel(provider, model.id)) ?? models[0];
|
|
14
|
+
}
|
|
@@ -2,18 +2,82 @@ import { readinessIsUsable } from "../../kernel/ports/model.js";
|
|
|
2
2
|
import { titleMax } from "../admission/rules.js";
|
|
3
3
|
import { defaultSentencePauseSeconds } from "../narration/pauses-model.js";
|
|
4
4
|
import { systemVoiceProvider } from "../settings/model.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { preferredModel } from "./preferred-model.js";
|
|
6
|
+
// Who is asked first for each part: the CLIs before a key for the text, Codex before a key for
|
|
7
|
+
// the images.
|
|
8
|
+
const textOrder = ["claude-code", "codex", "gemini", "openrouter"];
|
|
9
|
+
const imageOrder = ["codex-image", "openai-image", "google-image", "fal", "replicate"];
|
|
7
10
|
const keyedVoices = ["openai-tts", "elevenlabs", "cartesia", "inworld"];
|
|
8
|
-
// The first model of these providers is the one a first short uses, unless one of these is
|
|
9
|
-
// listed: a fast, capable default rather than the slowest or the smallest.
|
|
10
|
-
const preferred = {
|
|
11
|
-
"claude-code": /sonnet/i,
|
|
12
|
-
"openai-tts": /^gpt-4o-mini-tts$/,
|
|
13
|
-
};
|
|
14
11
|
function ready(status) {
|
|
15
12
|
return status !== undefined && readinessIsUsable(status.readiness);
|
|
16
13
|
}
|
|
14
|
+
const statusOf = (statuses) => (id) => statuses.find((row) => row.id === id);
|
|
15
|
+
// The keyed voice a first short narrates with: a saved voice of a voice service with a key,
|
|
16
|
+
// else the voice the pack suggests when its service has a key.
|
|
17
|
+
function keyedVoice(statuses, voices, suggested) {
|
|
18
|
+
const status = statusOf(statuses);
|
|
19
|
+
const saved = voices.find((voice) => voice.provider !== systemVoiceProvider && ready(status(voice.provider)));
|
|
20
|
+
if (saved !== undefined)
|
|
21
|
+
return { provider: saved.provider, voiceId: saved.voiceId };
|
|
22
|
+
return ready(status(suggested.provider))
|
|
23
|
+
? { provider: suggested.provider, voiceId: suggested.voiceId }
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
// One gap per part nothing can do, in plain words with what fixes it. The voice names a voice
|
|
27
|
+
// service that has a key but no saved voice, since a key alone is not the fix then.
|
|
28
|
+
function gapOf(need, statuses, install) {
|
|
29
|
+
if (need === "text")
|
|
30
|
+
return {
|
|
31
|
+
need,
|
|
32
|
+
fix: "key",
|
|
33
|
+
message: "Nothing can write the article yet. Sign in to Claude Code, Codex or Antigravity on this computer, or add an OpenRouter key.",
|
|
34
|
+
};
|
|
35
|
+
if (need === "images")
|
|
36
|
+
return {
|
|
37
|
+
need,
|
|
38
|
+
fix: "key",
|
|
39
|
+
message: "Nothing can make the images yet. Add an image key (OpenAI, Google, fal.ai or Replicate), or sign in to Codex on this computer.",
|
|
40
|
+
};
|
|
41
|
+
const status = statusOf(statuses);
|
|
42
|
+
const unsaved = keyedVoices.map(status).find((row) => ready(row));
|
|
43
|
+
if (unsaved !== undefined)
|
|
44
|
+
return {
|
|
45
|
+
need,
|
|
46
|
+
fix: "saved-voice",
|
|
47
|
+
message: `${unsaved.displayName} has a key but no voice saved yet. Save one in Settings → Voices, or add an OpenAI key, which works at once.`,
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
need,
|
|
51
|
+
fix: "key",
|
|
52
|
+
message: "Nothing can read the article aloud yet. Add a voice key: an OpenAI key works at once with the voices Slopify sets up.",
|
|
53
|
+
...(install === undefined ? {} : { install: { ...install, commands: [...install.commands] } }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// What a first short would use on this computer and what it lacks, without asking any provider
|
|
57
|
+
// for its models: the first-run checklist, which must agree with the refusal below.
|
|
58
|
+
export function shortReadiness(input) {
|
|
59
|
+
const status = statusOf(input.statuses);
|
|
60
|
+
const part = (row) => row === undefined
|
|
61
|
+
? { by: null, how: null }
|
|
62
|
+
: { by: row.displayName, how: row.readiness.kind === "cli" ? "sign-in" : "key" };
|
|
63
|
+
const text = part(textOrder.map(status).find((row) => ready(row)));
|
|
64
|
+
const images = part(imageOrder.map(status).find((row) => ready(row)));
|
|
65
|
+
const keyed = keyedVoice(input.statuses, input.voices, input.suggested);
|
|
66
|
+
const system = status(systemVoiceProvider)?.readiness;
|
|
67
|
+
const voice = keyed !== undefined
|
|
68
|
+
? part(status(keyed.provider))
|
|
69
|
+
: system?.kind === "local" && system.available
|
|
70
|
+
? { by: system.engine ?? "System voice", how: "computer" }
|
|
71
|
+
: { by: null, how: null };
|
|
72
|
+
const gaps = [
|
|
73
|
+
["text", text],
|
|
74
|
+
["images", images],
|
|
75
|
+
["voice", voice],
|
|
76
|
+
]
|
|
77
|
+
.filter(([, done]) => done.by === null)
|
|
78
|
+
.map(([need]) => gapOf(need, input.statuses, input.install));
|
|
79
|
+
return { text, voice, images, gaps };
|
|
80
|
+
}
|
|
17
81
|
async function firstModel(modelsFor, provider, family) {
|
|
18
82
|
let models;
|
|
19
83
|
try {
|
|
@@ -22,58 +86,37 @@ async function firstModel(modelsFor, provider, family) {
|
|
|
22
86
|
catch {
|
|
23
87
|
return undefined;
|
|
24
88
|
}
|
|
25
|
-
|
|
26
|
-
return ((liked === undefined ? undefined : models.find((model) => liked.test(model.id)))?.id ??
|
|
27
|
-
models[0]?.id);
|
|
89
|
+
return preferredModel(provider, models)?.id;
|
|
28
90
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
for (const id of [...textClis, "openrouter"]) {
|
|
91
|
+
// The first ready provider in `order` that lists a model.
|
|
92
|
+
async function firstReady(input, order, family) {
|
|
93
|
+
const status = statusOf(input.statuses);
|
|
94
|
+
for (const id of order) {
|
|
34
95
|
if (!ready(status(id)))
|
|
35
96
|
continue;
|
|
36
|
-
const model = await firstModel(input.modelsFor, id,
|
|
37
|
-
if (model !== undefined)
|
|
38
|
-
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (llm === undefined)
|
|
43
|
-
gaps.push({
|
|
44
|
-
need: "text",
|
|
45
|
-
message: "No text model is ready. Install Claude Code, Codex or Antigravity CLI and sign in to it (Settings → Providers shows what Slopify found), or add an OpenRouter key in Settings → Providers.",
|
|
46
|
-
});
|
|
47
|
-
let images;
|
|
48
|
-
for (const id of ["codex-image", ...keyedImages]) {
|
|
49
|
-
if (!ready(status(id)))
|
|
50
|
-
continue;
|
|
51
|
-
const model = await firstModel(input.modelsFor, id, "image");
|
|
52
|
-
if (model !== undefined) {
|
|
53
|
-
images = { provider: id, model };
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
97
|
+
const model = await firstModel(input.modelsFor, id, family);
|
|
98
|
+
if (model !== undefined)
|
|
99
|
+
return { provider: id, model };
|
|
56
100
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
export async function planShortProviders(input) {
|
|
104
|
+
const status = statusOf(input.statuses);
|
|
105
|
+
const llm = await firstReady(input, textOrder, "llm");
|
|
106
|
+
const images = await firstReady(input, imageOrder, "image");
|
|
62
107
|
// A keyed voice first (a saved one, else the pack's suggestion), then the computer's own: a
|
|
63
108
|
// saved system voice, else the one found. Keys are the better voice, never a requirement.
|
|
64
109
|
let audio;
|
|
65
|
-
const keyed = (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(ready(status(input.suggested.provider)) ? input.suggested.provider : undefined);
|
|
69
|
-
if (provider !== undefined) {
|
|
70
|
-
const model = await firstModel(input.modelsFor, provider, "tts");
|
|
110
|
+
const keyed = keyedVoice(input.statuses, input.voices, input.suggested);
|
|
111
|
+
if (keyed !== undefined) {
|
|
112
|
+
const model = await firstModel(input.modelsFor, keyed.provider, "tts");
|
|
71
113
|
if (model !== undefined)
|
|
72
|
-
audio = { provider, model, voice:
|
|
114
|
+
audio = { provider: keyed.provider, model, voice: keyed.voiceId };
|
|
73
115
|
}
|
|
74
116
|
let systemVoice = false;
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
if (audio === undefined &&
|
|
118
|
+
ready(status(systemVoiceProvider)) &&
|
|
119
|
+
input.systemVoice !== undefined) {
|
|
77
120
|
const savedSystem = input.voices.find((voice) => voice.provider === systemVoiceProvider);
|
|
78
121
|
audio = {
|
|
79
122
|
provider: systemVoiceProvider,
|
|
@@ -82,20 +125,16 @@ export async function planShortProviders(input) {
|
|
|
82
125
|
};
|
|
83
126
|
systemVoice = true;
|
|
84
127
|
}
|
|
85
|
-
if (audio === undefined)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return llm === undefined || images === undefined || audio === undefined
|
|
97
|
-
? { ok: false, gaps }
|
|
98
|
-
: { ok: true, providers: { llm, audio, images }, systemVoice };
|
|
128
|
+
if (llm === undefined || images === undefined || audio === undefined)
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
gaps: [
|
|
132
|
+
...(llm === undefined ? [gapOf("text", input.statuses, input.install)] : []),
|
|
133
|
+
...(images === undefined ? [gapOf("images", input.statuses, input.install)] : []),
|
|
134
|
+
...(audio === undefined ? [gapOf("voice", input.statuses, input.install)] : []),
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
return { ok: true, providers: { llm, audio, images }, systemVoice };
|
|
99
138
|
}
|
|
100
139
|
// The short-mode draft for a topic, using the pack's (or starter set's) script and scene
|
|
101
140
|
// prompts under the names they have in the library.
|
|
@@ -127,5 +127,8 @@ export function copySample(deps, projectId) {
|
|
|
127
127
|
status: 404,
|
|
128
128
|
detail: "That sample project is no longer here. Bring it back with Settings → Backup & storage → Restore samples, then press Make my own copy.",
|
|
129
129
|
};
|
|
130
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
...copyProject(deps, projectId, `${title} (my copy)`, { renamed: true }),
|
|
133
|
+
};
|
|
131
134
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { readJsonSetting, readSetting, writeSetting } from "../settings/repo.js";
|
|
3
|
+
import { sampleIds } from "./model.js";
|
|
3
4
|
// What the first five minutes remember, in the settings table: whether the first-run screen
|
|
4
5
|
// was dismissed, which project is the bundled sample, and which library rows each installed
|
|
5
6
|
// starter pack became. None of these keys is in a portable backup's settings, so another
|
|
@@ -41,6 +42,12 @@ const sampleKeyOf = (id) => (id === "library" ? sampleKey : `${sampleKey}.${id}`
|
|
|
41
42
|
export function readSampleRecord(db, id = "library") {
|
|
42
43
|
return readJson(db, sampleKeyOf(id), sampleRecord);
|
|
43
44
|
}
|
|
45
|
+
// The bundled samples' projects, by id, for a slice that must leave them as they are (Disk
|
|
46
|
+
// space's clean-ups). A deleted sample's record stays until Restore samples, so an id may name
|
|
47
|
+
// no project any more.
|
|
48
|
+
export function bundledSampleProjects(db) {
|
|
49
|
+
return new Set(sampleIds.flatMap((id) => readSampleRecord(db, id)?.projectId ?? []));
|
|
50
|
+
}
|
|
44
51
|
export function writeSampleRecord(db, record, id = "library") {
|
|
45
52
|
writeSetting(db, sampleKeyOf(id), JSON.stringify(record));
|
|
46
53
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { defaultEdgeSilenceSeconds } from "../admission/rules.js";
|
|
2
2
|
import { playDraftDocumentSchema } from "../play-drafts/schema.js";
|
|
3
3
|
// A pack's Play template: its prompts ticked, its voice picked, its style set, and the text and
|
|
4
|
-
// image providers
|
|
5
|
-
// differs per machine.
|
|
6
|
-
export function packTemplate(pack, names, voiceId) {
|
|
4
|
+
// image providers this machine's first launch found (Claude Code, Codex), since which of them
|
|
5
|
+
// is installed or keyed differs per machine. One it did not find is left for the user to pick.
|
|
6
|
+
export function packTemplate(pack, names, voiceId, providers = {}) {
|
|
7
7
|
const { style } = pack;
|
|
8
8
|
const name = (key) => names[key] ?? "";
|
|
9
|
+
const chosen = (choice) => choice === undefined ? { provider: "", model: "" } : { ...choice };
|
|
9
10
|
return playDraftDocumentSchema.parse({
|
|
10
11
|
schemaVersion: 1,
|
|
11
12
|
form: {
|
|
@@ -21,7 +22,7 @@ export function packTemplate(pack, names, voiceId) {
|
|
|
21
22
|
video: "generate",
|
|
22
23
|
document: "off",
|
|
23
24
|
},
|
|
24
|
-
llm:
|
|
25
|
+
llm: chosen(providers.llm),
|
|
25
26
|
audio: {
|
|
26
27
|
provider: pack.voice.provider,
|
|
27
28
|
model: pack.voice.model,
|
|
@@ -30,7 +31,7 @@ export function packTemplate(pack, names, voiceId) {
|
|
|
30
31
|
shareGlossary: true,
|
|
31
32
|
describeFigures: true,
|
|
32
33
|
},
|
|
33
|
-
images:
|
|
34
|
+
images: chosen(providers.images),
|
|
34
35
|
articlePrompt: name("article"),
|
|
35
36
|
narrationPrompt: "",
|
|
36
37
|
youtubeDescription: name("description") !== "",
|