@gentbajko/slopify 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/account-limits.js +1 -1
- package/dist/adapters/alignment/audio.js +1 -1
- package/dist/adapters/alignment/cache.js +1 -1
- package/dist/adapters/alignment/multilingual.js +2 -1
- package/dist/adapters/alignment/runner.js +1 -1
- package/dist/adapters/alignment/worker.js +1 -1
- package/dist/adapters/explain.js +10 -9
- package/dist/adapters/host-cli/index.js +2 -1
- package/dist/adapters/host-cli/transport.js +2 -1
- package/dist/adapters/image/bytes.js +2 -1
- package/dist/adapters/image/codex.js +6 -5
- package/dist/adapters/image/fal.js +4 -3
- package/dist/adapters/image/google.js +3 -2
- package/dist/adapters/image/models.js +2 -2
- package/dist/adapters/image/openai.js +2 -1
- package/dist/adapters/image/replicate.js +3 -2
- package/dist/adapters/image/video.js +2 -1
- package/dist/adapters/key-probes.js +1 -1
- package/dist/adapters/llm/antigravity-models.js +1 -1
- package/dist/adapters/llm/antigravity.js +7 -6
- package/dist/adapters/llm/assistant-cli.js +1 -1
- package/dist/adapters/llm/claude-code-models.js +1 -1
- package/dist/adapters/llm/claude-code.js +3 -2
- package/dist/adapters/llm/codex-models.js +1 -1
- package/dist/adapters/llm/document-workspace.js +3 -2
- package/dist/adapters/llm/image-workspace.js +3 -2
- package/dist/adapters/llm/openrouter.js +2 -1
- package/dist/adapters/llm/run-cli.js +3 -2
- package/dist/adapters/tts/gemini.js +2 -1
- package/dist/adapters/tts/inworld-async.js +2 -1
- package/dist/adapters/tts/inworld.js +3 -2
- package/dist/adapters/tts/system.js +2 -1
- package/dist/catalog/registry.js +20 -13
- package/dist/catalog/schema.js +2 -1
- package/dist/catalog/validate.js +5 -2
- package/dist/edge/http/actions.js +7 -7
- package/dist/edge/http/app.js +3 -3
- package/dist/edge/http/assistant.js +3 -1
- package/dist/edge/http/backups.js +1 -1
- package/dist/edge/http/channel-memory.js +2 -2
- package/dist/edge/http/channels.js +1 -1
- package/dist/edge/http/checkpoints.js +7 -5
- package/dist/edge/http/design-templates.js +3 -3
- package/dist/edge/http/designs.js +1 -1
- package/dist/edge/http/document-themes.js +3 -3
- package/dist/edge/http/draft-problem.js +1 -1
- package/dist/edge/http/extension-auth.js +1 -1
- package/dist/edge/http/home.js +9 -1
- package/dist/edge/http/model-checks.js +4 -4
- package/dist/edge/http/onboarding.js +29 -12
- package/dist/edge/http/outputs.js +3 -2
- package/dist/edge/http/patch-notes.js +2 -2
- package/dist/edge/http/planning.js +1 -1
- package/dist/edge/http/project-create.js +1 -1
- package/dist/edge/http/project-templates.js +2 -2
- package/dist/edge/http/projects.js +22 -4
- package/dist/edge/http/prompts.js +2 -2
- package/dist/edge/http/providers.js +9 -10
- package/dist/edge/http/refusal.js +1 -1
- package/dist/edge/http/remote-access.js +4 -4
- package/dist/edge/http/remote-guard.js +2 -2
- package/dist/edge/http/reviews.js +1 -1
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +4 -4
- package/dist/edge/http/schedules.js +13 -11
- package/dist/edge/http/settings.js +6 -2
- package/dist/edge/http/social.js +5 -4
- package/dist/edge/http/staging.js +1 -1
- package/dist/edge/http/storage.js +12 -4
- package/dist/edge/http/studio.js +71 -19
- package/dist/edge/http/style-preview.js +2 -2
- package/dist/edge/http/template-studio.js +12 -9
- package/dist/edge/http/trash.js +1 -1
- package/dist/edge/http/youtube-edits.js +1 -1
- package/dist/edge/mcp/excluded-routes.js +4 -3
- package/dist/edge/mcp/instructions.js +3 -3
- package/dist/edge/mcp/library-tools.js +8 -5
- package/dist/edge/mcp/remake-tools.js +7 -4
- package/dist/edge/mcp/server.js +1 -1
- package/dist/edge/mcp/tools-channels.js +9 -9
- package/dist/edge/mcp/tools-designs.js +9 -9
- package/dist/edge/mcp/tools-library.js +7 -7
- package/dist/edge/mcp/tools-planning.js +18 -18
- package/dist/edge/mcp/tools-projects.js +10 -10
- package/dist/edge/mcp/tools-publishing.js +9 -9
- package/dist/edge/mcp/tools-runs.js +9 -9
- package/dist/edge/mcp/tools-schedules.js +6 -6
- package/dist/edge/mcp/tools-setup.js +20 -14
- package/dist/edge/mcp/tools-social.js +5 -4
- package/dist/edge/mcp/tools-storage.js +3 -3
- package/dist/edge/mcp/tools-template-studio.js +18 -18
- package/dist/edge/remote-access/model.js +1 -1
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
- package/dist/kernel/pipeline.js +12 -0
- package/dist/kernel/ports/settings-places.js +31 -0
- package/dist/kernel/ports/system-speech.js +31 -3
- package/dist/kernel/runner/attempt.js +11 -3
- package/dist/kernel/runner/providers.js +3 -2
- package/dist/kernel/runner/standalone.js +2 -2
- package/dist/main.js +5 -3
- package/dist/model-catalog.js +20 -0
- package/dist/patch-notes/4.0.0.md +13 -13
- package/dist/patch-notes/4.1.0.md +42 -0
- package/dist/patch-notes/index.json +6 -0
- package/dist/slices/admission/listing-facts.js +95 -0
- package/dist/slices/admission/repo.js +9 -6
- package/dist/slices/admission/rules.js +7 -7
- package/dist/slices/admission/state-words.js +34 -5
- package/dist/slices/assistant/cost.js +1 -1
- package/dist/slices/assistant/model.js +10 -3
- package/dist/slices/assistant/turn.js +5 -3
- package/dist/slices/autopilot/warnings.js +1 -1
- package/dist/slices/backups/service.js +5 -5
- package/dist/slices/channels/cast-images.js +1 -1
- package/dist/slices/channels/schema.js +1 -1
- package/dist/slices/channels/videos.js +1 -1
- package/dist/slices/control/providers.js +2 -2
- package/dist/slices/designs/budget.js +1 -1
- package/dist/slices/designs/calls.js +2 -2
- package/dist/slices/designs/check-context.js +4 -4
- package/dist/slices/designs/fields.js +4 -4
- package/dist/slices/designs/gate.js +7 -5
- package/dist/slices/designs/render-input.js +1 -1
- package/dist/slices/designs/requests.js +1 -1
- package/dist/slices/designs/runner.js +1 -1
- package/dist/slices/designs/sample.js +2 -1
- package/dist/slices/designs/service.js +1 -1
- package/dist/slices/designs/template-checks.js +2 -2
- package/dist/slices/designs/template-make.js +3 -3
- package/dist/slices/designs/templates.js +3 -3
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/model.js +7 -4
- package/dist/slices/document/render.js +2 -1
- package/dist/slices/document/theme-schema.js +1 -1
- package/dist/slices/document/theme.js +49 -3
- package/dist/slices/episodes/summarize.js +2 -2
- package/dist/slices/estimate/index.js +28 -22
- package/dist/slices/estimate/requests.js +4 -4
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/fonts/catalog.js +2 -1
- package/dist/slices/fonts/coverage.js +2 -1
- package/dist/slices/images/appearance.js +2 -1
- package/dist/slices/images/scenes.js +2 -1
- package/dist/slices/keywords/digest.js +2 -1
- package/dist/slices/keywords/rules.js +2 -2
- package/dist/slices/keywords/specs.js +5 -5
- package/dist/slices/keywords/values.js +1 -1
- package/dist/slices/library/transfer.js +3 -3
- package/dist/slices/loudness/loudnorm.js +2 -1
- package/dist/slices/loudness/model.js +3 -2
- package/dist/slices/loudness/sentence-level.js +2 -1
- package/dist/slices/model-checks/rules.js +13 -0
- package/dist/slices/model-upkeep/model.js +1 -1
- package/dist/slices/model-upkeep/switch.js +3 -3
- package/dist/slices/model-upkeep/usage.js +13 -2
- package/dist/slices/narration/breaths.js +2 -1
- package/dist/slices/narration/clean-pieces.js +2 -1
- package/dist/slices/narration/pauses-model.js +3 -2
- package/dist/slices/narration/pronunciation.js +3 -2
- package/dist/slices/notifications/subject.js +2 -2
- package/dist/slices/onboarding/copy.js +19 -2
- package/dist/slices/onboarding/first-run.js +13 -38
- package/dist/slices/onboarding/install.js +2 -1
- package/dist/slices/onboarding/model.js +26 -21
- package/dist/slices/onboarding/preferred-model.js +14 -0
- package/dist/slices/onboarding/quick-short.js +103 -64
- package/dist/slices/onboarding/sample.js +4 -1
- package/dist/slices/onboarding/state.js +7 -0
- package/dist/slices/onboarding/template.js +6 -5
- package/dist/slices/outputs/add.js +3 -2
- package/dist/slices/outputs/service.js +2 -2
- package/dist/slices/patch-notes/library.js +1 -1
- package/dist/slices/play-drafts/readiness.js +3 -3
- package/dist/slices/play-drafts/review-inputs.js +1 -1
- package/dist/slices/play-drafts/service.js +17 -7
- package/dist/slices/project-templates/next-chapter.js +2 -1
- package/dist/slices/project-templates/provider-defaults.js +22 -0
- package/dist/slices/project-templates/setup-sections.js +26 -0
- package/dist/slices/project-templates/setup.js +6 -1
- package/dist/slices/rebuild/preview-details.js +5 -5
- package/dist/slices/rebuild/recipe-preparation.js +1 -1
- package/dist/slices/rebuild/recipe-save.js +2 -2
- package/dist/slices/rebuild/recipe-validation.js +5 -4
- package/dist/slices/rebuild/recipe-voices.js +3 -2
- package/dist/slices/rebuild/recipe-work.js +4 -4
- package/dist/slices/rebuild/recovery.js +6 -6
- package/dist/slices/rebuild/remake-outdated.js +5 -5
- package/dist/slices/rebuild/review-redo.js +1 -1
- package/dist/slices/rebuild/runtime-animate.js +2 -2
- package/dist/slices/rebuild/runtime-article.js +4 -3
- package/dist/slices/rebuild/runtime-document.js +5 -4
- package/dist/slices/rebuild/runtime-export-bed.js +2 -1
- package/dist/slices/rebuild/runtime-export-edit.js +2 -1
- package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
- package/dist/slices/rebuild/runtime-export-short.js +1 -1
- package/dist/slices/rebuild/runtime-export.js +4 -3
- package/dist/slices/rebuild/runtime-image.js +4 -3
- package/dist/slices/rebuild/runtime-local.js +3 -2
- package/dist/slices/rebuild/runtime-narration-text.js +3 -2
- package/dist/slices/rebuild/runtime-provider.js +7 -6
- package/dist/slices/rebuild/runtime-review.js +6 -5
- package/dist/slices/rebuild/runtime-shorts.js +6 -5
- package/dist/slices/rebuild/runtime-store.js +17 -2
- package/dist/slices/rebuild/runtime-subtitles.js +13 -12
- package/dist/slices/rebuild/runtime-voices.js +2 -1
- package/dist/slices/rebuild/runtime-youtube.js +3 -2
- package/dist/slices/rebuild/service-readiness.js +11 -3
- package/dist/slices/research/synthesis.js +3 -2
- package/dist/slices/reviews/external-model.js +1 -1
- package/dist/slices/reviews/holds.js +5 -4
- package/dist/slices/reviews/model.js +8 -5
- package/dist/slices/reviews/rules.js +3 -3
- package/dist/slices/reviews/verdict.js +2 -1
- package/dist/slices/revisions/image-plan.js +1 -1
- package/dist/slices/revisions/mutation-assets.js +2 -1
- package/dist/slices/revisions/mutations.js +3 -2
- package/dist/slices/schedules/agenda.js +7 -5
- package/dist/slices/schedules/design-check.js +4 -4
- package/dist/slices/schedules/design-run.js +3 -3
- package/dist/slices/schedules/repo.js +6 -5
- package/dist/slices/schedules/schema.js +3 -0
- package/dist/slices/schedules/topics.js +5 -5
- package/dist/slices/settings/first-run.js +6 -32
- package/dist/slices/settings/health.js +30 -15
- package/dist/slices/settings/key-test.js +1 -1
- package/dist/slices/settings/model.js +4 -2
- package/dist/slices/settings/playback.js +2 -2
- package/dist/slices/shorts/keep.js +2 -1
- package/dist/slices/shorts/pick.js +4 -3
- package/dist/slices/shorts/prompts.js +2 -1
- package/dist/slices/shorts/render.js +3 -2
- package/dist/slices/social/media.js +5 -4
- package/dist/slices/social/pack.js +1 -1
- package/dist/slices/social/post-prompt.js +2 -1
- package/dist/slices/social/reshape.js +2 -2
- package/dist/slices/social/serve.js +1 -1
- package/dist/slices/social/service.js +3 -2
- package/dist/slices/social/state.js +1 -1
- package/dist/slices/storage/backup-import.js +2 -2
- package/dist/slices/storage/portable.js +1 -1
- package/dist/slices/storage/trim.js +23 -13
- package/dist/slices/studio/calendar.js +3 -1
- package/dist/slices/studio/disclosure.js +4 -4
- package/dist/slices/studio/model.js +1 -1
- package/dist/slices/studio/pack.js +9 -8
- package/dist/slices/studio/plan.js +1 -1
- package/dist/slices/studio/settings.js +6 -6
- package/dist/slices/studio/videos.js +23 -4
- package/dist/slices/style-preview/render.js +3 -3
- package/dist/slices/style-preview/schema.js +1 -1
- package/dist/slices/subtitles/captions.js +1 -1
- package/dist/slices/template-studio/anatomy-view.js +46 -3
- package/dist/slices/template-studio/bakeoff.js +3 -3
- package/dist/slices/template-studio/budget.js +6 -3
- package/dist/slices/template-studio/cards.js +78 -0
- package/dist/slices/template-studio/checks.js +99 -50
- package/dist/slices/template-studio/for-all.js +5 -5
- package/dist/slices/template-studio/free-video.js +1 -1
- package/dist/slices/template-studio/free.js +5 -5
- package/dist/slices/template-studio/model.js +19 -15
- package/dist/slices/template-studio/pack-import.js +14 -14
- package/dist/slices/template-studio/pack.js +9 -7
- package/dist/slices/template-studio/paid.js +12 -5
- package/dist/slices/template-studio/quote.js +18 -11
- package/dist/slices/template-studio/ready.js +7 -7
- package/dist/slices/template-studio/runner.js +1 -1
- package/dist/slices/template-studio/save.js +12 -12
- package/dist/slices/template-studio/say-it-as.js +3 -3
- package/dist/slices/template-studio/schedule-gate.js +1 -1
- package/dist/slices/template-studio/schedule-price.js +3 -1
- package/dist/slices/template-studio/schedule-sample.js +5 -1
- package/dist/slices/template-studio/schema.js +25 -0
- package/dist/slices/template-studio/service.js +3 -1
- package/dist/slices/template-studio/status.js +26 -9
- package/dist/slices/template-studio/topic.js +3 -3
- package/dist/slices/thumbnail/by-llm.js +1 -1
- package/dist/slices/uploads/repo.js +37 -1
- package/dist/slices/uploads/rules.js +3 -2
- package/dist/slices/video/edit-list.js +2 -2
- package/dist/slices/video/edit-settings.js +1 -1
- package/dist/slices/video/encoding.js +14 -5
- package/dist/slices/video/ffmpeg.js +2 -2
- package/dist/slices/video/plan.js +2 -1
- package/dist/slices/video/slideshow.js +1 -1
- package/dist/slices/voices/attribution.js +3 -2
- package/dist/slices/voices/model.js +9 -8
- package/dist/slices/voices/script.js +2 -1
- package/dist/slices/youtube/answer.js +2 -1
- package/dist/slices/youtube/edits-repo.js +1 -1
- package/dist/tutorials/AI-CLIs.md +25 -25
- package/dist/tutorials/Agents-MCP.md +32 -32
- package/dist/tutorials/Assistant.md +5 -5
- package/dist/tutorials/Backups.md +7 -7
- package/dist/tutorials/Calendar.md +17 -25
- package/dist/tutorials/Cast-Library.md +19 -19
- package/dist/tutorials/Channels.md +40 -35
- package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
- package/dist/tutorials/Create-Article.md +114 -0
- package/dist/tutorials/Create-Extras.md +112 -0
- package/dist/tutorials/Create-Images.md +165 -0
- package/dist/tutorials/Create-Narration.md +178 -0
- package/dist/tutorials/Create-Overview.md +244 -0
- package/dist/tutorials/Create-Video-and-Style.md +144 -0
- package/dist/tutorials/Docker.md +3 -3
- package/dist/tutorials/Document-Themes.md +32 -32
- package/dist/tutorials/Editing-a-Project.md +94 -94
- package/dist/tutorials/FAQ.md +18 -18
- package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
- package/dist/tutorials/Home-and-Projects.md +101 -76
- package/dist/tutorials/Home.md +33 -33
- package/dist/tutorials/Install.md +13 -13
- package/dist/tutorials/Intros-and-Outros.md +33 -34
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
- package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
- package/dist/tutorials/Models.md +27 -27
- package/dist/tutorials/Multiple-Voices.md +41 -40
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
- package/dist/tutorials/Notifications.md +21 -17
- package/dist/tutorials/Other-Languages.md +15 -15
- package/dist/tutorials/PDF-Documents.md +38 -29
- package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
- package/dist/tutorials/Project-Page.md +118 -120
- package/dist/tutorials/Prompts-and-Templates.md +84 -0
- package/dist/tutorials/Prompts.md +45 -45
- package/dist/tutorials/Providers-and-Keys.md +105 -98
- package/dist/tutorials/Publishing-to-YouTube.md +31 -31
- package/dist/tutorials/Recovery-and-Retries.md +31 -31
- package/dist/tutorials/Remote-Access.md +2 -2
- package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
- package/dist/tutorials/Schedules.md +27 -28
- package/dist/tutorials/Settings-Reference.md +182 -204
- package/dist/tutorials/Shorts.md +34 -33
- package/dist/tutorials/Start-at-Login.md +3 -3
- package/dist/tutorials/Studio-Extension.md +22 -24
- package/dist/tutorials/Template-Studio.md +45 -42
- package/dist/tutorials/Templates.md +50 -79
- package/dist/tutorials/Trash.md +4 -4
- package/dist/tutorials/Troubleshooting.md +44 -44
- package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
- package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
- package/dist/tutorials/Video-Editing.md +34 -34
- package/dist/tutorials/Where-Your-Files-Live.md +10 -10
- package/dist/tutorials/YouTube-Description.md +26 -26
- package/dist/tutorials/Your-First-Short.md +35 -35
- package/dist/tutorials/_Sidebar.md +15 -15
- package/dist/web/assets/index-BNftKQrA.css +1 -0
- package/dist/web/assets/index-Cpa58woP.js +459 -0
- package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
- package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
- package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/tutorials/Library-Overview.md +0 -76
- package/dist/tutorials/Play-Images.md +0 -165
- package/dist/tutorials/Play-Narration.md +0 -167
- package/dist/tutorials/Play-Outputs.md +0 -109
- package/dist/tutorials/Play-Overview.md +0 -227
- package/dist/tutorials/Play-Title-and-Article.md +0 -136
- package/dist/tutorials/Play-Video-and-Style.md +0 -143
- package/dist/web/assets/index-CkAI_2E7.css +0 -1
- package/dist/web/assets/index-DcD0cdfo.js +0 -334
- package/dist/web/assets/pdf-TODRwF31.js +0 -54
- package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
- package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
|
@@ -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 {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
2
3
|
import { keywordSpecs } from "./specs.js";
|
|
3
4
|
// The keyword digest: one call to the project's AI model reads the article once and writes every
|
|
4
5
|
// digest keyword (`{{Slopify_Summary}}`, `{{Slopify_Hook}}`…), so a run that uses several pays for
|
|
@@ -39,7 +40,7 @@ const digestSchema = z.object({
|
|
|
39
40
|
tags: listOrText,
|
|
40
41
|
});
|
|
41
42
|
export function checkDigest(text) {
|
|
42
|
-
const fix =
|
|
43
|
+
const fix = `Use Try again; if it keeps happening, choose another model in ${editSettings.writer}.`;
|
|
43
44
|
const parsed = digestSchema.safeParse(jsonObjectOf(text));
|
|
44
45
|
if (!parsed.success)
|
|
45
46
|
return {
|
|
@@ -167,7 +167,7 @@ export function placedProblems(bodies, values) {
|
|
|
167
167
|
field,
|
|
168
168
|
message: place === "title"
|
|
169
169
|
? problem.message
|
|
170
|
-
: `${problem.message} Change the prompt in
|
|
170
|
+
: `${problem.message} Change the prompt in Prompts, or pick another one.`,
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
173
|
return problems;
|
|
@@ -180,7 +180,7 @@ export function textEntryProblems(body, category) {
|
|
|
180
180
|
? [
|
|
181
181
|
{
|
|
182
182
|
field: category,
|
|
183
|
-
message: `{{Slopify_Release_Date}} can't be used in
|
|
183
|
+
message: `{{Slopify_Release_Date}} can't be used in an ${category} that says these exact words: those are narrated as written, and the release date is only filled in when a step asks the AI model. In Prompts → Intros and outros, set this ${category}'s What the narrator reads to Let the AI write it from these instructions, or remove the keyword.`,
|
|
184
184
|
},
|
|
185
185
|
]
|
|
186
186
|
: [];
|
|
@@ -38,7 +38,7 @@ export const keywordSpecs = [
|
|
|
38
38
|
{
|
|
39
39
|
name: "Slopify_Series",
|
|
40
40
|
filler: "facts",
|
|
41
|
-
fills: "The channel's series brief from
|
|
41
|
+
fills: "The channel's series brief, from the channel's page under Name and look. A line with an empty brief is left out.",
|
|
42
42
|
example: "Calm histories of how things were made, one invention at a time.",
|
|
43
43
|
available: atStart,
|
|
44
44
|
places: everywhere,
|
|
@@ -88,7 +88,7 @@ export const keywordSpecs = [
|
|
|
88
88
|
{
|
|
89
89
|
name: "Slopify_Release_Date",
|
|
90
90
|
filler: "release",
|
|
91
|
-
fills: "The release date from the posting plan. A line with no date is left out; moving the date later
|
|
91
|
+
fills: "The release date from the posting plan. A line with no date is left out; moving the date later makes nothing again.",
|
|
92
92
|
example: "Thursday 22 October 2026",
|
|
93
93
|
available: "When the step runs.",
|
|
94
94
|
places: everywhere,
|
|
@@ -138,8 +138,8 @@ export const keywordSpecs = [
|
|
|
138
138
|
{
|
|
139
139
|
name: "Slopify_Brand",
|
|
140
140
|
filler: "facts",
|
|
141
|
-
fills: "The brand kit's caption and title
|
|
142
|
-
example: "
|
|
141
|
+
fills: "The brand kit's caption and title colors and font names. A line with an empty kit is left out.",
|
|
142
|
+
example: "Colors: #f5c542, #101010. Fonts: Barlow Condensed",
|
|
143
143
|
available: atStart,
|
|
144
144
|
places: pictures,
|
|
145
145
|
cost: "free",
|
|
@@ -229,7 +229,7 @@ export const keywordSpecs = [
|
|
|
229
229
|
{
|
|
230
230
|
name: "Slopify_Mood",
|
|
231
231
|
filler: "scenes",
|
|
232
|
-
fills: "The feeling of this picture's moment, for its light and
|
|
232
|
+
fills: "The feeling of this picture's moment, for its light and color.",
|
|
233
233
|
example: "tense",
|
|
234
234
|
available: sceneAvailable,
|
|
235
235
|
places: pictures,
|
|
@@ -37,7 +37,7 @@ function brandText(brand) {
|
|
|
37
37
|
if (brand === undefined)
|
|
38
38
|
return "";
|
|
39
39
|
const parts = [
|
|
40
|
-
...(brand.colours.length === 0 ? [] : [`
|
|
40
|
+
...(brand.colours.length === 0 ? [] : [`Colors: ${brand.colours.join(", ")}.`]),
|
|
41
41
|
...(brand.fonts.length === 0 ? [] : [`Fonts: ${brand.fonts.join(", ")}.`]),
|
|
42
42
|
];
|
|
43
43
|
return parts.join(" ");
|
|
@@ -17,7 +17,7 @@ const itemsMax = 10_000;
|
|
|
17
17
|
const sectionWords = {
|
|
18
18
|
prompts: "prompts",
|
|
19
19
|
entries: "intros or outros",
|
|
20
|
-
documentThemes: "PDF
|
|
20
|
+
documentThemes: "PDF looks",
|
|
21
21
|
aliases: "narration aliases",
|
|
22
22
|
};
|
|
23
23
|
// Only what the editor saves travels: ids, keywords and dates are the install's own.
|
|
@@ -93,7 +93,7 @@ export function readLibraryFile(text, section) {
|
|
|
93
93
|
if (!top.success)
|
|
94
94
|
return {
|
|
95
95
|
ok: false,
|
|
96
|
-
message: `it isn't a Slopify library file. Pick a .json file made by Export on
|
|
96
|
+
message: `it isn't a Slopify library file. Pick a .json file made by Export on Prompts, the PDF looks or Pronunciation.`,
|
|
97
97
|
};
|
|
98
98
|
if (top.data.version > libraryFileVersion)
|
|
99
99
|
return {
|
|
@@ -104,7 +104,7 @@ export function readLibraryFile(text, section) {
|
|
|
104
104
|
if (!Array.isArray(list) || list.length === 0)
|
|
105
105
|
return {
|
|
106
106
|
ok: false,
|
|
107
|
-
message: `it holds no ${what}. Import it on the
|
|
107
|
+
message: `it holds no ${what}. Import it on the page it was exported from.`,
|
|
108
108
|
};
|
|
109
109
|
if (list.length > itemsMax)
|
|
110
110
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { renameSync, rmSync } from "node:fs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
3
4
|
import { runFfmpeg } from "../video/ffmpeg.js";
|
|
4
5
|
// ceiling: below -70 LUFS (the filter's absolute gate) or under 0.4 s (one measuring block)
|
|
5
6
|
// there is nothing to measure: a piece that quiet or short is left as it is.
|
|
@@ -97,7 +98,7 @@ export async function measure(run, input, goal, chain = "") {
|
|
|
97
98
|
});
|
|
98
99
|
const measured = parseMeasured(stderr);
|
|
99
100
|
if (measured === undefined)
|
|
100
|
-
throw new Error(
|
|
101
|
+
throw new Error(`Slopify couldn't measure the loudness of the narration (ffmpeg printed no measurement). Try again; if it happens again, turn off Level the volume in ${editSettings.sound}, then use Download diagnostics in Settings and report it.`);
|
|
101
102
|
return measured;
|
|
102
103
|
}
|
|
103
104
|
export async function measureFile(run, path, goal) {
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
// The ambient bed and the shorts' music are mixed before the master and ducked by the levelled
|
|
10
10
|
// voice, so they keep their place under it.
|
|
11
11
|
//
|
|
12
|
-
// Browser-safe:
|
|
12
|
+
// Browser-safe: Create, Edit settings and Settings convert the same numbers.
|
|
13
13
|
//
|
|
14
14
|
// A run without the setting has no `loudness` at all, which is what every project saved before
|
|
15
15
|
// it was, and every recipe leaves it out of its fingerprint then.
|
|
16
|
+
import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
|
|
16
17
|
// YouTube and Spotify play everything back at about -14 LUFS, so a video mastered there is
|
|
17
18
|
// neither turned down nor left quiet. Audiobook shops ask for about -18 to -20 LUFS with more
|
|
18
19
|
// headroom, so the listening files sit lower.
|
|
@@ -87,7 +88,7 @@ export function loudnessProblem(lufs, target) {
|
|
|
87
88
|
db >= loudnessDbMin - 1e-9 &&
|
|
88
89
|
db <= loudnessDbMax + 1e-9
|
|
89
90
|
? undefined
|
|
90
|
-
: `The volume must be between ${String(loudnessDbMin)} dB and +${String(loudnessDbMax)} dB of the recommended level, in steps of ${String(loudnessDbStep)} dB. Change it under Level the volume (
|
|
91
|
+
: `The volume must be between ${String(loudnessDbMin)} dB and +${String(loudnessDbMax)} dB of the recommended level, in steps of ${String(loudnessDbStep)} dB. Change it under Level the volume (${createRows.video}, ${editSettings.sound}, or Settings → Production defaults).`;
|
|
91
92
|
}
|
|
92
93
|
export function loudnessFields(settings) {
|
|
93
94
|
if (settings === undefined)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { mkdirSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
4
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
4
5
|
import { measurePiece } from "../narration/pauses.js";
|
|
5
6
|
import { runFfmpeg } from "../video/ffmpeg.js";
|
|
6
7
|
import { sentenceLevel } from "./sentence-level-model.js";
|
|
@@ -243,7 +244,7 @@ export async function meterFile(run, file) {
|
|
|
243
244
|
else if (code === 0)
|
|
244
245
|
resolve();
|
|
245
246
|
else
|
|
246
|
-
reject(new Error(`Slopify couldn't measure a narration piece's sentences (ffmpeg said: ${errors.trim().split("\n").at(-1) ?? "nothing"}). Try again; if it happens again, turn off Level each sentence in
|
|
247
|
+
reject(new Error(`Slopify couldn't measure a narration piece's sentences (ffmpeg said: ${errors.trim().split("\n").at(-1) ?? "nothing"}). Try again; if it happens again, turn off Level each sentence in ${editSettings.sound}, then use Download diagnostics in Settings and report it.`));
|
|
247
248
|
});
|
|
248
249
|
});
|
|
249
250
|
return meter.readings();
|
|
@@ -110,3 +110,16 @@ export function dollarsText(dollars, unpriced = 0) {
|
|
|
110
110
|
const more = `${String(unpriced)} model${unpriced === 1 ? "" : "s"} with no listed price`;
|
|
111
111
|
return dollars <= 0 ? `${more} (price unknown)` : `${base}, plus ${more}`;
|
|
112
112
|
}
|
|
113
|
+
// The same estimate as the end of a sentence, "this costs …": "about $0.04 at list price",
|
|
114
|
+
// and for models with no listed price "an unknown amount: 3 models have no listed price".
|
|
115
|
+
export function costPhrase(dollars, unpriced = 0) {
|
|
116
|
+
const base = dollars <= 0
|
|
117
|
+
? "nothing"
|
|
118
|
+
: `${dollars < 0.01 ? "less than a cent" : `about $${dollars.toFixed(2)}`} at list price`;
|
|
119
|
+
if (unpriced === 0)
|
|
120
|
+
return base;
|
|
121
|
+
const models = `${String(unpriced)} model${unpriced === 1 ? "" : "s"}`;
|
|
122
|
+
return dollars <= 0
|
|
123
|
+
? `an unknown amount: ${models} ${unpriced === 1 ? "has" : "have"} no listed price`
|
|
124
|
+
: `${base}, plus an unknown amount for ${models} with no listed price`;
|
|
125
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export const usageSlots = ["llm", "audio", "images", "animate"];
|
|
4
4
|
export const usageKinds = ["template", "schedule", "draft", "project"];
|
|
5
5
|
export const slotLabels = {
|
|
6
|
-
llm: "
|
|
6
|
+
llm: "Writing model",
|
|
7
7
|
audio: "Voice model",
|
|
8
8
|
images: "Image model",
|
|
9
9
|
animate: "Animation model",
|
|
@@ -94,7 +94,7 @@ export async function switchRetiredModel(deps, catalogue, request) {
|
|
|
94
94
|
if (row.version !== row.head)
|
|
95
95
|
return {
|
|
96
96
|
ok: false,
|
|
97
|
-
message: "This schedule runs an older version of its template. In
|
|
97
|
+
message: "This schedule runs an older version of its template. In Calendar → Schedules, choose Edit, pick the template again so it runs the latest version, then save.",
|
|
98
98
|
};
|
|
99
99
|
return switchTemplate(deps, catalogue, request, String(row.templateId));
|
|
100
100
|
}
|
|
@@ -117,7 +117,7 @@ function switchTemplate(deps, catalogue, request, templateId) {
|
|
|
117
117
|
return {
|
|
118
118
|
ok: false,
|
|
119
119
|
message: error.reason === "missing-prompt"
|
|
120
|
-
? `The template "${template.name}" uses a prompt or intro/outro that was deleted, so it cannot be saved. Open it in
|
|
120
|
+
? `The template "${template.name}" uses a prompt or intro/outro that was deleted, so it cannot be saved. Open it in Templates → Edit template, choose another, and save.`
|
|
121
121
|
: changedMeanwhile,
|
|
122
122
|
};
|
|
123
123
|
}
|
|
@@ -230,7 +230,7 @@ async function switchProject(deps, catalogue, request) {
|
|
|
230
230
|
? " because the project changed meanwhile"
|
|
231
231
|
: "fields" in saved && saved.fields?.[0] !== undefined
|
|
232
232
|
? ` (${saved.fields[0].message})`
|
|
233
|
-
: ""}. Open the project
|
|
233
|
+
: ""}. Open the project and change it in Edit settings.`,
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
236
|
// "Switch all": every listed use that has a suggestion and no reason to hold back, one by one.
|