@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
|
@@ -22,16 +22,16 @@ export function designCheckContext(deps) {
|
|
|
22
22
|
statuses = await deps.providers();
|
|
23
23
|
}
|
|
24
24
|
catch (error) {
|
|
25
|
-
return `Slopify couldn't check the image
|
|
25
|
+
return `Slopify couldn't check the image service: ${errorText(error)} Open Settings → Keys & services, then press Approve for schedules again.`;
|
|
26
26
|
}
|
|
27
27
|
const status = statuses.find((one) => one.id === provider);
|
|
28
28
|
if (status === undefined)
|
|
29
|
-
return `The image
|
|
29
|
+
return `The image service “${provider}” isn't one this Slopify knows. Choose another image model under Picture.`;
|
|
30
30
|
const readiness = status.readiness;
|
|
31
31
|
if (readiness.kind === "keyed" && !readiness.hasKey)
|
|
32
|
-
return `${status.displayName} has no API key, so the picture can't be made. Add the key in Settings →
|
|
32
|
+
return `${status.displayName} has no API key, so the picture can't be made. Add the key in Settings → Keys & services, then press Approve for schedules again.`;
|
|
33
33
|
if (readiness.kind === "cli" && (!readiness.installed || readiness.issue !== undefined))
|
|
34
|
-
return `${status.displayName} can't be used yet: ${readiness.issue ?? "it isn't installed on this computer."} Fix it in Settings →
|
|
34
|
+
return `${status.displayName} can't be used yet: ${readiness.issue ?? "it isn't installed on this computer."} Fix it in Settings → Keys & services, then press Approve for schedules again.`;
|
|
35
35
|
return null;
|
|
36
36
|
},
|
|
37
37
|
};
|
|
@@ -26,7 +26,7 @@ export function designFields(doc) {
|
|
|
26
26
|
const slotSentences = {
|
|
27
27
|
unclosed: "has a {{ without its }}",
|
|
28
28
|
empty: "has an empty {{}}",
|
|
29
|
-
nested: "has a { or } inside a {{
|
|
29
|
+
nested: "has a { or } inside a {{keyword}}",
|
|
30
30
|
};
|
|
31
31
|
export function fieldProblems(doc) {
|
|
32
32
|
const problems = [];
|
|
@@ -36,7 +36,7 @@ export function fieldProblems(doc) {
|
|
|
36
36
|
kind: "slot",
|
|
37
37
|
where: body.where,
|
|
38
38
|
error: error.kind,
|
|
39
|
-
sentence: `The ${body.where.replace(/^the /, "")} ${slotSentences[error.kind]}. Fix it so every
|
|
39
|
+
sentence: `The ${body.where.replace(/^the /, "")} ${slotSentences[error.kind]}. Fix it so every keyword reads {{Name}}.`,
|
|
40
40
|
});
|
|
41
41
|
const names = designFields(doc);
|
|
42
42
|
for (const name of names)
|
|
@@ -51,7 +51,7 @@ export function fieldProblems(doc) {
|
|
|
51
51
|
problems.push({
|
|
52
52
|
kind: "date-field",
|
|
53
53
|
name: dateField,
|
|
54
|
-
sentence: `The schedule reads each row's date from {{${dateField}}}, but no text uses it. Put {{${dateField}}} in a text block, or choose another date
|
|
54
|
+
sentence: `The schedule reads each row's date from {{${dateField}}}, but no text uses it. Put {{${dateField}}} in a text block, or choose another date keyword under Schedule.`,
|
|
55
55
|
});
|
|
56
56
|
return problems;
|
|
57
57
|
}
|
|
@@ -64,7 +64,7 @@ export function missingSentence(missing) {
|
|
|
64
64
|
const named = list.length === 1
|
|
65
65
|
? `${list[0] ?? ""} has`
|
|
66
66
|
: `${list.slice(0, -1).join(", ")} and ${list.at(-1) ?? ""} have`;
|
|
67
|
-
return `${named} no value. Fill ${list.length === 1 ? "it" : "them"} in under
|
|
67
|
+
return `${named} no value. Fill ${list.length === 1 ? "it" : "them"} in under Keywords, then press Make.`;
|
|
68
68
|
}
|
|
69
69
|
// Every text with its fields filled. Values are inserted verbatim, never read as fields.
|
|
70
70
|
export function fillDocument(doc, values) {
|
|
@@ -33,8 +33,10 @@ export async function contextChecks(doc, templateId, ctx) {
|
|
|
33
33
|
checks.push({
|
|
34
34
|
id: "provider",
|
|
35
35
|
ok: problem === null,
|
|
36
|
-
sentence: problem ?? "The image
|
|
37
|
-
fix: problem === null
|
|
36
|
+
sentence: problem ?? "The image service is ready.",
|
|
37
|
+
fix: problem === null
|
|
38
|
+
? null
|
|
39
|
+
: { label: "Open Keys & services", href: "/settings?section=providers" },
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
else {
|
|
@@ -56,7 +58,7 @@ export async function contextChecks(doc, templateId, ctx) {
|
|
|
56
58
|
ok: used,
|
|
57
59
|
sentence: used
|
|
58
60
|
? `Each row's date is read from {{${schedule.dateField}}}.`
|
|
59
|
-
: `The schedule reads each date from {{${schedule.dateField}}}, but the template doesn't use that
|
|
61
|
+
: `The schedule reads each date from {{${schedule.dateField}}}, but the template doesn't use that keyword. Choose a keyword it uses under Schedule.`,
|
|
60
62
|
fix: used ? null : at("schedule"),
|
|
61
63
|
});
|
|
62
64
|
}
|
|
@@ -107,12 +109,12 @@ export async function contextChecks(doc, templateId, ctx) {
|
|
|
107
109
|
});
|
|
108
110
|
return checks;
|
|
109
111
|
}
|
|
110
|
-
//
|
|
112
|
+
// Approve for schedules asks for the version's own sample, made, as Edit template does: the
|
|
111
113
|
// person has seen what a schedule will make before it makes it.
|
|
112
114
|
export function sampleCheck(db, templateId, version) {
|
|
113
115
|
const row = sampleRow(db, templateId);
|
|
114
116
|
const made = row?.state === "done" && row.version === version;
|
|
115
|
-
const how = "Make its sample, look it over beside the settings, then press
|
|
117
|
+
const how = "Make its sample, look it over beside the settings, then press Approve for schedules.";
|
|
116
118
|
return {
|
|
117
119
|
id: "sample",
|
|
118
120
|
ok: made,
|
|
@@ -9,7 +9,7 @@ export function prepare(db, designId, format) {
|
|
|
9
9
|
return {
|
|
10
10
|
ok: false,
|
|
11
11
|
reason: "not-found",
|
|
12
|
-
message: "This design no longer exists.
|
|
12
|
+
message: "This design no longer exists. Go back to Designs to choose another.",
|
|
13
13
|
};
|
|
14
14
|
const parsed = designDocumentSchema.safeParse(design.document);
|
|
15
15
|
if (!parsed.success)
|
|
@@ -13,7 +13,7 @@ const sha256 = z.string().regex(/^[a-f0-9]{64}$/);
|
|
|
13
13
|
export const designValuesSchema = z
|
|
14
14
|
.record(z.string().min(1).max(100), z.string().max(2000))
|
|
15
15
|
.refine((values) => Object.keys(values).length <= 100, {
|
|
16
|
-
message: "A design has at most 100
|
|
16
|
+
message: "A design has at most 100 keywords.",
|
|
17
17
|
});
|
|
18
18
|
export const createDesignSchema = z
|
|
19
19
|
.object({
|
|
@@ -14,7 +14,7 @@ const noProvider = {
|
|
|
14
14
|
const notFound = {
|
|
15
15
|
ok: false,
|
|
16
16
|
reason: "not-found",
|
|
17
|
-
message: "This design no longer exists.
|
|
17
|
+
message: "This design no longer exists. Go back to Designs to choose another.",
|
|
18
18
|
};
|
|
19
19
|
export function createDesignRunner(deps) {
|
|
20
20
|
const calls = createDesignCalls(deps);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { transact } from "../../kernel/db/tx.js";
|
|
2
|
+
import { plansSwitch } from "../template-studio/budget.js";
|
|
2
3
|
import { budgetSentence } from "./budget.js";
|
|
3
4
|
import { missingValues } from "./fields.js";
|
|
4
5
|
import { costChanged, costChangedMessage } from "./quote.js";
|
|
@@ -56,7 +57,7 @@ export function createDesignSamples(deps) {
|
|
|
56
57
|
const spent = sampleSpentToday(db, now(), deps.timeZone()) + sampleReserved(db, templateId);
|
|
57
58
|
const budget = deps.budget();
|
|
58
59
|
return quote.rows.some((one) => one.onPlan === true) && !deps.plans()
|
|
59
|
-
?
|
|
60
|
+
? `Waiting: this sample's picture runs on your Claude Code or Codex plan, and samples may not use it. Turn on ${plansSwitch} in Settings → Production defaults, or press Make sample to make it anyway.`
|
|
60
61
|
: quote.unknown > 0
|
|
61
62
|
? "Waiting: the price of this sample's picture isn't known, so Slopify didn't make it. Press Make sample to make it anyway."
|
|
62
63
|
: quote.high > holdSlack(acknowledgedHigh)
|
|
@@ -16,7 +16,7 @@ export const reusedMessage = "This request id was already used for a different c
|
|
|
16
16
|
const notFound = {
|
|
17
17
|
ok: false,
|
|
18
18
|
reason: "not-found",
|
|
19
|
-
message: "This design no longer exists.
|
|
19
|
+
message: "This design no longer exists. Go back to Designs to choose another.",
|
|
20
20
|
};
|
|
21
21
|
export function mutationHash(input) {
|
|
22
22
|
return createHash("sha256").update(JSON.stringify(input)).digest("hex");
|
|
@@ -21,14 +21,14 @@ export function documentChecks(doc, templateId) {
|
|
|
21
21
|
{
|
|
22
22
|
id: "fields",
|
|
23
23
|
ok: problems.length === 0,
|
|
24
|
-
sentence: problems[0]?.sentence ?? "Every
|
|
24
|
+
sentence: problems[0]?.sentence ?? "Every keyword is written as {{Name}}.",
|
|
25
25
|
fix: problems.length === 0 ? null : at("text"),
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
id: "samples",
|
|
29
29
|
ok: missing.length === 0,
|
|
30
30
|
sentence: missing.length === 0
|
|
31
|
-
? "Every
|
|
31
|
+
? "Every keyword has a sample value."
|
|
32
32
|
: `${missing.map((name) => `{{${name}}}`).join(", ")} ${missing.length === 1 ? "has" : "have"} no sample value. Fill ${missing.length === 1 ? "it" : "them"} in under Text → Sample values.`,
|
|
33
33
|
fix: missing.length === 0 ? null : at("text"),
|
|
34
34
|
},
|
|
@@ -27,7 +27,7 @@ export const makeFromTemplateSchema = z
|
|
|
27
27
|
const notFound = {
|
|
28
28
|
ok: false,
|
|
29
29
|
reason: "not-found",
|
|
30
|
-
message: "This design template no longer exists. Open
|
|
30
|
+
message: "This design template no longer exists. Open Templates → Poster templates to choose another.",
|
|
31
31
|
};
|
|
32
32
|
// What one piece made from this document costs: its artwork, or nothing when the template
|
|
33
33
|
// keeps its own picture.
|
|
@@ -53,7 +53,7 @@ export function makeFromTemplate(deps, templateId, raw, origin = {}) {
|
|
|
53
53
|
return {
|
|
54
54
|
ok: false,
|
|
55
55
|
reason: "invalid",
|
|
56
|
-
message: `The
|
|
56
|
+
message: `The poster can't be made: ${parsed.error.issues[0]?.message ?? "a keyword is missing"}.`,
|
|
57
57
|
};
|
|
58
58
|
const input = parsed.data;
|
|
59
59
|
const template = getTemplate(deps.service.db, templateId);
|
|
@@ -78,7 +78,7 @@ export function makeFromTemplate(deps, templateId, raw, origin = {}) {
|
|
|
78
78
|
return {
|
|
79
79
|
ok: false,
|
|
80
80
|
reason: "cost-changed",
|
|
81
|
-
message: costChangedMessage(quote, "this
|
|
81
|
+
message: costChangedMessage(quote, "this poster"),
|
|
82
82
|
current: quote,
|
|
83
83
|
};
|
|
84
84
|
const own = doc.picture.source === "picture" ? doc.picture.sha256 : undefined;
|
|
@@ -109,7 +109,7 @@ export function listTemplates(db) {
|
|
|
109
109
|
const notFound = {
|
|
110
110
|
ok: false,
|
|
111
111
|
reason: "not-found",
|
|
112
|
-
message: "This design template no longer exists. Open
|
|
112
|
+
message: "This design template no longer exists. Open Templates → Poster templates to choose another.",
|
|
113
113
|
};
|
|
114
114
|
function invalid(error) {
|
|
115
115
|
return {
|
|
@@ -187,7 +187,7 @@ export function saveTemplate(deps, templateId, raw) {
|
|
|
187
187
|
export async function markTemplateReady(deps, templateId, raw, more = async () => []) {
|
|
188
188
|
const parsed = markReadySchema.safeParse(raw);
|
|
189
189
|
if (!parsed.success)
|
|
190
|
-
return { ok: false, reason: "invalid", message: "Say which version to
|
|
190
|
+
return { ok: false, reason: "invalid", message: "Say which version to approve for schedules." };
|
|
191
191
|
if (getTemplate(deps.db, templateId) === undefined)
|
|
192
192
|
return notFound;
|
|
193
193
|
const revision = templateRevision(deps.db, templateId, parsed.data.version);
|
|
@@ -210,7 +210,7 @@ export async function markTemplateReady(deps, templateId, raw, more = async () =
|
|
|
210
210
|
return {
|
|
211
211
|
ok: false,
|
|
212
212
|
reason: "not-ready",
|
|
213
|
-
message: `Version ${String(parsed.data.version)} can't be
|
|
213
|
+
message: `Version ${String(parsed.data.version)} can't be approved for schedules yet: ${failed.map((check) => check.sentence).join(" ")}`,
|
|
214
214
|
current: failed,
|
|
215
215
|
};
|
|
216
216
|
const template = getTemplate(deps.db, templateId);
|
|
@@ -33,7 +33,7 @@ export function readDocumentAssets() {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
|
-
throw new Error(`Slopify couldn't read the fonts and page texture it makes documents with (${errorText(error)}). Its installation is incomplete: reinstall or update Slopify, then use Try again on
|
|
36
|
+
throw new Error(`Slopify couldn't read the fonts and page texture it makes documents with (${errorText(error)}). Its installation is incomplete: reinstall or update Slopify, then use Try again on PDF.`, { cause: error });
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
export function registerFonts(doc, fonts) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// The
|
|
2
|
-
//
|
|
3
|
-
|
|
1
|
+
// The ready looks a project's PDF can be given, so a PDF looks right without a theme of one's
|
|
2
|
+
// own: Plain, Book (book type on parchment) and Modern (a sans-serif on white). Browser-safe:
|
|
3
|
+
// Create, Edit template, Edit settings and the server all offer the same names from here.
|
|
4
|
+
export const documentThemes = ["plain", "book", "modern"];
|
|
4
5
|
// DiceMaster was built in before 2.3.0 and is one brand's look, not Slopify's. A project
|
|
5
6
|
// saved with it keeps it (see legacy-dicemaster.ts), so the name is still read everywhere a
|
|
6
7
|
// config is parsed, but it is never offered.
|
|
@@ -11,6 +12,8 @@ export const documentThemeNames = [...documentThemes, legacyDocumentTheme];
|
|
|
11
12
|
export const defaultDocumentTheme = "plain";
|
|
12
13
|
export const documentThemeLabels = {
|
|
13
14
|
plain: "Plain",
|
|
15
|
+
book: "Book",
|
|
16
|
+
modern: "Modern",
|
|
14
17
|
dicemaster: "DiceMaster",
|
|
15
18
|
};
|
|
16
19
|
// The built-in a saved project config is drawn with. A project saved before the Document stage
|
|
@@ -34,7 +37,7 @@ export const documentThemeNameMax = 80;
|
|
|
34
37
|
export function documentThemeNameProblems(name) {
|
|
35
38
|
const trimmed = name.trim();
|
|
36
39
|
if (trimmed === "")
|
|
37
|
-
return [{ field: "name", message: "Give the
|
|
40
|
+
return [{ field: "name", message: "Give the look a name." }];
|
|
38
41
|
if (trimmed.length > documentThemeNameMax)
|
|
39
42
|
return [
|
|
40
43
|
{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsPDF } from "jspdf";
|
|
2
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
2
3
|
import { runsText } from "./blocks.js";
|
|
3
4
|
import { drawLine, wrapRuns } from "./flow.js";
|
|
4
5
|
import { faceWidth, registerFonts, useFace } from "./fonts.js";
|
|
@@ -10,7 +11,7 @@ export function renderDocument(input) {
|
|
|
10
11
|
const text = documentText(input.articleMarkdown, input.researchNotes);
|
|
11
12
|
const blocks = withoutTitle(text.blocks, input.title);
|
|
12
13
|
if (blocks.length === 0)
|
|
13
|
-
throw new Error(
|
|
14
|
+
throw new Error(`The article has no text to put in the PDF. Write it in ${editSettings.article}, or press Write the article again in the project's Article section, then use Try again on PDF.`);
|
|
14
15
|
const words = blocks
|
|
15
16
|
.map((block) => block.kind === "heading" ? block.text : "runs" in block ? runsText(block.runs) : "")
|
|
16
17
|
.join(" ")
|
|
@@ -8,7 +8,7 @@ const mm = (min, max) => {
|
|
|
8
8
|
const range = { error: `Use a value from ${String(min)} to ${String(max)}.` };
|
|
9
9
|
return z.number({ error: "Enter a number." }).finite(range).min(min, range).max(max, range);
|
|
10
10
|
};
|
|
11
|
-
const color = z.string().regex(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i, "Use a
|
|
11
|
+
const color = z.string().regex(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i, "Use a color like #8c1e14.");
|
|
12
12
|
const line = z.string().max(300, { error: "Keep this under 300 characters." });
|
|
13
13
|
const nullableLine = line.nullable();
|
|
14
14
|
const url = z.string().max(500, { error: "Keep this under 500 characters." }).nullable();
|
|
@@ -9,8 +9,11 @@ const cinzel = (style, letterSpacing) => ({
|
|
|
9
9
|
style,
|
|
10
10
|
letterSpacing,
|
|
11
11
|
});
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const literata = (style) => ({ family: "Literata", style, letterSpacing: 0 });
|
|
13
|
+
const sans = (style) => ({ family: "helvetica", style, letterSpacing: 0 });
|
|
14
|
+
// The first built-in, and the default: an unbranded flat page. Its layout is the one Slopify
|
|
15
|
+
// has always drawn, written out in full rather than as changes to another theme. The other
|
|
16
|
+
// built-ins are changes to it.
|
|
14
17
|
const plain = {
|
|
15
18
|
page: { format: "a4", margin: 22, contentTop: 32 },
|
|
16
19
|
background: { image: null, color: "#fdfaf3" },
|
|
@@ -91,10 +94,53 @@ const plain = {
|
|
|
91
94
|
closing: null,
|
|
92
95
|
},
|
|
93
96
|
};
|
|
97
|
+
// Book: the article set like a novel, in Literata on the parchment texture, with Plain's Cinzel
|
|
98
|
+
// headings and drop caps in a warm brown.
|
|
99
|
+
const book = {
|
|
100
|
+
background: { image: "parchment", color: "#f5ebd7" },
|
|
101
|
+
colors: { heading: "#5a3214", text: "#231a12", muted: "#5e5246", faint: "#857767" },
|
|
102
|
+
fonts: {
|
|
103
|
+
body: literata("normal"),
|
|
104
|
+
strong: literata("bold"),
|
|
105
|
+
emphasis: literata("italic"),
|
|
106
|
+
footer: literata("italic"),
|
|
107
|
+
},
|
|
108
|
+
sizes: { body: 11 },
|
|
109
|
+
spacing: { bodyLine: 6.5, paragraphGap: 0.6 },
|
|
110
|
+
// A paragraph of a line or two starts plainly: a three-line initial beside it looks lost.
|
|
111
|
+
dropCap: { minLength: 120 },
|
|
112
|
+
};
|
|
113
|
+
// Modern: a sans-serif on a white page, blue headings and no drop caps, like a report.
|
|
114
|
+
const modern = {
|
|
115
|
+
background: { image: null, color: "#ffffff" },
|
|
116
|
+
colors: { heading: "#1d4e89", text: "#1c1c1c", muted: "#5a5a5a", faint: "#8a8a8a" },
|
|
117
|
+
fonts: {
|
|
118
|
+
body: sans("normal"),
|
|
119
|
+
strong: sans("bold"),
|
|
120
|
+
emphasis: sans("italic"),
|
|
121
|
+
heading: sans("bold"),
|
|
122
|
+
dramatic: sans("bold"),
|
|
123
|
+
decorative: sans("normal"),
|
|
124
|
+
dropCap: sans("bold"),
|
|
125
|
+
footer: sans("normal"),
|
|
126
|
+
},
|
|
127
|
+
sizes: { brand: 28, title: 24, section: 18, heading: 15, subheading: 12, body: 10.5 },
|
|
128
|
+
spacing: { bodyLine: 6, headingLine: 9, subheadingLine: 7, paragraphGap: 0.7 },
|
|
129
|
+
dropCap: { enabled: false },
|
|
130
|
+
};
|
|
94
131
|
// A built-in by name. "dicemaster" is still read, from projects saved while it was built in,
|
|
95
132
|
// and gives exactly the values it gave then.
|
|
96
133
|
export function builtInTheme(name) {
|
|
97
|
-
|
|
134
|
+
switch (name) {
|
|
135
|
+
case "book":
|
|
136
|
+
return resolveTheme(book, plain);
|
|
137
|
+
case "modern":
|
|
138
|
+
return resolveTheme(modern, plain);
|
|
139
|
+
case legacyDocumentTheme:
|
|
140
|
+
return resolveTheme({}, legacyDiceMasterTheme);
|
|
141
|
+
default:
|
|
142
|
+
return resolveTheme({}, plain);
|
|
143
|
+
}
|
|
98
144
|
}
|
|
99
145
|
// The theme a project's document is drawn with: its copy of a Library theme, or a built-in.
|
|
100
146
|
export function resolvedDocumentTheme(settings) {
|
|
@@ -53,7 +53,7 @@ export async function summarizeEpisode(deps, projectId, signal) {
|
|
|
53
53
|
return "failed";
|
|
54
54
|
deps.log.write("warn", "episode.memory", {
|
|
55
55
|
projectId,
|
|
56
|
-
detail: `The episode summary wasn't saved: ${errorText(error)}. The project is unaffected; only later episodes won't be reminded of this one. Check the
|
|
56
|
+
detail: `The episode summary wasn't saved: ${errorText(error)}. The project is unaffected; only later episodes won't be reminded of this one. Check the service it writes with in Settings → Keys & services.`,
|
|
57
57
|
});
|
|
58
58
|
return "failed";
|
|
59
59
|
}
|
|
@@ -82,7 +82,7 @@ async function summarize(deps, projectId, signal) {
|
|
|
82
82
|
if (choice === undefined) {
|
|
83
83
|
deps.log.write("info", "episode.memory", {
|
|
84
84
|
projectId,
|
|
85
|
-
detail: "No episode summary: this project has no
|
|
85
|
+
detail: "No episode summary: this project has no AI model chosen, so later episodes won't be reminded of it.",
|
|
86
86
|
});
|
|
87
87
|
return "no-llm";
|
|
88
88
|
}
|
|
@@ -42,14 +42,13 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
42
42
|
const tts = draft.audio ?? { provider: "", model: "" };
|
|
43
43
|
const image = draft.images ?? { provider: "", model: "" };
|
|
44
44
|
const textNote = isLocalCliProvider(llm.provider)
|
|
45
|
-
? "Runs on your
|
|
45
|
+
? "Runs on your subscription at no extra charge; the API figure is what the same work would cost through the API."
|
|
46
46
|
: (data.llm.find((model) => model.provider === llm.provider &&
|
|
47
47
|
model.id === llm.model &&
|
|
48
48
|
model.enabled &&
|
|
49
|
-
!model.deprecated)?.pricing.note ??
|
|
50
|
-
"CLI subscription or model pricing is unavailable; usage may be billed by your account.");
|
|
49
|
+
!model.deprecated)?.pricing.note ?? "No price is listed for this model; your account may be billed for it.");
|
|
51
50
|
const imageNote = isLocalCliProvider(image.provider)
|
|
52
|
-
? "Runs on your Codex
|
|
51
|
+
? "Runs on your Codex subscription at no extra charge."
|
|
53
52
|
: (data.image.find((model) => model.provider === image.provider &&
|
|
54
53
|
model.id === image.model &&
|
|
55
54
|
model.enabled &&
|
|
@@ -98,15 +97,18 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
98
97
|
code: draft.audio?.skipCode === true ? "skip" : "describe",
|
|
99
98
|
language: draft.language,
|
|
100
99
|
}));
|
|
101
|
-
|
|
100
|
+
// On a subscription the count does not change the price.
|
|
101
|
+
if (blocks === undefined && isLocalCliProvider(llm.provider))
|
|
102
|
+
local("Describing tables and figures", "One AI call per table, figure, equation or code block in the article, on your subscription at no extra charge.");
|
|
103
|
+
else if (blocks === undefined)
|
|
102
104
|
requests.push({
|
|
103
105
|
kind: "unknown",
|
|
104
|
-
stage: "
|
|
105
|
-
detail: "One
|
|
106
|
+
stage: "Describing tables and figures",
|
|
107
|
+
detail: "One AI call per table, figure, equation or code block in the article; how many is known once the article is written.",
|
|
106
108
|
});
|
|
107
109
|
else
|
|
108
110
|
for (const block of blocks)
|
|
109
|
-
text("
|
|
111
|
+
text("Describing tables and figures", describeMessages(block, rendered.narration ?? "", draft.language).reduce((n, message) => n + message.content.length, 0), 600, `${String(blocks.length)} AI ${blocks.length === 1 ? "call" : "calls"}: one per table, figure, equation or code block.`);
|
|
110
112
|
}
|
|
111
113
|
if (draft.sources.audio === "generate") {
|
|
112
114
|
if (usesNarrationPreparation(draft)) {
|
|
@@ -122,16 +124,16 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
122
124
|
if (unknown)
|
|
123
125
|
requests.push({
|
|
124
126
|
kind: "unknown",
|
|
125
|
-
stage: "Narration
|
|
126
|
-
detail: "One
|
|
127
|
+
stage: "Narration preparation",
|
|
128
|
+
detail: "One AI call per narration piece and per intro or outro written for it; how many is known once the text is written.",
|
|
127
129
|
});
|
|
128
130
|
else
|
|
129
131
|
for (const source of known)
|
|
130
|
-
text("Narration
|
|
132
|
+
text("Narration preparation", preparationMessages(rendered.narration ?? "", source, [], draft.language).reduce((n, message) => n + message.content.length, 0), 2400, `${known.length} AI calls: one per narration piece or intro or outro.`);
|
|
131
133
|
requests.push({
|
|
132
134
|
kind: "unknown",
|
|
133
|
-
stage: "Delivery
|
|
134
|
-
detail: "
|
|
135
|
+
stage: "Delivery cues",
|
|
136
|
+
detail: "The voice service charges per character, delivery tags included; their length is known after preparation.",
|
|
135
137
|
});
|
|
136
138
|
}
|
|
137
139
|
// A template studio sample says only the narration's opening (`template-studio/scope.ts`).
|
|
@@ -155,9 +157,9 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
155
157
|
if (extras + llmExtras > 0)
|
|
156
158
|
requests.push({ ...choice, kind: "tts-estimate", characters: extras + llmExtras });
|
|
157
159
|
if (draft.voices?.source === "attribute")
|
|
158
|
-
text("Speaker split", articleChars + 1500, Math.round(articleChars * 1.1), "One
|
|
160
|
+
text("Speaker split", articleChars + 1500, Math.round(articleChars * 1.1), "One AI call hands the text's passages to the speakers.");
|
|
159
161
|
if (draft.voices?.source === "adapt")
|
|
160
|
-
text("Conversation script", articleChars + 1500, Math.round(articleChars * 1.2), "One
|
|
162
|
+
text("Conversation script", articleChars + 1500, Math.round(articleChars * 1.2), "One AI call rewrites the text as a conversation between the speakers; the article stays as written.");
|
|
161
163
|
}
|
|
162
164
|
else
|
|
163
165
|
requests.push(generatedArticle || llmExtras > 0
|
|
@@ -230,7 +232,11 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
230
232
|
local("Thumbnail", "Provided or off.");
|
|
231
233
|
if (draft.sources.thumbnail === "prompt_by_llm")
|
|
232
234
|
text("Thumbnail prompt", promptChars + articleChars, 1200);
|
|
233
|
-
|
|
235
|
+
// Putting it together on this computer: the video (with its captions), or the sound alone.
|
|
236
|
+
if (draft.sources.video === "generate")
|
|
237
|
+
local("Video", "Made on this computer; no API fee.");
|
|
238
|
+
else if (draft.sources.audio !== "off")
|
|
239
|
+
local("Audio export", "Made on this computer; no API fee.");
|
|
234
240
|
// The timed transcript is the narration with a time before every passage of about 20 s,
|
|
235
241
|
// plus the fixed rules of the answer.
|
|
236
242
|
if (usesYoutubeDescription(draft))
|
|
@@ -238,13 +244,13 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
238
244
|
? rendered.description.length
|
|
239
245
|
: defaultDescriptionPrompt.length) +
|
|
240
246
|
Math.round(articleChars * 1.05) +
|
|
241
|
-
1500, 2400, "One
|
|
247
|
+
1500, 2400, "One AI call on the timed transcript.");
|
|
242
248
|
// Each site whose post is written by a post prompt: one call that reads the article.
|
|
243
249
|
for (const platform of socialPlatforms) {
|
|
244
250
|
if (draft.social?.[platform]?.text !== "prompt")
|
|
245
251
|
continue;
|
|
246
252
|
const own = rendered[`social.${platform}`]?.trim() ?? "";
|
|
247
|
-
text("Posts", (own === "" ? defaultPostPrompt.length : own.length) + articleChars + 400, 600, "One
|
|
253
|
+
text("Posts", (own === "" ? defaultPostPrompt.length : own.length) + articleChars + 400, 600, "One AI call per site whose post a post prompt writes, reading the article.");
|
|
248
254
|
}
|
|
249
255
|
// Shorts: one call picks the clips from the numbered transcript, one per clip writes its
|
|
250
256
|
// image prompts, and the images are charged at the most the step can ask for, every clip
|
|
@@ -257,12 +263,12 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
257
263
|
imageSecondsProblem(draft.imageSeconds) === undefined) {
|
|
258
264
|
text("Shorts", (rendered.shorts?.trim() ? rendered.shorts.length : defaultShortsPrompt.length) +
|
|
259
265
|
Math.round(articleChars * 1.15) +
|
|
260
|
-
1500, 400 * shorts.count, "One
|
|
266
|
+
1500, 400 * shorts.count, "One AI call on the numbered transcript picks the clips.");
|
|
261
267
|
const style = rendered.shortsImage?.trim()
|
|
262
268
|
? rendered.shortsImage.length
|
|
263
269
|
: defaultShortsImagePrompt.length;
|
|
264
270
|
for (let clip = 0; clip < shorts.count; clip++)
|
|
265
|
-
text("Shorts", style + 3000, 2400, "One
|
|
271
|
+
text("Shorts", style + 3000, 2400, "One AI call per short writes its image prompts.");
|
|
266
272
|
const count = shortsImageUpperBound(shorts, draft.imageSeconds);
|
|
267
273
|
for (let index = 0; index < count; index++)
|
|
268
274
|
requests.push({
|
|
@@ -298,7 +304,7 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
298
304
|
const reviews = draft.reviews;
|
|
299
305
|
if (reviews !== undefined && reviewedByAgent(reviews)) {
|
|
300
306
|
if (activeReviewStages(draft).length > 0)
|
|
301
|
-
local("Reviews", "
|
|
307
|
+
local("Reviews", "Your own AI agent answers each check; no API fee.");
|
|
302
308
|
}
|
|
303
309
|
else if (reviews !== undefined)
|
|
304
310
|
for (const stage of activeReviewStages(draft)) {
|
|
@@ -324,7 +330,7 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
324
330
|
});
|
|
325
331
|
}
|
|
326
332
|
if (sourceOf(draft.sources, "document") === "generate")
|
|
327
|
-
local("
|
|
333
|
+
local("PDF", "Laid out on this computer from the article; no API fee.");
|
|
328
334
|
return {
|
|
329
335
|
...groupEstimateRows(estimateRequests(requests, data)),
|
|
330
336
|
expectedWords,
|
|
@@ -38,8 +38,8 @@ export function estimateRequests(requests, catalogue) {
|
|
|
38
38
|
catalogueDate: catalogue.updatedAt,
|
|
39
39
|
assumptions: [
|
|
40
40
|
"Planning estimate, not a spending limit. Generated lengths use a ±50% range; actual output can exceed it.",
|
|
41
|
-
"Assumes
|
|
42
|
-
"Retries, extra
|
|
41
|
+
"Assumes about 6 letters per word and 150 spoken words a minute.",
|
|
42
|
+
"Retries, extra thinking, web search, taxes, discounts and included credits are not counted. A charge with no listed price is not counted as zero.",
|
|
43
43
|
],
|
|
44
44
|
};
|
|
45
45
|
}
|
|
@@ -99,8 +99,8 @@ function onPlan(request, catalogue) {
|
|
|
99
99
|
high: 0,
|
|
100
100
|
detail: request.detail ??
|
|
101
101
|
(amount === null
|
|
102
|
-
? "Runs on your
|
|
103
|
-
: `Runs on your
|
|
102
|
+
? "Runs on your subscription at no extra charge; no API price is listed for this model."
|
|
103
|
+
: `Runs on your subscription at no extra charge; through the API it would cost what ${api?.name ?? "the API model"} costs.`),
|
|
104
104
|
onPlan: true,
|
|
105
105
|
apiLow: amount === null ? null : amount * 0.5,
|
|
106
106
|
apiHigh: amount === null ? null : amount * 1.5,
|
|
@@ -35,7 +35,9 @@ export function fixFor(step) {
|
|
|
35
35
|
const name = nameOf(step.provider);
|
|
36
36
|
return {
|
|
37
37
|
kind: "provider-settings",
|
|
38
|
-
label: name === undefined
|
|
38
|
+
label: name === undefined
|
|
39
|
+
? "Open Settings → Keys & services"
|
|
40
|
+
: `Open Settings → Keys & services → ${name}`,
|
|
39
41
|
provider: step.provider ?? "",
|
|
40
42
|
};
|
|
41
43
|
}
|
|
@@ -3,6 +3,7 @@ import { lstat, readdir } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { isUnreachablePath } from "../../kernel/errors.js";
|
|
6
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
6
7
|
import { discoverSystemFonts } from "./discovery.js";
|
|
7
8
|
import { readFontFile } from "./files.js";
|
|
8
9
|
import { fontSummary } from "./model.js";
|
|
@@ -55,7 +56,7 @@ export async function resolveBoldFont(paths, id) {
|
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
export function missingFont() {
|
|
58
|
-
return new Error(
|
|
59
|
+
return new Error(`The chosen caption font is no longer available; it may have been uninstalled or deleted. Choose another font in ${editSettings.captions}, then try again.`, {
|
|
59
60
|
cause: "font-not-found",
|
|
60
61
|
});
|
|
61
62
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
2
|
import { languageInfo, languageLetters } from "../../kernel/ports/languages.js";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
3
4
|
import { resolveBoldFont, resolveFont } from "./catalog.js";
|
|
4
5
|
import { discoverSystemFonts } from "./discovery.js";
|
|
5
6
|
import { readFontFile } from "./files.js";
|
|
@@ -45,7 +46,7 @@ export async function captionFont(deps, fontId, language) {
|
|
|
45
46
|
for (const font of await deps.system())
|
|
46
47
|
if (await covers(font, letters))
|
|
47
48
|
return font;
|
|
48
|
-
throw new Error(`The caption font ${chosen.name} has no ${info.name} letters, and Slopify doesn't ship a font for ${info.name} (they are 10-20 MB). In
|
|
49
|
+
throw new Error(`The caption font ${chosen.name} has no ${info.name} letters, and Slopify doesn't ship a font for ${info.name} (they are 10-20 MB). In ${editSettings.captions}, use Upload font in the caption font picker to add a font that has them (for example Noto Sans ${info.code === "ja" ? "JP" : info.code === "ko" ? "KR" : "SC"} from fonts.google.com), choose it there, then Try again.`);
|
|
49
50
|
}
|
|
50
51
|
// The same for text drawn bold (the shorts' captions): the chosen font's bold face when it has
|
|
51
52
|
// the letters, else the fallback, which the renderer emboldens.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonBetween } from "../../kernel/json-answer.js";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
3
4
|
import { appearanceKeyword, detectSlots, render } from "../admission/substitute.js";
|
|
4
5
|
// How things look, looked up rather than guessed: when an image or thumbnail prompt has
|
|
5
6
|
// `{{Appearance}}`, one call to the project's AI model searches the web for how the video's
|
|
@@ -44,7 +45,7 @@ export function appearanceMessages(brief) {
|
|
|
44
45
|
];
|
|
45
46
|
}
|
|
46
47
|
export function checkAppearance(text) {
|
|
47
|
-
const fix =
|
|
48
|
+
const fix = `Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`;
|
|
48
49
|
const parsed = appearanceSchema.safeParse(jsonBetween(text, "{", "}"));
|
|
49
50
|
if (!parsed.success)
|
|
50
51
|
return { ok: false, reason: `The AI model didn't answer with the looks it looked up. ${fix}` };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonBetween } from "../../kernel/json-answer.js";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
3
4
|
import { detectSlots, render, sceneKeyword } from "../admission/substitute.js";
|
|
4
5
|
// Scenes from the article (Images → Scenes from the article): each image is drawn from its
|
|
5
6
|
// own scene instead of the same text every time. One call to the project's AI model reads the
|
|
@@ -197,7 +198,7 @@ const sceneAnswer = z.array(z.union([
|
|
|
197
198
|
]));
|
|
198
199
|
// A list of scenes, or of scenes with their setting and mood (`details`, only then present).
|
|
199
200
|
export function checkScenes(text, count) {
|
|
200
|
-
const fix =
|
|
201
|
+
const fix = `Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`;
|
|
201
202
|
const parsed = sceneAnswer.safeParse(jsonBetween(text, "[", "]"));
|
|
202
203
|
if (!parsed.success)
|
|
203
204
|
return {
|