@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
|
@@ -93,7 +93,7 @@ const refuse = (reason, message) => ({
|
|
|
93
93
|
reason,
|
|
94
94
|
message,
|
|
95
95
|
});
|
|
96
|
-
const missingTemplate = "This template no longer exists; it may have been deleted. Go to
|
|
96
|
+
const missingTemplate = "This template no longer exists; it may have been deleted. Go to Templates to see your templates.";
|
|
97
97
|
// What the bake-off is made on: the template's newest version, with a current paid sample.
|
|
98
98
|
function basis(deps, templateId, section, models) {
|
|
99
99
|
const template = templateById(deps.db, templateId);
|
|
@@ -106,7 +106,7 @@ function basis(deps, templateId, section, models) {
|
|
|
106
106
|
const sample = sampleOf(deps.db, templateId);
|
|
107
107
|
const made = sampleVersions(deps.db, templateId).find((one) => one.templateVersion === template.version);
|
|
108
108
|
if (sample === undefined || !projectExists(deps.db, sample.projectId) || made?.state !== "done")
|
|
109
|
-
return refuse("not-ready", `Compare models runs on the template's current sample, and version ${String(template.version)} has none yet. Press
|
|
109
|
+
return refuse("not-ready", `Compare models runs on the template's current sample, and version ${String(template.version)} has none yet. Press Make sample, wait for it, then compare.`);
|
|
110
110
|
const seen = new Set();
|
|
111
111
|
for (const model of models) {
|
|
112
112
|
const key = `${model.provider}:${model.model}`;
|
|
@@ -116,7 +116,7 @@ function basis(deps, templateId, section, models) {
|
|
|
116
116
|
return refuse("invalid-input", `${model.model} is picked twice. Pick each model once.`);
|
|
117
117
|
seen.add(key);
|
|
118
118
|
if (section === "voice" && model.provider !== current.provider)
|
|
119
|
-
return refuse("invalid-input", "A voice
|
|
119
|
+
return refuse("invalid-input", "A voice belongs to its voice service, so voice models are compared within the template's voice service. Pick models of that service, or change the voice in Narration first.");
|
|
120
120
|
}
|
|
121
121
|
return { ok: true, document: template.document, version: template.version, current };
|
|
122
122
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { dailySampleBudget, dailySampleBudgetKey } from "../designs/budget.js";
|
|
3
3
|
import { readJsonSetting, writeSetting } from "../settings/repo.js";
|
|
4
|
-
// Settings →
|
|
4
|
+
// Settings → Production defaults: the most the studio's samples may spend in one day, in dollars,
|
|
5
5
|
// and whether they may use a CLI plan at all. Plan usage has no dollar price, so the budget
|
|
6
6
|
// can't hold it back: samples that run on a CLI plan wait until the person turns that on. By
|
|
7
7
|
// default ($2 a day, plans off) samples spend at most $2 a day and never a plan; at $0 nothing
|
|
@@ -91,13 +91,16 @@ export function fitsBudget(room, high, usesPlan = false) {
|
|
|
91
91
|
return high <= room.left + 1e-9;
|
|
92
92
|
}
|
|
93
93
|
const money = (value) => `$${value.toFixed(2)}`;
|
|
94
|
+
// Settings → Production defaults' switch by its name on screen (web `budget-words.ts`
|
|
95
|
+
// plansLabel), so a sentence that says to turn it on names what the person will find.
|
|
96
|
+
export const plansSwitch = "Let samples use my Claude Code or Codex plan";
|
|
94
97
|
// Why a sample that doesn't fit (`fitsBudget`) waits, and what lets it start, as one sentence
|
|
95
98
|
// (no full stop) about `subject`: "This sample", "Comparing".
|
|
96
99
|
export function budgetWords(room, cost, subject) {
|
|
97
100
|
if (cost.usesPlan && !room.plans)
|
|
98
|
-
return `${subject} runs on your
|
|
101
|
+
return `${subject} runs on your Claude Code or Codex plan, and samples may not use it. Turn on ${plansSwitch} in Settings → Production defaults`;
|
|
99
102
|
const unpriced = room.unpriced === 0
|
|
100
103
|
? ""
|
|
101
104
|
: ` (samples spent ${money(room.spent)} today plus ${String(room.unpriced)} ${room.unpriced === 1 ? "call" : "calls"} with no known price)`;
|
|
102
|
-
return `${subject} costs about ${money(cost.high)} and ${money(room.left)} of today's ${money(room.budget)} sample budget is left${unpriced}. Raise the budget in Settings →
|
|
105
|
+
return `${subject} costs about ${money(cost.high)} and ${money(room.left)} of today's ${money(room.budget)} sample budget is left${unpriced}. Raise the budget in Settings → Production defaults`;
|
|
103
106
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { projectExists } from "../admission/repo.js";
|
|
2
|
+
import { topicKeywords } from "../project-templates/one-off.js";
|
|
3
|
+
import { templateById, templateSummaries } from "../project-templates/repo.js";
|
|
4
|
+
import { currentRevisionId } from "../revisions/repo.js";
|
|
5
|
+
import { getRevisionView } from "../revisions/view.js";
|
|
6
|
+
import { listVoices } from "../settings/repo.js";
|
|
7
|
+
import { packSamples } from "./pack-samples.js";
|
|
8
|
+
import { sampleOf } from "./repo.js";
|
|
9
|
+
import { templateSchedules } from "./service.js";
|
|
10
|
+
const formats = new Set(["16:9", "9:16", "1:1"]);
|
|
11
|
+
export function cardFacts(document, voices) {
|
|
12
|
+
const { form } = document;
|
|
13
|
+
const voiced = form.sources.audio === "generate" && form.audio.voice.trim() !== "";
|
|
14
|
+
const saved = voices.find((one) => one.provider === form.audio.provider && one.voiceId === form.audio.voice);
|
|
15
|
+
const words = Number.parseInt(document.expectedWords, 10);
|
|
16
|
+
const shorts = Number.parseInt(form.shorts?.count ?? "0", 10);
|
|
17
|
+
return {
|
|
18
|
+
format: formats.has(form.format) ? form.format : "16:9",
|
|
19
|
+
video: form.sources.video !== "off" && form.sources.images !== "off",
|
|
20
|
+
words: form.sources.article === "off" || !Number.isFinite(words) ? null : Math.max(0, words),
|
|
21
|
+
voice: voiced
|
|
22
|
+
? {
|
|
23
|
+
provider: form.audio.provider,
|
|
24
|
+
voiceId: form.audio.voice,
|
|
25
|
+
name: saved?.name.trim() ? saved.name : form.audio.voice,
|
|
26
|
+
}
|
|
27
|
+
: null,
|
|
28
|
+
captions: form.subtitles.mode,
|
|
29
|
+
shorts: form.shorts?.enabled === true && Number.isFinite(shorts) ? Math.max(0, shorts) : 0,
|
|
30
|
+
pdf: form.sources.document === "generate",
|
|
31
|
+
asks: [...topicKeywords(form.title)],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// The sample's thumbnail or first picture, as the project's file route serves it.
|
|
35
|
+
function sampleStill(deps, templateId) {
|
|
36
|
+
const sample = sampleOf(deps.db, templateId);
|
|
37
|
+
if (sample === undefined || !projectExists(deps.db, sample.projectId))
|
|
38
|
+
return null;
|
|
39
|
+
const revisionId = currentRevisionId(deps.db, sample.projectId);
|
|
40
|
+
if (revisionId === undefined)
|
|
41
|
+
return null;
|
|
42
|
+
const revision = getRevisionView({
|
|
43
|
+
db: deps.db,
|
|
44
|
+
paths: deps.paths,
|
|
45
|
+
log: deps.log,
|
|
46
|
+
ids: { next: () => "" },
|
|
47
|
+
clock: { now: () => new Date(), sleep: async () => undefined },
|
|
48
|
+
}, sample.projectId, revisionId);
|
|
49
|
+
const shown = (role) => revision?.outputs.find((row) => row.output.role === role && row.selected && row.available && row.state === "ready");
|
|
50
|
+
const row = shown("thumbnail") ?? shown("image");
|
|
51
|
+
return row === undefined
|
|
52
|
+
? null
|
|
53
|
+
: `/files/${encodeURIComponent(sample.projectId)}/revisions/${encodeURIComponent(revisionId)}/${encodeURIComponent(row.recordId)}`;
|
|
54
|
+
}
|
|
55
|
+
function packStill(deps, templateId) {
|
|
56
|
+
const samples = packSamples(deps.paths, templateId);
|
|
57
|
+
const picture = samples.find((one) => one.role === "thumbnail") ??
|
|
58
|
+
samples.find((one) => one.role === "picture");
|
|
59
|
+
return picture === undefined
|
|
60
|
+
? null
|
|
61
|
+
: `/api/template-studio/${templateId}/pack-samples/${picture.file}`;
|
|
62
|
+
}
|
|
63
|
+
export function templateCards(deps) {
|
|
64
|
+
const voices = listVoices(deps.db);
|
|
65
|
+
return templateSummaries(deps.db).flatMap((summary) => {
|
|
66
|
+
const template = templateById(deps.db, summary.id);
|
|
67
|
+
if (template === undefined)
|
|
68
|
+
return [];
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
id: template.id,
|
|
72
|
+
still: sampleStill(deps, template.id) ?? packStill(deps, template.id),
|
|
73
|
+
...cardFacts(template.document, voices),
|
|
74
|
+
schedules: templateSchedules(deps.db, template.id).length,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { detectSlots, filledKeywords } from "../admission/substitute.js";
|
|
2
2
|
import { isReserved } from "../keywords/model.js";
|
|
3
3
|
import { topicKeywords } from "../project-templates/one-off.js";
|
|
4
|
+
import { withProviderDefaults, } from "../project-templates/provider-defaults.js";
|
|
4
5
|
import { usesPostPrompt } from "../social/rules.js";
|
|
5
6
|
import { sectionLabel } from "./model.js";
|
|
6
7
|
export const freeCheckIds = [
|
|
@@ -16,13 +17,18 @@ export const studioHref = (templateId, section) => `/templates/${encodeURICompon
|
|
|
16
17
|
const scheduleHref = (id) => `/calendar?tab=schedules&schedule=${encodeURIComponent(id)}`;
|
|
17
18
|
const weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
18
19
|
const agentWindowMs = 7 * 24 * 60 * 60 * 1000;
|
|
20
|
+
// The checks in order. Providers is one line per missing thing, each with its own fix, so the
|
|
21
|
+
// count of lines to fix is the count of things to fix. A run that can't start can't be priced
|
|
22
|
+
// either: while a line above says why, the monthly cost's line would only say it again, so it
|
|
23
|
+
// waits until they are fixed.
|
|
19
24
|
export function freeChecks(input) {
|
|
25
|
+
const start = [keywordsCheck(input), ...providersChecks(input), voiceCheck(input)];
|
|
26
|
+
const blocked = start.some((check) => !check.ok);
|
|
27
|
+
const cost = monthlyCostCheck(input);
|
|
20
28
|
return [
|
|
21
|
-
|
|
22
|
-
providersCheck(input),
|
|
23
|
-
voiceCheck(input),
|
|
29
|
+
...start,
|
|
24
30
|
releaseSlotsCheck(input),
|
|
25
|
-
|
|
31
|
+
...(blocked && !cost.ok ? [] : [cost]),
|
|
26
32
|
answerersCheck(input),
|
|
27
33
|
];
|
|
28
34
|
}
|
|
@@ -56,7 +62,7 @@ function keywordsCheck(input) {
|
|
|
56
62
|
ok: false,
|
|
57
63
|
sentence: `${empty.length === 1 ? "A keyword is" : "Keywords are"} empty: ${list(empty.map((name) => `{{${name}}}`))}. A run would send the prompt with nothing in ${empty.length === 1 ? "its place" : "their place"}.`,
|
|
58
64
|
fix: {
|
|
59
|
-
label:
|
|
65
|
+
label: `Fill them in ${sectionLabel.subject}`,
|
|
60
66
|
href: studioHref(input.templateId, "subject"),
|
|
61
67
|
},
|
|
62
68
|
};
|
|
@@ -79,8 +85,9 @@ function usedProviders(document) {
|
|
|
79
85
|
{
|
|
80
86
|
family: "llm",
|
|
81
87
|
provider: form.llm.provider,
|
|
88
|
+
model: form.llm.model,
|
|
82
89
|
section: "script",
|
|
83
|
-
what: "
|
|
90
|
+
what: "writing service",
|
|
84
91
|
},
|
|
85
92
|
]
|
|
86
93
|
: []),
|
|
@@ -89,8 +96,9 @@ function usedProviders(document) {
|
|
|
89
96
|
{
|
|
90
97
|
family: "tts",
|
|
91
98
|
provider: form.audio.provider,
|
|
99
|
+
model: form.audio.model,
|
|
92
100
|
section: "voice",
|
|
93
|
-
what: "voice",
|
|
101
|
+
what: "voice service",
|
|
94
102
|
},
|
|
95
103
|
]
|
|
96
104
|
: []),
|
|
@@ -99,8 +107,9 @@ function usedProviders(document) {
|
|
|
99
107
|
{
|
|
100
108
|
family: "image",
|
|
101
109
|
provider: form.images.provider,
|
|
110
|
+
model: form.images.model,
|
|
102
111
|
section: "pictures",
|
|
103
|
-
what: "
|
|
112
|
+
what: "image service",
|
|
104
113
|
},
|
|
105
114
|
]
|
|
106
115
|
: []),
|
|
@@ -109,43 +118,72 @@ function usedProviders(document) {
|
|
|
109
118
|
{
|
|
110
119
|
family: "llm",
|
|
111
120
|
provider: form.reviews.provider,
|
|
121
|
+
model: form.reviews.model,
|
|
112
122
|
section: "reviews",
|
|
113
|
-
what: "
|
|
123
|
+
what: "service for the AI checks",
|
|
114
124
|
},
|
|
115
125
|
]
|
|
116
126
|
: []),
|
|
117
127
|
];
|
|
118
128
|
}
|
|
119
|
-
function
|
|
120
|
-
const used = usedProviders(input.document);
|
|
121
|
-
const unchosen = used.
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
function providersChecks(input) {
|
|
130
|
+
const used = usedProviders(withProviderDefaults(input.document, input.defaults ?? {}));
|
|
131
|
+
const unchosen = used.filter((one, index) => one.provider === "" && used.findIndex((other) => other.what === one.what) === index);
|
|
132
|
+
// A service picked without its model is refused by every run, as the sample says.
|
|
133
|
+
const modelless = used.filter((one, index) => one.provider !== "" &&
|
|
134
|
+
one.model.trim() === "" &&
|
|
135
|
+
used.findIndex((other) => other.what === one.what) === index);
|
|
136
|
+
const missing = used.filter((one, index) => one.provider !== "" &&
|
|
137
|
+
input.providers.get(one.provider)?.ready !== true &&
|
|
138
|
+
used.findIndex((other) => other.provider === one.provider) === index);
|
|
139
|
+
const lines = [
|
|
140
|
+
...unchosen.map((one) => ({
|
|
124
141
|
id: "providers",
|
|
125
142
|
ok: false,
|
|
126
|
-
sentence: `No ${
|
|
143
|
+
sentence: `No ${one.what} is chosen, so a run can't start.`,
|
|
127
144
|
fix: {
|
|
128
|
-
label: `Choose one in ${sectionLabel[
|
|
129
|
-
href: studioHref(input.templateId,
|
|
145
|
+
label: `Choose one in ${sectionLabel[one.section]}`,
|
|
146
|
+
href: studioHref(input.templateId, one.section),
|
|
130
147
|
},
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
})),
|
|
149
|
+
...modelless.map((one) => {
|
|
150
|
+
const name = input.providers.get(one.provider)?.name ?? one.provider;
|
|
151
|
+
return {
|
|
152
|
+
id: "providers",
|
|
153
|
+
ok: false,
|
|
154
|
+
sentence: `No model is chosen for ${name}, the ${one.what}, so a run can't start.`,
|
|
155
|
+
fix: {
|
|
156
|
+
label: `Choose one in ${sectionLabel[one.section]}`,
|
|
157
|
+
href: studioHref(input.templateId, one.section),
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}),
|
|
161
|
+
...missing.map((one) => {
|
|
162
|
+
const name = input.providers.get(one.provider)?.name ?? one.provider;
|
|
163
|
+
return {
|
|
164
|
+
id: "providers",
|
|
165
|
+
ok: false,
|
|
166
|
+
sentence: `${name}, the ${one.what}, isn't set up on this computer: no key is stored or its program wasn't found.`,
|
|
167
|
+
fix: {
|
|
168
|
+
label: "Set it up in Settings → Keys & services",
|
|
169
|
+
href: "/settings?section=providers",
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}),
|
|
173
|
+
];
|
|
174
|
+
return lines.length > 0
|
|
175
|
+
? lines
|
|
176
|
+
: [{ id: "providers", ok: true, sentence: "Every service this template uses is set up." }];
|
|
142
177
|
}
|
|
143
178
|
// The narration's voice (and each speaker's) is chosen and is one of the voices this computer
|
|
144
179
|
// keeps in Settings → Voices: an imported pack's voice may be someone else's.
|
|
145
180
|
function voiceCheck(input) {
|
|
146
181
|
const { form } = input.document;
|
|
147
182
|
const voiced = form.sources.audio === "generate";
|
|
148
|
-
const fix = {
|
|
183
|
+
const fix = {
|
|
184
|
+
label: `Choose a voice in ${sectionLabel.voice}`,
|
|
185
|
+
href: studioHref(input.templateId, "voice"),
|
|
186
|
+
};
|
|
149
187
|
if (!voiced)
|
|
150
188
|
return { id: "voice", ok: true, sentence: "The narration isn't voiced by Slopify." };
|
|
151
189
|
const picks = [
|
|
@@ -160,7 +198,7 @@ function voiceCheck(input) {
|
|
|
160
198
|
return {
|
|
161
199
|
id: "voice",
|
|
162
200
|
ok: false,
|
|
163
|
-
sentence: "No voice is chosen for the narration, so
|
|
201
|
+
sentence: "No voice is chosen for the narration, so a run can't start.",
|
|
164
202
|
fix,
|
|
165
203
|
};
|
|
166
204
|
const known = input.voices;
|
|
@@ -214,36 +252,47 @@ function releaseSlotsCheck(input) {
|
|
|
214
252
|
: "Every run has a release time for the video and its shorts.",
|
|
215
253
|
};
|
|
216
254
|
}
|
|
255
|
+
// A run that can't be priced at all fails: it can't start either, and the sentence says why.
|
|
256
|
+
// Steps with no price (how many tables a written article describes is known only once it is
|
|
257
|
+
// written) never fail it, since nothing fixes them: they are named as not counted, here and in
|
|
258
|
+
// Schedule, so the dollar figure reads as the least it costs.
|
|
217
259
|
function monthlyCostCheck(input) {
|
|
218
|
-
|
|
260
|
+
const cost = input.monthlyCost;
|
|
261
|
+
if (cost === null)
|
|
219
262
|
return {
|
|
220
263
|
id: "monthly-cost",
|
|
221
264
|
ok: true,
|
|
222
265
|
sentence: "No schedule runs this template, so it costs nothing a month.",
|
|
223
266
|
};
|
|
224
|
-
|
|
225
|
-
const resumed = `up to about $${(input.monthlyCost.paused?.high ?? 0).toFixed(2)} a month when resumed`;
|
|
226
|
-
const many = (n) => `${String(n)} paused ${n === 1 ? "schedule" : "schedules"}`;
|
|
227
|
-
if (input.monthlyCost.unknown === 0) {
|
|
228
|
-
const activeCost = `Its schedules cost up to about $${input.monthlyCost.high.toFixed(2)} a month`;
|
|
267
|
+
if (cost.problem !== undefined && cost.problem !== null)
|
|
229
268
|
return {
|
|
230
269
|
id: "monthly-cost",
|
|
231
|
-
ok:
|
|
232
|
-
sentence:
|
|
233
|
-
|
|
234
|
-
: input.monthlyCost.high === 0 && (input.monthlyCost.runs ?? 0) === 0
|
|
235
|
-
? `${many(paused)} ${paused === 1 ? "uses" : "use"} this template: ${paused === 1 ? "it costs" : "they cost"} nothing while paused, and ${resumed}.`
|
|
236
|
-
: `${activeCost}; ${many(paused)} would add ${resumed}.`,
|
|
270
|
+
ok: false,
|
|
271
|
+
sentence: cost.problem,
|
|
272
|
+
fix: { label: "See its runs in Schedule", href: studioHref(input.templateId, "schedule") },
|
|
237
273
|
};
|
|
238
|
-
|
|
274
|
+
const paused = cost.paused?.schedules ?? 0;
|
|
275
|
+
const resumed = `up to about $${(cost.paused?.high ?? 0).toFixed(2)} a month when resumed`;
|
|
276
|
+
const many = (n) => `${String(n)} paused ${n === 1 ? "schedule" : "schedules"}`;
|
|
277
|
+
const activeCost = `Its schedules cost ${cost.unknown === 0 ? "up to about" : "at least"} $${cost.high.toFixed(2)} a month`;
|
|
278
|
+
const sentence = paused === 0
|
|
279
|
+
? `${activeCost}.`
|
|
280
|
+
: cost.high === 0 && (cost.runs ?? 0) === 0
|
|
281
|
+
? `${many(paused)} ${paused === 1 ? "uses" : "use"} this template: ${paused === 1 ? "it costs" : "they cost"} nothing while paused, and ${resumed}.`
|
|
282
|
+
: `${activeCost}; ${many(paused)} would add ${resumed}.`;
|
|
239
283
|
return {
|
|
240
284
|
id: "monthly-cost",
|
|
241
|
-
ok:
|
|
242
|
-
sentence:
|
|
243
|
-
`The monthly cost isn't known: ${String(input.monthlyCost.unknown)} ${input.monthlyCost.unknown === 1 ? "step has" : "steps have"} no price in the model list.`,
|
|
244
|
-
fix: { label: "See which in Schedule", href: studioHref(input.templateId, "schedule") },
|
|
285
|
+
ok: true,
|
|
286
|
+
sentence: cost.unknown === 0 ? sentence : `${sentence} ${unpricedSentence(cost)}`,
|
|
245
287
|
};
|
|
246
288
|
}
|
|
289
|
+
// "Not counted, with no known price: Narration descriptions and Delivery cue overhead."
|
|
290
|
+
function unpricedSentence(cost) {
|
|
291
|
+
const names = cost.unpriced ?? [];
|
|
292
|
+
return names.length === 0
|
|
293
|
+
? `${String(cost.unknown)} ${cost.unknown === 1 ? "step has" : "steps have"} no known price and ${cost.unknown === 1 ? "isn't" : "aren't"} counted.`
|
|
294
|
+
: `Not counted, with no known price: ${list(names)}.`;
|
|
295
|
+
}
|
|
247
296
|
function answerersCheck(input) {
|
|
248
297
|
const { form } = input.document;
|
|
249
298
|
const asks = (form.checkpoints?.length ?? 0) > 0 ||
|
|
@@ -258,13 +307,13 @@ function answerersCheck(input) {
|
|
|
258
307
|
id: "answerers",
|
|
259
308
|
ok: true,
|
|
260
309
|
sentence: agent
|
|
261
|
-
? "An agent connected this week answers
|
|
262
|
-
: "You're notified when a
|
|
310
|
+
? "An agent connected this week answers when a run pauses."
|
|
311
|
+
: "You're notified when a run pauses for you.",
|
|
263
312
|
};
|
|
264
313
|
return {
|
|
265
314
|
id: "answerers",
|
|
266
315
|
ok: false,
|
|
267
|
-
sentence: "A run
|
|
316
|
+
sentence: "A run pauses for your approval and nobody would know: no notification address is set and no agent connected in the last 7 days.",
|
|
268
317
|
fix: {
|
|
269
318
|
label: "Set a notification address in Settings → Notifications",
|
|
270
319
|
href: "/settings?section=notifications",
|
|
@@ -2,7 +2,7 @@ import { setImmediate as yieldTurn } from "node:timers/promises";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { transact } from "../../kernel/db/tx.js";
|
|
4
4
|
import { templateById, templateSummaries } from "../project-templates/repo.js";
|
|
5
|
-
import { budgetRoom, budgetWords, fitsBudget } from "./budget.js";
|
|
5
|
+
import { budgetRoom, budgetWords, fitsBudget, plansSwitch } from "./budget.js";
|
|
6
6
|
import { enqueueJob } from "./jobs.js";
|
|
7
7
|
import { quoteSample } from "./quote.js";
|
|
8
8
|
import { sampleVersions, setSampleVersion } from "./repo.js";
|
|
@@ -79,10 +79,10 @@ function sentenceOf(makeable, room) {
|
|
|
79
79
|
const waiting = count - fitting;
|
|
80
80
|
const onPlan = makeable.filter((item) => !item.fits && item.usesPlan && !room.plans).length;
|
|
81
81
|
const why = onPlan === waiting
|
|
82
|
-
?
|
|
82
|
+
? `: they run on your Claude Code or Codex plan, which samples may not use (turn on ${plansSwitch} in Settings → Production defaults)`
|
|
83
83
|
: onPlan > 0
|
|
84
|
-
? `: ${String(onPlan)} run on your
|
|
85
|
-
: " for tomorrow or a higher budget in Settings →
|
|
84
|
+
? `: ${String(onPlan)} run on your Claude Code or Codex plan, which samples may not use (turn on ${plansSwitch} in Settings → Production defaults), and the rest wait for tomorrow or a higher budget`
|
|
85
|
+
: " for tomorrow or a higher budget in Settings → Production defaults";
|
|
86
86
|
return `${head} ${String(fitting)} of them ${fitting === 1 ? "fits" : "fit"} today's budget; the other ${String(waiting)} wait${why}.`;
|
|
87
87
|
}
|
|
88
88
|
export const samplesForAllSchema = z
|
|
@@ -94,7 +94,7 @@ export const samplesForAllSchema = z
|
|
|
94
94
|
})
|
|
95
95
|
.strict()
|
|
96
96
|
.readonly();
|
|
97
|
-
const budgetHeld = "Waiting: today's sample budget is used by the other templates' samples. Press Make sample tomorrow, or raise the budget in Settings →
|
|
97
|
+
const budgetHeld = "Waiting: today's sample budget is used by the other templates' samples. Press Make sample tomorrow, or raise the budget in Settings → Production defaults.";
|
|
98
98
|
export async function makeSamplesForAll(deps, raw) {
|
|
99
99
|
const parsed = samplesForAllSchema.safeParse(raw);
|
|
100
100
|
if (!parsed.success)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defaultImageSeconds, defaultMotionStyle, defaultZoomPercent } from "../admission/rules.js";
|
|
2
|
-
// The Video
|
|
2
|
+
// The Video and style sample: 30 seconds made by the style preview's renderer from the
|
|
3
3
|
// template's frame, captions, Look and picture timing, on the bundled sample's pictures and
|
|
4
4
|
// narration. Local and free; no provider is called.
|
|
5
5
|
export const videoSampleSeconds = 30;
|
|
@@ -8,7 +8,7 @@ import { loudnessGoalOf, loudnessHash } from "./loudness.js";
|
|
|
8
8
|
// and after, and the PDF. Each is kept by the hash of what it is made from, so a setting changed
|
|
9
9
|
// back, or the same setup in another template, shows at once.
|
|
10
10
|
export const freeKinds = ["video", "loudness", "document"];
|
|
11
|
-
const press = "press Make
|
|
11
|
+
const press = "press Make sample";
|
|
12
12
|
export function createFreeSamples(deps) {
|
|
13
13
|
const failures = new Map();
|
|
14
14
|
const making = new Set();
|
|
@@ -71,7 +71,7 @@ export function createFreeSamples(deps) {
|
|
|
71
71
|
if (form.sources.document === "generate" && deps.documents !== undefined) {
|
|
72
72
|
const documents = deps.documents;
|
|
73
73
|
const theme = documentThemeOfTemplate(document);
|
|
74
|
-
await attempt("document", documentHash(theme), () => documents.make(theme), (error) => `The PDF sample couldn't be made: ${errorText(error)} Change the theme
|
|
74
|
+
await attempt("document", documentHash(theme), () => documents.make(theme), (error) => `The PDF sample couldn't be made: ${errorText(error)} Change the theme under All PDF settings, then ${press}.`);
|
|
75
75
|
}
|
|
76
76
|
},
|
|
77
77
|
view: (templateId, document, job) => {
|
|
@@ -129,7 +129,7 @@ export function createFreeSamples(deps) {
|
|
|
129
129
|
loudness = {
|
|
130
130
|
...(shown ?? {
|
|
131
131
|
state: "current",
|
|
132
|
-
sentence: "
|
|
132
|
+
sentence: "How your narration will be levelled, shown on Slopify's sample narration until your voice's own sample is made.",
|
|
133
133
|
url: `${base}/loudness/after.mp3?v=${lHash.slice(0, 12)}`,
|
|
134
134
|
}),
|
|
135
135
|
beforeUrl: `${base}/loudness/before.mp3`,
|
|
@@ -141,7 +141,7 @@ export function createFreeSamples(deps) {
|
|
|
141
141
|
}
|
|
142
142
|
let pdf;
|
|
143
143
|
if (form.sources.document !== "generate")
|
|
144
|
-
pdf = { state: "off", sentence: "The PDF
|
|
144
|
+
pdf = { state: "off", sentence: "The PDF is off.", url: null };
|
|
145
145
|
else if (deps.documents === undefined)
|
|
146
146
|
pdf = unavailable("PDF sample");
|
|
147
147
|
else {
|
|
@@ -162,7 +162,7 @@ export function createFreeSamples(deps) {
|
|
|
162
162
|
function videoFailure(error) {
|
|
163
163
|
if (error instanceof StylePreviewError) {
|
|
164
164
|
if (error.reason === "font-missing")
|
|
165
|
-
return `The video sample couldn't be made because the caption font is no longer on this computer. Choose another font in Video
|
|
165
|
+
return `The video sample couldn't be made because the caption font is no longer on this computer. Choose another Caption font in Video and style, then ${press}.`;
|
|
166
166
|
if (error.reason === "ffmpeg-missing")
|
|
167
167
|
return `The video sample couldn't be made because ffmpeg wasn't found. Install ffmpeg, or use the Docker image, then ${press}.`;
|
|
168
168
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
import { setupSectionNames } from "../project-templates/setup-sections.js";
|
|
2
|
+
// Template studio (4.0, "Edit template" on screen): a template is set up once, section by
|
|
3
|
+
// section, with live samples on a hidden project, then approved for its schedules. Not to be
|
|
4
|
+
// confused with `slices/studio`, which is the YouTube Studio pack and its extension.
|
|
4
5
|
// In pipeline order: the studio's rail shows them so.
|
|
5
6
|
export const templateSections = [
|
|
6
7
|
"subject",
|
|
@@ -15,19 +16,22 @@ export const templateSections = [
|
|
|
15
16
|
"upload",
|
|
16
17
|
"schedule",
|
|
17
18
|
];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
// Each section's place in the one list of setup sections Create, Edit template and a project's
|
|
20
|
+
// settings share. The ids stay as they were, so every saved address still opens its section.
|
|
21
|
+
export const setupSectionOf = {
|
|
22
|
+
subject: "topic",
|
|
23
|
+
script: "article",
|
|
24
|
+
voice: "narration",
|
|
25
|
+
pictures: "images",
|
|
26
|
+
video: "video",
|
|
27
|
+
thumbnail: "thumbnail",
|
|
28
|
+
shorts: "shorts",
|
|
29
|
+
document: "pdf",
|
|
30
|
+
reviews: "checks",
|
|
31
|
+
upload: "youtube",
|
|
32
|
+
schedule: "schedule",
|
|
30
33
|
};
|
|
34
|
+
export const sectionLabel = Object.fromEntries(templateSections.map((id) => [id, setupSectionNames[setupSectionOf[id]]]));
|
|
31
35
|
// What a section's sample is: made for the current version, being made, queued behind another,
|
|
32
36
|
// failed with a fix, made for an older version, switched off, or provided by the user at run time.
|
|
33
37
|
export const sectionStates = [
|
|
@@ -15,14 +15,14 @@ const refuse = (message) => ({
|
|
|
15
15
|
reason: "invalid-input",
|
|
16
16
|
message,
|
|
17
17
|
});
|
|
18
|
-
const notAPack = "This file isn't a template
|
|
18
|
+
const notAPack = "This file isn't a template file Slopify can read. Choose the .zip a template's Export as a file (.zip) made.";
|
|
19
19
|
class ZipStopped extends Error {
|
|
20
20
|
}
|
|
21
21
|
// Every check that needs nothing written: a zip, small enough, with a manifest of this format
|
|
22
22
|
// and version, no keys, and every sample it names inside it.
|
|
23
23
|
export function readPack(bytes) {
|
|
24
24
|
if (bytes.byteLength > packLimits.uploadBytes)
|
|
25
|
-
return refuse(`This
|
|
25
|
+
return refuse(`This template file is larger than ${String(packLimits.uploadBytes / 1024 / 1024)} MB, more than an exported template ever is. Export it again from the template's Edit template page, or ask whoever made it to.`);
|
|
26
26
|
let oversized;
|
|
27
27
|
let unexpected;
|
|
28
28
|
let twice;
|
|
@@ -73,42 +73,42 @@ export function readPack(bytes) {
|
|
|
73
73
|
entries = {};
|
|
74
74
|
}
|
|
75
75
|
if (tooMany)
|
|
76
|
-
return refuse(`This
|
|
76
|
+
return refuse(`This template file holds more files than an exported template ever has (a manifest and at most ${String(packLimits.samples)} samples), so nothing was imported. Export it again from the template's Edit template page.`);
|
|
77
77
|
if (twice !== undefined)
|
|
78
|
-
return refuse(`This
|
|
78
|
+
return refuse(`This template file holds ${twice.slice(0, 120)} twice, which an exported template never does, so nothing was imported. Export it again from the template's Edit template page.`);
|
|
79
79
|
if (oversized !== undefined)
|
|
80
|
-
return refuse(`This
|
|
80
|
+
return refuse(`This template file's ${oversized} is larger than a template file allows, so nothing was imported. Export it again from the template's Edit template page.`);
|
|
81
81
|
if (unexpected !== undefined)
|
|
82
|
-
return refuse(`This
|
|
82
|
+
return refuse(`This template file holds a file an exported template never has (${unexpected.slice(0, 120)}), so nothing was imported. Export it again from the template's Edit template page.`);
|
|
83
83
|
const raw = entries["manifest.json"];
|
|
84
84
|
if (raw === undefined)
|
|
85
|
-
return refuse("This zip has no manifest.json, so it isn't a template
|
|
85
|
+
return refuse("This zip has no manifest.json, so it isn't a template file. Choose the .zip a template's Export as a file (.zip) made.");
|
|
86
86
|
const text = strFromU8(raw);
|
|
87
87
|
if (looksSecret(text))
|
|
88
|
-
return refuse("This
|
|
88
|
+
return refuse("This template file holds what looks like an API key or a login token, so Slopify didn't import it: keys never travel in a template file. Ask whoever made it to remove it from the template and export it again.");
|
|
89
89
|
let json;
|
|
90
90
|
try {
|
|
91
91
|
json = JSON.parse(text);
|
|
92
92
|
}
|
|
93
93
|
catch {
|
|
94
|
-
return refuse("This
|
|
94
|
+
return refuse("This template file's manifest.json isn't readable JSON; the file may be damaged. Export it again from the template's Edit template page.");
|
|
95
95
|
}
|
|
96
96
|
const head = json;
|
|
97
97
|
if (typeof json !== "object" || json === null || head.format !== packFormat)
|
|
98
98
|
return refuse(notAPack);
|
|
99
99
|
if (typeof head.formatVersion === "number" && head.formatVersion > packFormatVersion)
|
|
100
|
-
return refuse(`This
|
|
100
|
+
return refuse(`This template file was made by a newer Slopify (file format ${String(head.formatVersion)}). Update Slopify in Settings → About, then import it again.`);
|
|
101
101
|
const parsed = packManifestSchema.safeParse(json);
|
|
102
102
|
if (!parsed.success) {
|
|
103
103
|
const issue = parsed.error.issues[0];
|
|
104
104
|
const where = issue === undefined ? "" : ` (${issue.path.join(".") || "manifest"}: ${issue.message})`;
|
|
105
|
-
return refuse(`This
|
|
105
|
+
return refuse(`This template file's manifest isn't one Slopify can use${where.slice(0, 300)}. It may be damaged or edited by hand; export it again from the template's Edit template page.`);
|
|
106
106
|
}
|
|
107
107
|
const files = new Map();
|
|
108
108
|
for (const sample of parsed.data.samples) {
|
|
109
109
|
const file = entries[sample.file];
|
|
110
110
|
if (file === undefined)
|
|
111
|
-
return refuse(`This
|
|
111
|
+
return refuse(`This template file names a sample (${sample.file}) it doesn't hold; the file may be damaged. Export it again from the template's Edit template page.`);
|
|
112
112
|
files.set(sample.file, file);
|
|
113
113
|
}
|
|
114
114
|
return { ok: true, value: { manifest: parsed.data, files } };
|
|
@@ -198,8 +198,8 @@ export function importPack(deps, bytes) {
|
|
|
198
198
|
});
|
|
199
199
|
if (!created.ok)
|
|
200
200
|
throw new PackRefused(created.reason === "missing-prompt"
|
|
201
|
-
? "This
|
|
202
|
-
: "Slopify couldn't make a template from this
|
|
201
|
+
? "This file's template names a prompt or intro/outro the file doesn't carry, so it can't be made here. Ask whoever made it to export it again."
|
|
202
|
+
: "Slopify couldn't make a template from this file. Import it again; if it fails again, use Download diagnostics in Settings and report it.");
|
|
203
203
|
const audio = created.value.document.form.audio;
|
|
204
204
|
const voiceKnown = audio.voice === "" ||
|
|
205
205
|
listVoices(deps.db).some((voice) => voice.provider === audio.provider && voice.voiceId === audio.voice);
|