@gentbajko/slopify 3.25.8 → 4.0.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 +30 -0
- package/dist/adapters/alignment/cache.js +3 -5
- package/dist/adapters/alignment/ctc.js +1 -2
- package/dist/adapters/alignment/lock.js +4 -3
- package/dist/adapters/alignment/multilingual.js +0 -3
- package/dist/adapters/alignment/prefetch.js +2 -1
- package/dist/adapters/alignment/worker.js +2 -1
- package/dist/adapters/explain.js +0 -3
- package/dist/adapters/ffmpeg.js +19 -1
- package/dist/adapters/host-cli/index.js +19 -3
- package/dist/adapters/http-failure.js +53 -0
- package/dist/adapters/image/codex.js +36 -4
- package/dist/adapters/image/fal.js +17 -50
- package/dist/adapters/image/google.js +31 -55
- package/dist/adapters/image/openai.js +20 -47
- package/dist/adapters/image/reference.js +36 -0
- package/dist/adapters/image/replicate.js +18 -47
- package/dist/adapters/interest/search-interest.js +113 -0
- package/dist/adapters/key-probes.js +53 -2
- package/dist/adapters/llm/antigravity.js +4 -0
- package/dist/adapters/llm/assistant-cli.js +388 -0
- package/dist/adapters/llm/child-session.js +83 -0
- package/dist/adapters/llm/claude-code-models.js +51 -98
- package/dist/adapters/llm/claude-code.js +13 -2
- package/dist/adapters/llm/codex-limits.js +27 -68
- package/dist/adapters/llm/codex-server-models.js +61 -109
- package/dist/adapters/llm/codex.js +12 -1
- package/dist/adapters/llm/openrouter.js +10 -47
- package/dist/adapters/llm/run-cli.js +6 -22
- package/dist/adapters/llm/sse-lines.js +5 -1
- package/dist/adapters/model-access.js +37 -0
- package/dist/adapters/tts/cartesia.js +15 -46
- package/dist/adapters/tts/elevenlabs.js +20 -52
- package/dist/adapters/tts/gemini.js +19 -40
- package/dist/adapters/tts/inworld-async.js +4 -1
- package/dist/adapters/tts/inworld.js +3 -0
- package/dist/adapters/tts/openai.js +20 -50
- package/dist/assets/models-lifecycle.yaml +6 -0
- package/dist/assistant-wiring.js +60 -0
- package/dist/autopilot-timers.js +59 -0
- package/dist/catalog/lifecycle.js +52 -0
- package/dist/catalog/propose.js +204 -0
- package/dist/catalog/registry.js +3 -1
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +39 -0
- package/dist/edge/autostart/native.js +3 -5
- package/dist/edge/autostart/prompt.js +2 -1
- package/dist/edge/autostart/service.js +3 -4
- package/dist/edge/cli-args.js +3 -4
- package/dist/edge/cli.js +2 -1
- package/dist/edge/docker-install/apply.js +11 -11
- package/dist/edge/docker-install/run.js +2 -1
- package/dist/edge/docker-install/state.js +4 -3
- package/dist/edge/docker-install/tree.js +2 -4
- package/dist/edge/docker-install/volume.js +3 -2
- package/dist/edge/docker.js +5 -4
- package/dist/edge/events/hub.js +5 -5
- package/dist/edge/http/actions.js +4 -25
- package/dist/edge/http/actor.js +5 -3
- package/dist/edge/http/app.js +85 -10
- package/dist/edge/http/assistant.js +127 -0
- package/dist/edge/http/audio-preview.js +4 -11
- package/dist/edge/http/auditions.js +1 -1
- package/dist/edge/http/autostart.js +5 -5
- package/dist/edge/http/backups.js +1 -3
- package/dist/edge/http/channel-memory.js +7 -20
- package/dist/edge/http/channels.js +13 -26
- package/dist/edge/http/checkpoints.js +33 -17
- package/dist/edge/http/design-templates.js +167 -0
- package/dist/edge/http/designs.js +178 -0
- package/dist/edge/http/diagnostics.js +5 -1
- package/dist/edge/http/document-themes.js +2 -11
- package/dist/edge/http/draft-files.js +1 -3
- package/dist/edge/http/draft-problem.js +4 -9
- package/dist/edge/http/drafts.js +3 -3
- package/dist/edge/http/entries.js +6 -9
- package/dist/edge/http/extension-auth.js +27 -0
- package/dist/edge/http/external-reviews.js +12 -11
- package/dist/edge/http/files.js +5 -11
- package/dist/edge/http/folder-location.js +1 -4
- package/dist/edge/http/fonts.js +0 -7
- package/dist/edge/http/home.js +38 -13
- package/dist/edge/http/host-cli.js +18 -8
- package/dist/edge/http/ids.js +8 -0
- package/dist/edge/http/keywords.js +35 -0
- package/dist/edge/http/model-checks.js +132 -0
- package/dist/edge/http/narration-peaks.js +5 -13
- package/dist/edge/http/onboarding.js +1 -14
- package/dist/edge/http/open-folder.js +5 -10
- package/dist/edge/http/outputs.js +4 -12
- package/dist/edge/http/patch-notes.js +4 -10
- package/dist/edge/http/planning.js +2 -6
- package/dist/edge/http/problem.js +2 -4
- package/dist/edge/http/project-create.js +1 -0
- package/dist/edge/http/project-templates.js +29 -17
- package/dist/edge/http/projects.js +2 -12
- package/dist/edge/http/prompts.js +7 -14
- package/dist/edge/http/pronunciations.js +1 -2
- package/dist/edge/http/providers.js +39 -24
- package/dist/edge/http/refusal.js +29 -0
- package/dist/edge/http/remote-access.js +40 -0
- package/dist/edge/http/remote-guard.js +71 -0
- package/dist/edge/http/request-guard.js +91 -0
- package/dist/edge/http/reviews.js +11 -24
- package/dist/edge/http/revision-files.js +5 -10
- package/dist/edge/http/revisions.js +10 -15
- package/dist/edge/http/run-cost.js +1 -2
- package/dist/edge/http/same-page.js +80 -0
- package/dist/edge/http/schedules.js +59 -32
- package/dist/edge/http/settings.js +22 -13
- package/dist/edge/http/social.js +282 -0
- package/dist/edge/http/staging.js +2 -4
- package/dist/edge/http/storage-files.js +5 -7
- package/dist/edge/http/storage.js +23 -23
- package/dist/edge/http/studio.js +23 -50
- package/dist/edge/http/style-preview.js +1 -4
- package/dist/edge/http/subtitles.js +3 -17
- package/dist/edge/http/template-studio.js +433 -0
- package/dist/edge/http/trash.js +10 -11
- package/dist/edge/http/tutorial.js +1 -2
- package/dist/edge/http/tutorials.js +5 -11
- package/dist/edge/http/update.js +7 -13
- package/dist/edge/http/waveform.js +3 -6
- package/dist/edge/http/youtube-edits.js +2 -3
- package/dist/edge/mcp/assistant.js +279 -0
- package/dist/edge/mcp/attribution.js +8 -2
- package/dist/edge/mcp/call-route.js +11 -2
- package/dist/edge/mcp/excluded-routes.js +25 -0
- package/dist/edge/mcp/http.js +11 -2
- package/dist/edge/mcp/instructions.js +5 -3
- package/dist/edge/mcp/library-tools.js +1 -0
- package/dist/edge/mcp/route-tool.js +35 -15
- package/dist/edge/mcp/server.js +13 -8
- package/dist/edge/mcp/tools-channels.js +2 -0
- package/dist/edge/mcp/tools-designs.js +165 -0
- package/dist/edge/mcp/tools-library.js +14 -2
- package/dist/edge/mcp/tools-planning.js +23 -4
- package/dist/edge/mcp/tools-projects.js +28 -1
- package/dist/edge/mcp/tools-runs.js +14 -1
- package/dist/edge/mcp/tools-schedules.js +28 -4
- package/dist/edge/mcp/tools-setup.js +61 -3
- package/dist/edge/mcp/tools-social.js +46 -0
- package/dist/edge/mcp/tools-storage.js +8 -0
- package/dist/edge/mcp/tools-template-studio.js +166 -0
- package/dist/edge/mcp/tools.js +6 -0
- package/dist/edge/remote-access/cli.js +71 -0
- package/dist/edge/remote-access/model.js +32 -0
- package/dist/edge/remote-access/rules.js +207 -0
- package/dist/edge/remote-access/service.js +203 -0
- package/dist/edge/remote-access/store.js +44 -0
- package/dist/edge/remote-access/views.js +43 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/install.js +2 -2
- package/dist/host-cli/open-folder.js +2 -2
- package/dist/host-cli/paths.js +2 -4
- package/dist/host-cli/server.js +4 -4
- package/dist/host-cli/service.js +3 -3
- package/dist/host-cli/status.js +7 -7
- package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
- package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
- package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
- package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
- package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
- package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
- package/dist/kernel/db/migrations/0070-designs.sql +53 -0
- package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
- package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
- package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
- package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
- package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
- package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
- package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
- package/dist/kernel/db/tx.js +2 -4
- package/dist/kernel/errors.js +27 -1
- package/dist/kernel/json-answer.js +15 -0
- package/dist/kernel/lock.js +4 -9
- package/dist/kernel/paths.js +8 -0
- package/dist/kernel/ports/assistant-cli.js +4 -0
- package/dist/kernel/ports/host-cli.js +3 -0
- package/dist/kernel/ports/interest.js +4 -0
- package/dist/kernel/ports/languages.js +0 -3
- package/dist/kernel/ports/model.js +2 -0
- package/dist/kernel/runner/attempt.js +3 -1
- package/dist/kernel/runner/audition.js +3 -1
- package/dist/kernel/runner/checkpoint-authority.js +2 -1
- package/dist/kernel/runner/index.js +8 -10
- package/dist/kernel/runner/model-access.js +40 -0
- package/dist/kernel/runner/model-check.js +153 -0
- package/dist/kernel/runner/providers.js +15 -10
- package/dist/kernel/runner/standalone.js +9 -5
- package/dist/main.js +334 -19
- package/dist/model-checks.js +166 -0
- package/dist/patch-notes/3.25.9.md +5 -0
- package/dist/patch-notes/4.0.0.md +51 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/admission/heads.js +2 -1
- package/dist/slices/admission/repo.js +2 -1
- package/dist/slices/admission/rules.js +43 -8
- package/dist/slices/admission/schema.js +8 -0
- package/dist/slices/admission/stage-writes.js +0 -3
- package/dist/slices/admission/start.js +48 -31
- package/dist/slices/admission/substitute.js +7 -4
- package/dist/slices/agents/attention-posts.js +31 -0
- package/dist/slices/agents/attention-templates.js +33 -0
- package/dist/slices/agents/attention.js +10 -1
- package/dist/slices/agents/connection.js +2 -6
- package/dist/slices/agents/model.js +6 -3
- package/dist/slices/agents/repo.js +15 -7
- package/dist/slices/article/continuation.js +0 -63
- package/dist/slices/article/plain.js +3 -2
- package/dist/slices/article/segments.js +2 -62
- package/dist/slices/assistant/access.js +1 -0
- package/dist/slices/assistant/asks.js +80 -0
- package/dist/slices/assistant/cost.js +54 -0
- package/dist/slices/assistant/gate.js +222 -0
- package/dist/slices/assistant/model.js +79 -0
- package/dist/slices/assistant/names.js +23 -0
- package/dist/slices/assistant/prompt.js +74 -0
- package/dist/slices/assistant/repo.js +74 -0
- package/dist/slices/assistant/tokens.js +18 -0
- package/dist/slices/assistant/turn.js +342 -0
- package/dist/slices/autopilot/buffer.js +76 -0
- package/dist/slices/autopilot/model.js +14 -0
- package/dist/slices/autopilot/notify.js +27 -0
- package/dist/slices/autopilot/repo.js +34 -0
- package/dist/slices/autopilot/signals.js +194 -0
- package/dist/slices/autopilot/warnings.js +85 -0
- package/dist/slices/autopilot/weekly.js +100 -0
- package/dist/slices/backups/files.js +2 -6
- package/dist/slices/backups/repo.js +3 -15
- package/dist/slices/backups/service.js +4 -3
- package/dist/slices/channels/cast-images.js +2 -1
- package/dist/slices/channels/model.js +0 -1
- package/dist/slices/channels/rebrand.js +0 -3
- package/dist/slices/channels/repo.js +4 -0
- package/dist/slices/channels/schema.js +6 -0
- package/dist/slices/channels/service.js +10 -0
- package/dist/slices/channels/videos.js +2 -14
- package/dist/slices/checkpoints/index.js +0 -1
- package/dist/slices/control/providers.js +0 -26
- package/dist/slices/designs/artwork.js +66 -0
- package/dist/slices/designs/budget.js +23 -0
- package/dist/slices/designs/calls.js +124 -0
- package/dist/slices/designs/check-context.js +38 -0
- package/dist/slices/designs/check-text.js +43 -0
- package/dist/slices/designs/fields.js +88 -0
- package/dist/slices/designs/frames.js +34 -0
- package/dist/slices/designs/gate.js +128 -0
- package/dist/slices/designs/layout.js +293 -0
- package/dist/slices/designs/model.js +35 -0
- package/dist/slices/designs/pdf-job.js +23 -0
- package/dist/slices/designs/pdf-pages.js +30 -0
- package/dist/slices/designs/pdf-runner.js +68 -0
- package/dist/slices/designs/pdf-worker.js +24 -0
- package/dist/slices/designs/picture-size.js +79 -0
- package/dist/slices/designs/pictures.js +55 -0
- package/dist/slices/designs/quote.js +24 -0
- package/dist/slices/designs/render-args.js +36 -0
- package/dist/slices/designs/render-input.js +59 -0
- package/dist/slices/designs/render.js +215 -0
- package/dist/slices/designs/repo.js +101 -0
- package/dist/slices/designs/requests.js +80 -0
- package/dist/slices/designs/runner.js +239 -0
- package/dist/slices/designs/sample-repo.js +91 -0
- package/dist/slices/designs/sample.js +222 -0
- package/dist/slices/designs/schema.js +87 -0
- package/dist/slices/designs/sections.js +44 -0
- package/dist/slices/designs/service.js +233 -0
- package/dist/slices/designs/steps-repo.js +102 -0
- package/dist/slices/designs/studio-kind.js +39 -0
- package/dist/slices/designs/template-checks.js +44 -0
- package/dist/slices/designs/template-make.js +118 -0
- package/dist/slices/designs/templates.js +218 -0
- package/dist/slices/document/fonts.js +2 -1
- package/dist/slices/episodes/summarize.js +5 -1
- package/dist/slices/estimate/index.js +45 -7
- package/dist/slices/fonts/catalog.js +3 -2
- package/dist/slices/fonts/discovery.js +3 -2
- package/dist/slices/fonts/files.js +2 -6
- package/dist/slices/fonts/index.js +0 -1
- package/dist/slices/fonts/sfnt.js +45 -10
- package/dist/slices/images/appearance.js +6 -15
- package/dist/slices/images/scenes.js +100 -27
- package/dist/slices/keywords/digest.js +84 -0
- package/dist/slices/keywords/facts.js +92 -0
- package/dist/slices/keywords/fill.js +80 -0
- package/dist/slices/keywords/model.js +75 -0
- package/dist/slices/keywords/rules.js +187 -0
- package/dist/slices/keywords/schema.js +28 -0
- package/dist/slices/keywords/specs.js +260 -0
- package/dist/slices/keywords/values.js +74 -0
- package/dist/slices/library/lint.js +29 -12
- package/dist/slices/library/model.js +2 -0
- package/dist/slices/library/photorealistic.js +4 -12
- package/dist/slices/library/rename.js +9 -7
- package/dist/slices/library/save.js +14 -10
- package/dist/slices/library/slots.js +37 -11
- package/dist/slices/library/transfer.js +2 -1
- package/dist/slices/library/used-by.js +25 -10
- package/dist/slices/loudness/loudnorm.js +51 -11
- package/dist/slices/model-checks/cost.js +61 -0
- package/dist/slices/model-checks/listing.js +124 -0
- package/dist/slices/model-checks/model.js +20 -0
- package/dist/slices/model-checks/record.js +32 -0
- package/dist/slices/model-checks/repo.js +120 -0
- package/dist/slices/model-checks/rules.js +112 -0
- package/dist/slices/model-checks/run.js +143 -0
- package/dist/slices/model-checks/service.js +198 -0
- package/dist/slices/model-upkeep/switch.js +70 -25
- package/dist/slices/model-upkeep/usage.js +26 -11
- package/dist/slices/narration/breaths.js +2 -1
- package/dist/slices/narration/clean-pieces.js +2 -1
- package/dist/slices/narration/concat.js +2 -1
- package/dist/slices/narration/peaks.js +3 -4
- package/dist/slices/narration/pronunciation-chunks.js +4 -3
- package/dist/slices/narration/pronunciation.js +3 -2
- package/dist/slices/narration/steering.js +4 -3
- package/dist/slices/notifications/notifier.js +4 -3
- package/dist/slices/notifications/rules.js +5 -2
- package/dist/slices/notifications/settings.js +4 -12
- package/dist/slices/notifications/subject.js +2 -1
- package/dist/slices/onboarding/copy.js +2 -1
- package/dist/slices/onboarding/first-run.js +3 -2
- package/dist/slices/onboarding/full-video.js +3 -2
- package/dist/slices/onboarding/install.js +4 -3
- package/dist/slices/onboarding/model.js +0 -16
- package/dist/slices/onboarding/packs.js +22 -22
- package/dist/slices/onboarding/quick-short.js +2 -1
- package/dist/slices/onboarding/sample.js +0 -1
- package/dist/slices/onboarding/state.js +2 -11
- package/dist/slices/onboarding/template.js +2 -1
- package/dist/slices/patch-notes/library.js +3 -2
- package/dist/slices/play-drafts/convert.js +4 -0
- package/dist/slices/play-drafts/review-inputs.js +8 -2
- package/dist/slices/play-drafts/review.js +5 -4
- package/dist/slices/play-drafts/schema.js +4 -0
- package/dist/slices/project-templates/from-project.js +9 -2
- package/dist/slices/project-templates/repo.js +6 -1
- package/dist/slices/project-templates/setup.js +8 -0
- package/dist/slices/project-templates/stand-ins.js +22 -0
- package/dist/slices/rebuild/activity.js +2 -0
- package/dist/slices/rebuild/admission-repo.js +3 -2
- package/dist/slices/rebuild/narration-history.js +4 -5
- package/dist/slices/rebuild/narration-retry.js +2 -1
- package/dist/slices/rebuild/preview-details.js +27 -9
- package/dist/slices/rebuild/preview-plan.js +3 -8
- package/dist/slices/rebuild/recipe-build.js +8 -3
- package/dist/slices/rebuild/recipe-describe.js +3 -3
- package/dist/slices/rebuild/recipe-digest.js +58 -0
- package/dist/slices/rebuild/recipe-input-schema.js +10 -0
- package/dist/slices/rebuild/recipe-keywords.js +71 -0
- package/dist/slices/rebuild/recipe-legacy.js +5 -1
- package/dist/slices/rebuild/recipe-model.js +4 -0
- package/dist/slices/rebuild/recipe-narration-text.js +2 -1
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-reference.js +8 -4
- package/dist/slices/rebuild/recipe-reviews.js +27 -7
- package/dist/slices/rebuild/recipe-save.js +6 -3
- package/dist/slices/rebuild/recipe-scenes.js +40 -11
- package/dist/slices/rebuild/recipe-shorts.js +8 -8
- package/dist/slices/rebuild/recipe-social.js +46 -0
- package/dist/slices/rebuild/recipe-text.js +64 -20
- package/dist/slices/rebuild/recipe-validation.js +98 -28
- package/dist/slices/rebuild/recipe-visual.js +55 -16
- package/dist/slices/rebuild/recipe-work.js +15 -3
- package/dist/slices/rebuild/recipe-youtube.js +4 -3
- package/dist/slices/rebuild/recovery-selection.js +3 -1
- package/dist/slices/rebuild/recovery.js +3 -2
- package/dist/slices/rebuild/repo.js +4 -3
- package/dist/slices/rebuild/review-promise.js +25 -0
- package/dist/slices/rebuild/review-redo.js +2 -1
- package/dist/slices/rebuild/runtime-admission.js +4 -3
- package/dist/slices/rebuild/runtime-agent-review.js +11 -5
- package/dist/slices/rebuild/runtime-animate.js +9 -8
- package/dist/slices/rebuild/runtime-article.js +3 -2
- package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
- package/dist/slices/rebuild/runtime-document.js +8 -11
- package/dist/slices/rebuild/runtime-export-bed.js +6 -11
- package/dist/slices/rebuild/runtime-export-edit.js +6 -15
- package/dist/slices/rebuild/runtime-export-inputs.js +7 -7
- package/dist/slices/rebuild/runtime-export-short.js +8 -4
- package/dist/slices/rebuild/runtime-export.js +34 -12
- package/dist/slices/rebuild/runtime-figure-card.js +2 -1
- package/dist/slices/rebuild/runtime-image.js +4 -9
- package/dist/slices/rebuild/runtime-instructions.js +2 -0
- package/dist/slices/rebuild/runtime-keywords.js +59 -0
- package/dist/slices/rebuild/runtime-lines.js +2 -11
- package/dist/slices/rebuild/runtime-local.js +14 -20
- package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
- package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
- package/dist/slices/rebuild/runtime-narration-text.js +10 -15
- package/dist/slices/rebuild/runtime-pictures.js +2 -4
- package/dist/slices/rebuild/runtime-piece-label.js +10 -6
- package/dist/slices/rebuild/runtime-plan.js +31 -5
- package/dist/slices/rebuild/runtime-provider.js +60 -19
- package/dist/slices/rebuild/runtime-publication.js +10 -9
- package/dist/slices/rebuild/runtime-review.js +33 -28
- package/dist/slices/rebuild/runtime-run.js +4 -1
- package/dist/slices/rebuild/runtime-shorts.js +34 -50
- package/dist/slices/rebuild/runtime-store.js +3 -8
- package/dist/slices/rebuild/runtime-subtitles.js +16 -14
- package/dist/slices/rebuild/runtime-voices.js +8 -15
- package/dist/slices/rebuild/runtime-youtube.js +11 -29
- package/dist/slices/rebuild/transition-repo.js +6 -5
- package/dist/slices/rebuild/work-records.js +6 -0
- package/dist/slices/reruns/cascade.js +1 -2
- package/dist/slices/reruns/index.js +6 -9
- package/dist/slices/reviews/external-model.js +3 -0
- package/dist/slices/reviews/external-repo.js +2 -1
- package/dist/slices/reviews/external-rows.js +2 -3
- package/dist/slices/reviews/model.js +26 -8
- package/dist/slices/reviews/repo.js +2 -1
- package/dist/slices/reviews/rules.js +1 -0
- package/dist/slices/reviews/view.js +5 -2
- package/dist/slices/revisions/downloads.js +4 -5
- package/dist/slices/revisions/mutation-assets.js +7 -4
- package/dist/slices/revisions/mutation-cues.js +2 -5
- package/dist/slices/revisions/mutations.js +0 -1
- package/dist/slices/revisions/repo.js +3 -1
- package/dist/slices/revisions/schema.js +1 -0
- package/dist/slices/revisions/view.js +20 -0
- package/dist/slices/run-cost/limits.js +5 -2
- package/dist/slices/run-cost/meter.js +12 -5
- package/dist/slices/run-cost/panel.js +8 -74
- package/dist/slices/run-cost/plan-readings.js +127 -0
- package/dist/slices/run-cost/plan-use.js +169 -0
- package/dist/slices/run-cost/pricing.js +3 -3
- package/dist/slices/run-cost/week.js +32 -4
- package/dist/slices/schedules/agenda.js +11 -2
- package/dist/slices/schedules/by-date.js +42 -0
- package/dist/slices/schedules/calendar.js +5 -0
- package/dist/slices/schedules/design-agenda.js +36 -0
- package/dist/slices/schedules/design-check.js +41 -0
- package/dist/slices/schedules/design-run.js +126 -0
- package/dist/slices/schedules/next-run.js +62 -0
- package/dist/slices/schedules/prepare.js +18 -2
- package/dist/slices/schedules/repo.js +24 -16
- package/dist/slices/schedules/rescore.js +35 -0
- package/dist/slices/schedules/scheduler.js +13 -3
- package/dist/slices/schedules/schema.js +54 -3
- package/dist/slices/schedules/score.js +107 -0
- package/dist/slices/schedules/service.js +57 -24
- package/dist/slices/schedules/topic-list.js +15 -0
- package/dist/slices/schedules/topic-scores.js +104 -0
- package/dist/slices/schedules/topics.js +48 -36
- package/dist/slices/settings/cli-paths.js +4 -6
- package/dist/slices/settings/first-run.js +2 -11
- package/dist/slices/settings/model-reach.js +3 -17
- package/dist/slices/settings/model.js +10 -0
- package/dist/slices/settings/playback.js +33 -22
- package/dist/slices/settings/probe-read.js +16 -0
- package/dist/slices/settings/repo.js +33 -0
- package/dist/slices/settings/tutorial.js +2 -2
- package/dist/slices/settings/voices.js +4 -12
- package/dist/slices/shorts/pick.js +2 -15
- package/dist/slices/shorts/prompts.js +2 -13
- package/dist/slices/shorts/render.js +3 -0
- package/dist/slices/social/channel.js +44 -0
- package/dist/slices/social/defaults.js +17 -0
- package/dist/slices/social/due.js +36 -0
- package/dist/slices/social/extension.js +18 -0
- package/dist/slices/social/fit.js +115 -0
- package/dist/slices/social/limits.js +186 -0
- package/dist/slices/social/links.js +21 -0
- package/dist/slices/social/media.js +131 -0
- package/dist/slices/social/model.js +75 -0
- package/dist/slices/social/notice.js +19 -0
- package/dist/slices/social/pack.js +176 -0
- package/dist/slices/social/post-prompt.js +55 -0
- package/dist/slices/social/queue.js +31 -0
- package/dist/slices/social/repo.js +133 -0
- package/dist/slices/social/reshape.js +97 -0
- package/dist/slices/social/rules.js +5 -0
- package/dist/slices/social/schema.js +70 -0
- package/dist/slices/social/serve.js +51 -0
- package/dist/slices/social/service.js +128 -0
- package/dist/slices/social/settings.js +21 -0
- package/dist/slices/social/sources.js +114 -0
- package/dist/slices/social/state.js +110 -0
- package/dist/slices/social/text.js +149 -0
- package/dist/slices/storage/arrange.js +3 -1
- package/dist/slices/storage/assets.js +8 -10
- package/dist/slices/storage/backup-designs.js +74 -0
- package/dist/slices/storage/backup-export.js +8 -3
- package/dist/slices/storage/backup-format.js +10 -0
- package/dist/slices/storage/backup-import.js +20 -2
- package/dist/slices/storage/delete-project.js +6 -5
- package/dist/slices/storage/files-location.js +6 -8
- package/dist/slices/storage/layout.js +13 -2
- package/dist/slices/storage/model.js +3 -0
- package/dist/slices/storage/portable.js +16 -11
- package/dist/slices/storage/schema.js +2 -0
- package/dist/slices/storage/staging-refs.js +2 -1
- package/dist/slices/storage/staging.js +6 -9
- package/dist/slices/storage/trim.js +10 -3
- package/dist/slices/studio/calendar.js +4 -1
- package/dist/slices/studio/pick.js +2 -11
- package/dist/slices/studio/plan.js +6 -15
- package/dist/slices/studio/queue.js +3 -14
- package/dist/slices/studio/releases.js +26 -0
- package/dist/slices/studio/settings.js +14 -51
- package/dist/slices/studio/videos.js +12 -0
- package/dist/slices/style-preview/render.js +34 -13
- package/dist/slices/style-preview/schema.js +13 -0
- package/dist/slices/style-preview/service.js +2 -1
- package/dist/slices/style-preview/settings.js +33 -5
- package/dist/slices/telemetry/collector-client.js +2 -4
- package/dist/slices/telemetry/flush.js +2 -4
- package/dist/slices/telemetry/model.js +0 -7
- package/dist/slices/telemetry/record.js +10 -3
- package/dist/slices/template-studio/anatomy-view.js +118 -0
- package/dist/slices/template-studio/anatomy.js +73 -0
- package/dist/slices/template-studio/bakeoff-make.js +225 -0
- package/dist/slices/template-studio/bakeoff-repo.js +83 -0
- package/dist/slices/template-studio/bakeoff.js +333 -0
- package/dist/slices/template-studio/budget.js +103 -0
- package/dist/slices/template-studio/checks.js +273 -0
- package/dist/slices/template-studio/document-sample.js +56 -0
- package/dist/slices/template-studio/edit.js +15 -0
- package/dist/slices/template-studio/for-all.js +161 -0
- package/dist/slices/template-studio/free-video.js +31 -0
- package/dist/slices/template-studio/free.js +176 -0
- package/dist/slices/template-studio/gate.js +40 -0
- package/dist/slices/template-studio/hidden.js +20 -0
- package/dist/slices/template-studio/jobs.js +79 -0
- package/dist/slices/template-studio/loudness.js +111 -0
- package/dist/slices/template-studio/make.js +40 -0
- package/dist/slices/template-studio/model.js +54 -0
- package/dist/slices/template-studio/pack-import.js +253 -0
- package/dist/slices/template-studio/pack-samples.js +60 -0
- package/dist/slices/template-studio/pack.js +205 -0
- package/dist/slices/template-studio/paid-view.js +66 -0
- package/dist/slices/template-studio/paid.js +348 -0
- package/dist/slices/template-studio/quote.js +148 -0
- package/dist/slices/template-studio/ready.js +104 -0
- package/dist/slices/template-studio/repo.js +134 -0
- package/dist/slices/template-studio/reviews-path.js +58 -0
- package/dist/slices/template-studio/runner.js +84 -0
- package/dist/slices/template-studio/save.js +236 -0
- package/dist/slices/template-studio/say-it-as.js +248 -0
- package/dist/slices/template-studio/schedule-gate.js +34 -0
- package/dist/slices/template-studio/schedule-price.js +30 -0
- package/dist/slices/template-studio/schedule-sample.js +87 -0
- package/dist/slices/template-studio/schema.js +218 -0
- package/dist/slices/template-studio/scope.js +124 -0
- package/dist/slices/template-studio/sections.js +176 -0
- package/dist/slices/template-studio/service.js +173 -0
- package/dist/slices/template-studio/status.js +174 -0
- package/dist/slices/template-studio/topic.js +223 -0
- package/dist/slices/trash/designs.js +76 -0
- package/dist/slices/trash/model.js +13 -4
- package/dist/slices/trash/service.js +52 -2
- package/dist/slices/tutorials/library.js +3 -2
- package/dist/slices/video/ass-text.js +17 -0
- package/dist/slices/video/audio-export.js +2 -64
- package/dist/slices/video/encoding.js +160 -0
- package/dist/slices/video/ffmpeg.js +23 -21
- package/dist/slices/video/figure-card.js +1 -15
- package/dist/slices/video/slideshow.js +43 -18
- package/dist/slices/voices/palette.js +0 -3
- package/dist/slices/youtube/answer.js +2 -15
- package/dist/slices/youtube/edits-repo.js +2 -11
- package/dist/tutorials/Agents-MCP.md +2 -1
- package/dist/tutorials/Assistant.md +42 -0
- package/dist/tutorials/Channels.md +2 -0
- package/dist/tutorials/Home-and-Projects.md +19 -0
- package/dist/tutorials/Install.md +1 -0
- package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
- package/dist/tutorials/Models.md +33 -1
- package/dist/tutorials/Notifications.md +6 -0
- package/dist/tutorials/Play-Images.md +4 -0
- package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
- package/dist/tutorials/Prompts.md +10 -6
- package/dist/tutorials/Remote-Access.md +63 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +13 -2
- package/dist/tutorials/Schedules.md +21 -1
- package/dist/tutorials/Settings-Reference.md +50 -8
- package/dist/tutorials/Template-Studio.md +125 -0
- package/dist/tutorials/Templates.md +3 -0
- package/dist/tutorials/_Sidebar.md +5 -0
- package/dist/updater/work-in-progress.js +42 -12
- package/dist/web/assets/index-CkAI_2E7.css +1 -0
- package/dist/web/assets/index-DcD0cdfo.js +334 -0
- package/dist/web/assets/{pdf-T7MCwjIv.js → pdf-TODRwF31.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -1
- package/dist/slices/article/provided-entries.js +0 -66
- package/dist/slices/article/run.js +0 -145
- package/dist/slices/images/run.js +0 -234
- package/dist/slices/narration/legacy-plan.js +0 -58
- package/dist/slices/narration/run.js +0 -270
- package/dist/slices/rebuild/runtime-actions.js +0 -90
- package/dist/slices/research/run.js +0 -224
- package/dist/slices/subtitles/prepare.js +0 -194
- package/dist/slices/subtitles/transcript.js +0 -34
- package/dist/slices/thumbnail/run.js +0 -219
- package/dist/slices/video/audio-inputs.js +0 -33
- package/dist/slices/video/reuse-audio.js +0 -62
- package/dist/slices/video/run.js +0 -78
- package/dist/slices/video/write-export.js +0 -158
- package/dist/slices/video/write-subtitles.js +0 -59
- package/dist/web/assets/index-BadsoORk.css +0 -1
- package/dist/web/assets/index-D8LqNsCl.js +0 -326
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { render } from "../admission/substitute.js";
|
|
3
|
+
import { fontAdvances, mappedCodepoints } from "../fonts/sfnt.js";
|
|
4
|
+
import { designFields } from "./fields.js";
|
|
5
|
+
import { frames } from "./frames.js";
|
|
6
|
+
import { layoutText } from "./layout.js";
|
|
7
|
+
import { formatLabels } from "./model.js";
|
|
8
|
+
import { sampleRow } from "./sample-repo.js";
|
|
9
|
+
import { templateRevision } from "./templates.js";
|
|
10
|
+
export function runnableDesignVersion(db, templateId) {
|
|
11
|
+
const row = db
|
|
12
|
+
.prepare(`SELECT r.version FROM design_template_revisions AS r
|
|
13
|
+
JOIN design_templates AS t ON t.id = r.template_id
|
|
14
|
+
WHERE r.template_id=? AND r.gate='ready' AND t.deleted_at IS NULL
|
|
15
|
+
ORDER BY r.version DESC LIMIT 1`)
|
|
16
|
+
.get(templateId);
|
|
17
|
+
if (row === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
const version = z.object({ version: z.number() }).parse(row).version;
|
|
20
|
+
const revision = templateRevision(db, templateId, version);
|
|
21
|
+
return revision === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: { version, name: revision.name, document: revision.document };
|
|
24
|
+
}
|
|
25
|
+
export async function contextChecks(doc, templateId, ctx) {
|
|
26
|
+
const at = (section, label = "Open") => ({
|
|
27
|
+
label,
|
|
28
|
+
href: `/library/design-templates/${templateId}#${section}`,
|
|
29
|
+
});
|
|
30
|
+
const checks = [];
|
|
31
|
+
if (doc.picture.source === "generate") {
|
|
32
|
+
const problem = await ctx.providerProblem(doc.picture.provider);
|
|
33
|
+
checks.push({
|
|
34
|
+
id: "provider",
|
|
35
|
+
ok: problem === null,
|
|
36
|
+
sentence: problem ?? "The image provider is ready.",
|
|
37
|
+
fix: problem === null ? null : { label: "Open Providers", href: "/settings?section=providers" },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const kept = doc.picture.sha256 !== undefined;
|
|
42
|
+
checks.push({
|
|
43
|
+
id: "provider",
|
|
44
|
+
ok: kept,
|
|
45
|
+
sentence: kept
|
|
46
|
+
? "The template's own picture is saved with it."
|
|
47
|
+
: "This template uses its own picture, but none is saved with it. Upload one under Picture.",
|
|
48
|
+
fix: kept ? null : at("picture"),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const schedule = doc.schedule;
|
|
52
|
+
if (schedule !== undefined) {
|
|
53
|
+
const used = designFields(doc).includes(schedule.dateField);
|
|
54
|
+
checks.push({
|
|
55
|
+
id: "date-field",
|
|
56
|
+
ok: used,
|
|
57
|
+
sentence: used
|
|
58
|
+
? `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 field. Choose a field it uses under Schedule.`,
|
|
60
|
+
fix: used ? null : at("schedule"),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// With the AI drawing the text, Slopify draws none, so there are no fonts to check.
|
|
64
|
+
if (doc.picture.aiText)
|
|
65
|
+
return checks;
|
|
66
|
+
const fonts = new Map();
|
|
67
|
+
for (const block of doc.text.blocks)
|
|
68
|
+
if (!fonts.has(block.fontId))
|
|
69
|
+
fonts.set(block.fontId, await ctx.font(block.fontId));
|
|
70
|
+
const fontProblems = [];
|
|
71
|
+
for (const [index, block] of doc.text.blocks.entries()) {
|
|
72
|
+
const filled = render(block.text, doc.samples);
|
|
73
|
+
if (filled.trim() === "")
|
|
74
|
+
continue;
|
|
75
|
+
const where = `text block ${String(index + 1)}`;
|
|
76
|
+
const font = fonts.get(block.fontId);
|
|
77
|
+
if (font === undefined) {
|
|
78
|
+
fontProblems.push(`The font chosen for ${where} is no longer on this computer. Choose another font for it under Text layout.`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const letters = [...new Set([...filled].filter((ch) => ch.trim() !== ""))];
|
|
82
|
+
const found = mappedCodepoints(font.bytes, font.faceIndex, letters.map((ch) => ch.codePointAt(0) ?? 0));
|
|
83
|
+
const missing = found === undefined ? [] : letters.filter((ch) => !found.has(ch.codePointAt(0) ?? 0));
|
|
84
|
+
if (missing.length > 0)
|
|
85
|
+
fontProblems.push(`The font ${font.name} has no ${missing.map((ch) => `“${ch}”`).join(" ")} for the sample of ${where}. Choose another font for it under Text layout, or upload one that has ${missing.length === 1 ? "it" : "them"}.`);
|
|
86
|
+
}
|
|
87
|
+
checks.push({
|
|
88
|
+
id: "fonts",
|
|
89
|
+
ok: fontProblems.length === 0,
|
|
90
|
+
sentence: fontProblems[0] ?? "The fonts have every letter of the sample values.",
|
|
91
|
+
fix: fontProblems.length === 0 ? null : at("text"),
|
|
92
|
+
});
|
|
93
|
+
const layoutFonts = {};
|
|
94
|
+
for (const [id, font] of fonts)
|
|
95
|
+
if (font !== undefined)
|
|
96
|
+
layoutFonts[id] = { assName: font.name, advance: fontAdvances(font.bytes, font.faceIndex) };
|
|
97
|
+
const tooFull = doc.formats
|
|
98
|
+
.map((entry) => entry.format)
|
|
99
|
+
.filter((format) => layoutText(frames[format], doc.text, doc.samples, layoutFonts).overflow);
|
|
100
|
+
checks.push({
|
|
101
|
+
id: "formats",
|
|
102
|
+
ok: tooFull.length === 0,
|
|
103
|
+
sentence: tooFull.length === 0
|
|
104
|
+
? "The sample values fit on every format."
|
|
105
|
+
: `The sample text doesn't fit on ${tooFull.map((format) => formatLabels[format]).join(", ")}, even at the smallest readable size. Shorten it or choose a placement with more room under Text layout.`,
|
|
106
|
+
fix: tooFull.length === 0 ? null : at("text"),
|
|
107
|
+
});
|
|
108
|
+
return checks;
|
|
109
|
+
}
|
|
110
|
+
// Mark ready asks for the version's own sample, made, as a project template's studio does: the
|
|
111
|
+
// person has seen what a schedule will make before it makes it.
|
|
112
|
+
export function sampleCheck(db, templateId, version) {
|
|
113
|
+
const row = sampleRow(db, templateId);
|
|
114
|
+
const made = row?.state === "done" && row.version === version;
|
|
115
|
+
const how = "Make its sample, look it over beside the settings, then press Mark ready.";
|
|
116
|
+
return {
|
|
117
|
+
id: "sample",
|
|
118
|
+
ok: made,
|
|
119
|
+
sentence: made
|
|
120
|
+
? `Version ${String(version)}'s sample is made.`
|
|
121
|
+
: row?.state === "done" && row.version !== version
|
|
122
|
+
? `Version ${String(version)} has no sample yet: the one shown is of version ${String(row.version)}. ${how}`
|
|
123
|
+
: `Version ${String(version)} has no sample yet. ${how}`,
|
|
124
|
+
fix: made
|
|
125
|
+
? null
|
|
126
|
+
: { label: "Open Picture", href: `/library/design-templates/${templateId}#picture` },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { render } from "../admission/substitute.js";
|
|
2
|
+
import { assColour, assText } from "../video/ass-text.js";
|
|
3
|
+
export const minTextShare = 0.024;
|
|
4
|
+
const roleShare = {
|
|
5
|
+
headline: 0.11,
|
|
6
|
+
subhead: 0.06,
|
|
7
|
+
details: 0.045,
|
|
8
|
+
small: 0.03,
|
|
9
|
+
};
|
|
10
|
+
const lineSpacing = 1.2;
|
|
11
|
+
const maxFilled = 10_000;
|
|
12
|
+
const blockGap = 0.45;
|
|
13
|
+
// Average advance per em: about half for mixed case, more for capitals.
|
|
14
|
+
export function advanceOf(text) {
|
|
15
|
+
const letters = [...text].filter((ch) => /\p{L}/u.test(ch));
|
|
16
|
+
const upper = letters.filter((ch) => ch !== ch.toLowerCase()).length;
|
|
17
|
+
return letters.length > 0 && upper / letters.length > 0.6 ? 0.68 : 0.56;
|
|
18
|
+
}
|
|
19
|
+
export function estimatedWidth(text, size) {
|
|
20
|
+
return [...text].length * size * advanceOf(text);
|
|
21
|
+
}
|
|
22
|
+
// What a line takes on the frame: the font's advances when known, plus room for the outline.
|
|
23
|
+
export function measuredWidth(text, size, font) {
|
|
24
|
+
const advance = font?.advance;
|
|
25
|
+
if (advance === undefined)
|
|
26
|
+
return estimatedWidth(text, size);
|
|
27
|
+
const guess = advanceOf(text);
|
|
28
|
+
let em = 0;
|
|
29
|
+
for (const ch of text)
|
|
30
|
+
em += advance(ch.codePointAt(0) ?? 0) ?? guess;
|
|
31
|
+
return em * size * 1.02 + size * 0.08;
|
|
32
|
+
}
|
|
33
|
+
export function safeArea(frame, preset) {
|
|
34
|
+
const { width, height } = frame;
|
|
35
|
+
const margin = Math.round(Math.min(width, height) * 0.06);
|
|
36
|
+
switch (preset) {
|
|
37
|
+
case "top-title":
|
|
38
|
+
return {
|
|
39
|
+
x: margin,
|
|
40
|
+
y: margin,
|
|
41
|
+
width: width - margin * 2,
|
|
42
|
+
height: Math.round(height * 0.45) - margin,
|
|
43
|
+
};
|
|
44
|
+
case "bottom-band": {
|
|
45
|
+
const top = Math.round(height * 0.62);
|
|
46
|
+
return { x: margin, y: top, width: width - margin * 2, height: height - top - margin };
|
|
47
|
+
}
|
|
48
|
+
case "centered":
|
|
49
|
+
return { x: margin, y: margin, width: width - margin * 2, height: height - margin * 2 };
|
|
50
|
+
case "left-column":
|
|
51
|
+
return {
|
|
52
|
+
x: margin,
|
|
53
|
+
y: margin,
|
|
54
|
+
width: Math.round(width * 0.5) - margin,
|
|
55
|
+
height: height - margin * 2,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// A word this short is never cut: it moves to its own line, and the text shrinks if even that
|
|
60
|
+
// is too wide.
|
|
61
|
+
const shortWord = 8;
|
|
62
|
+
const numeric = /\p{N}/u;
|
|
63
|
+
// A currency or code written before the number ("CHF 15", "€ 12"), and a unit after it
|
|
64
|
+
// ("20:00 Uhr", "15 €", "5 km").
|
|
65
|
+
const unitBefore = /^(?:\p{Sc}{1,3}|\p{Lu}{2,4}\.?)$/u;
|
|
66
|
+
const unitAfter = /^[\p{L}\p{Sc}%°]{1,4}[.,;:!?]?$/u;
|
|
67
|
+
// The words that must stay on one line: a number with the unit or currency beside it.
|
|
68
|
+
export function wrapUnits(text) {
|
|
69
|
+
const words = text.split(/\s+/).filter((part) => part !== "");
|
|
70
|
+
const units = [];
|
|
71
|
+
for (const word of words) {
|
|
72
|
+
const last = units.at(-1);
|
|
73
|
+
const previous = last?.at(-1);
|
|
74
|
+
const open = last !== undefined && last.length === 1 && previous !== undefined;
|
|
75
|
+
const joins = open &&
|
|
76
|
+
((numeric.test(previous) && !numeric.test(word) && unitAfter.test(word)) ||
|
|
77
|
+
(numeric.test(word) && !numeric.test(previous) && unitBefore.test(previous)));
|
|
78
|
+
if (joins)
|
|
79
|
+
last.push(word);
|
|
80
|
+
else
|
|
81
|
+
units.push([word]);
|
|
82
|
+
}
|
|
83
|
+
return units.map((unit) => unit.join(" "));
|
|
84
|
+
}
|
|
85
|
+
// Greedy wrap by width. A long word wider than the line is broken, so nothing leaves the area
|
|
86
|
+
// sideways; a short word or a number with its unit is kept whole (the caller shrinks the text),
|
|
87
|
+
// and only `split` (at the smallest size) lets a unit go across two lines.
|
|
88
|
+
export function wrapText(text, size, room, font, split = false) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
let line = "";
|
|
91
|
+
const fits = (candidate) => measuredWidth(candidate, size, font) <= room;
|
|
92
|
+
const pieces = wrapUnits(text).flatMap((unit) => split && !fits(unit) ? unit.split(" ") : [unit]);
|
|
93
|
+
for (const word of pieces) {
|
|
94
|
+
const candidate = line === "" ? word : `${line} ${word}`;
|
|
95
|
+
if (fits(candidate)) {
|
|
96
|
+
line = candidate;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (line !== "")
|
|
100
|
+
lines.push(line);
|
|
101
|
+
line = "";
|
|
102
|
+
if ([...word].length <= shortWord || word.includes(" ")) {
|
|
103
|
+
line = word;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const cuts = cutWord([...word], size, room, font);
|
|
107
|
+
lines.push(...cuts.slice(0, -1));
|
|
108
|
+
line = cuts.at(-1) ?? "";
|
|
109
|
+
}
|
|
110
|
+
if (line !== "")
|
|
111
|
+
lines.push(line);
|
|
112
|
+
return lines;
|
|
113
|
+
}
|
|
114
|
+
// A word too wide for the line, cut into pieces that each take the longest run of characters
|
|
115
|
+
// that fits (at least one). Each character's advance has a lowest and highest share (an
|
|
116
|
+
// unknown one is guessed between `advanceOf`'s two figures), so running sums rule most cut
|
|
117
|
+
// points in or out at once and only the few in between are measured exactly: the result is
|
|
118
|
+
// what measuring every cut point would give, without the time that takes on a long URL.
|
|
119
|
+
function cutWord(chars, size, room, font) {
|
|
120
|
+
const advance = font?.advance;
|
|
121
|
+
const low = [0];
|
|
122
|
+
const high = [0];
|
|
123
|
+
for (const ch of chars) {
|
|
124
|
+
const known = advance?.(ch.codePointAt(0) ?? 0);
|
|
125
|
+
if (known !== undefined && !(known >= 0))
|
|
126
|
+
return cutWordSlowly(chars, size, room, font);
|
|
127
|
+
low.push((low.at(-1) ?? 0) + (known ?? 0.56));
|
|
128
|
+
high.push((high.at(-1) ?? 0) + (known ?? 0.68));
|
|
129
|
+
}
|
|
130
|
+
const width = (em) => advance === undefined ? em * size : em * size * 1.02 + size * 0.08;
|
|
131
|
+
const slack = 1e-6 * (Math.abs(room) + size);
|
|
132
|
+
const lowAt = (from, to) => width((low[to] ?? 0) - (low[from] ?? 0));
|
|
133
|
+
const fitsRun = (from, to) => {
|
|
134
|
+
if (lowAt(from, to) > room + slack)
|
|
135
|
+
return false;
|
|
136
|
+
if (width((high[to] ?? 0) - (high[from] ?? 0)) < room - slack)
|
|
137
|
+
return true;
|
|
138
|
+
return measuredWidth(chars.slice(from, to).join(""), size, font) <= room;
|
|
139
|
+
};
|
|
140
|
+
const pieces = [];
|
|
141
|
+
let start = 0;
|
|
142
|
+
while (!fitsRun(start, chars.length)) {
|
|
143
|
+
const top = Math.max(1, chars.length - start - 1);
|
|
144
|
+
let below = 0;
|
|
145
|
+
let above = top + 1;
|
|
146
|
+
while (above - below > 1) {
|
|
147
|
+
const middle = Math.floor((below + above) / 2);
|
|
148
|
+
if (lowAt(start, start + middle) <= room + slack)
|
|
149
|
+
below = middle;
|
|
150
|
+
else
|
|
151
|
+
above = middle;
|
|
152
|
+
}
|
|
153
|
+
let cut = Math.max(1, below);
|
|
154
|
+
while (cut > 1 && !fitsRun(start, start + cut))
|
|
155
|
+
cut -= 1;
|
|
156
|
+
pieces.push(chars.slice(start, start + cut).join(""));
|
|
157
|
+
start += cut;
|
|
158
|
+
}
|
|
159
|
+
pieces.push(chars.slice(start).join(""));
|
|
160
|
+
return pieces;
|
|
161
|
+
}
|
|
162
|
+
function cutWordSlowly(chars, size, room, font) {
|
|
163
|
+
const fits = (candidate) => measuredWidth(candidate, size, font) <= room;
|
|
164
|
+
const pieces = [];
|
|
165
|
+
let rest = [...chars];
|
|
166
|
+
while (!fits(rest.join(""))) {
|
|
167
|
+
let cut = Math.max(1, rest.length - 1);
|
|
168
|
+
while (cut > 1 && !fits(rest.slice(0, cut).join("")))
|
|
169
|
+
cut -= 1;
|
|
170
|
+
pieces.push(rest.slice(0, cut).join(""));
|
|
171
|
+
rest = rest.slice(cut);
|
|
172
|
+
}
|
|
173
|
+
pieces.push(rest.join(""));
|
|
174
|
+
return pieces;
|
|
175
|
+
}
|
|
176
|
+
export function layoutText(frame, text, values, fonts) {
|
|
177
|
+
const short = Math.min(frame.width, frame.height);
|
|
178
|
+
const floor = Math.round(short * minTextShare);
|
|
179
|
+
const area = safeArea(frame, text.preset);
|
|
180
|
+
let cutShort = false;
|
|
181
|
+
const blocks = text.blocks
|
|
182
|
+
.map((block) => {
|
|
183
|
+
const filled = render(block.text, values).replace(/\s+/g, " ").trim();
|
|
184
|
+
// More than any format holds even at the smallest size: the rest is never laid out, and
|
|
185
|
+
// the piece says it doesn't fit.
|
|
186
|
+
const chars = filled.length > maxFilled ? [...filled] : [];
|
|
187
|
+
if (chars.length <= maxFilled)
|
|
188
|
+
return { ...block, filled };
|
|
189
|
+
cutShort = true;
|
|
190
|
+
return { ...block, filled: chars.slice(0, maxFilled).join("") };
|
|
191
|
+
})
|
|
192
|
+
.filter((block) => block.filled !== "");
|
|
193
|
+
const size = (role, scale) => Math.max(floor, Math.round(short * roleShare[role] * scale));
|
|
194
|
+
const sized = (scale, split = false) => blocks.map((block) => {
|
|
195
|
+
const at = size(block.role, scale);
|
|
196
|
+
return {
|
|
197
|
+
id: block.id,
|
|
198
|
+
lines: wrapText(block.filled, at, area.width, fonts[block.fontId], split),
|
|
199
|
+
size: at,
|
|
200
|
+
font: fonts[block.fontId],
|
|
201
|
+
fontName: fonts[block.fontId]?.assName ?? "Barlow",
|
|
202
|
+
color: block.color,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
const heightOf = (set) => set.reduce((sum, block, index) => sum +
|
|
206
|
+
block.lines.length * block.size * lineSpacing +
|
|
207
|
+
(index === 0 ? 0 : block.size * blockGap), 0);
|
|
208
|
+
const widest = (set) => Math.max(0, ...set.flatMap((block) => block.lines.map((line) => measuredWidth(line, block.size, block.font))));
|
|
209
|
+
const tooBig = (set) => heightOf(set) > area.height || widest(set) > area.width;
|
|
210
|
+
let scale = 1;
|
|
211
|
+
let set = sized(scale);
|
|
212
|
+
while (tooBig(set) && set.some((block) => block.size > floor)) {
|
|
213
|
+
scale *= 0.92;
|
|
214
|
+
set = sized(scale);
|
|
215
|
+
}
|
|
216
|
+
if (widest(set) > area.width)
|
|
217
|
+
set = sized(scale, true);
|
|
218
|
+
const total = heightOf(set);
|
|
219
|
+
const overflow = cutShort || total > area.height || widest(set) > area.width + 1;
|
|
220
|
+
const vertical = text.preset === "top-title" ? 0 : (area.height - Math.min(total, area.height)) / 2;
|
|
221
|
+
const left = text.preset === "left-column";
|
|
222
|
+
const lines = [];
|
|
223
|
+
let y = area.y + vertical;
|
|
224
|
+
for (const [index, block] of set.entries()) {
|
|
225
|
+
if (index > 0)
|
|
226
|
+
y += block.size * blockGap;
|
|
227
|
+
for (const line of block.lines) {
|
|
228
|
+
lines.push({
|
|
229
|
+
blockId: block.id,
|
|
230
|
+
text: line,
|
|
231
|
+
x: Math.round(left ? area.x : area.x + area.width / 2),
|
|
232
|
+
y: Math.round(y),
|
|
233
|
+
size: block.size,
|
|
234
|
+
width: Math.round(measuredWidth(line, block.size, block.font)),
|
|
235
|
+
});
|
|
236
|
+
y += block.size * lineSpacing;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const events = [
|
|
240
|
+
...scrimEvents(text, frame, area, lines),
|
|
241
|
+
...lines.map((line) => {
|
|
242
|
+
const block = set.find((one) => one.id === line.blockId);
|
|
243
|
+
const outline = text.scrim === "none" ? Math.max(1, Math.round(line.size * 0.035)) : 0;
|
|
244
|
+
return dialogue(1, `{\\an${left ? "7" : "8"}\\pos(${String(line.x)},${String(line.y)})\\fn${(block?.fontName ?? "Barlow").replace(/[\\{}]/g, "")}\\fs${String(line.size)}\\c${assColour(block?.color ?? "#FFFFFF")}\\bord${String(outline)}\\3c&H000000&\\shad0}${assText(line.text)}`);
|
|
245
|
+
}),
|
|
246
|
+
];
|
|
247
|
+
return { ass: script(frame, events), area, lines, overflow };
|
|
248
|
+
}
|
|
249
|
+
function scrimEvents({ scrim, preset }, frame, area, lines) {
|
|
250
|
+
if (scrim === "none" || lines.length === 0)
|
|
251
|
+
return [];
|
|
252
|
+
const first = lines[0];
|
|
253
|
+
const last = lines.at(-1);
|
|
254
|
+
if (first === undefined || last === undefined)
|
|
255
|
+
return [];
|
|
256
|
+
const pad = Math.round(Math.min(frame.width, frame.height) * 0.03);
|
|
257
|
+
// A band under a bottom or top title runs to that edge of the frame.
|
|
258
|
+
const band = scrim === "band";
|
|
259
|
+
const top = band && preset === "top-title" ? 0 : Math.max(0, first.y - pad);
|
|
260
|
+
const bottom = band && preset === "bottom-band"
|
|
261
|
+
? frame.height
|
|
262
|
+
: Math.min(frame.height, last.y + last.size * lineSpacing + pad);
|
|
263
|
+
// A band runs the frame's full width; a soft scrim sits behind the text area only.
|
|
264
|
+
const [x0, x1, alpha] = band
|
|
265
|
+
? [0, frame.width, "&H40&"]
|
|
266
|
+
: [Math.max(0, area.x - pad), Math.min(frame.width, area.x + area.width + pad), "&H80&"];
|
|
267
|
+
const corners = [x0, top, x1, bottom].map((n) => String(Math.round(n)));
|
|
268
|
+
const [a, b, c, d] = corners;
|
|
269
|
+
return [
|
|
270
|
+
dialogue(0, `{\\an7\\pos(0,0)\\p1\\bord0\\shad0\\c&H000000&\\1a${alpha}}m ${a ?? "0"} ${b ?? "0"} l ${c ?? "0"} ${b ?? "0"} l ${c ?? "0"} ${d ?? "0"} l ${a ?? "0"} ${d ?? "0"}{\\p0}`),
|
|
271
|
+
];
|
|
272
|
+
}
|
|
273
|
+
function dialogue(layer, body) {
|
|
274
|
+
return `Dialogue: ${String(layer)},0:00:00.00,0:00:10.00,Design,,0,0,0,,${body}`;
|
|
275
|
+
}
|
|
276
|
+
function script(frame, events) {
|
|
277
|
+
return `${[
|
|
278
|
+
"[Script Info]",
|
|
279
|
+
"ScriptType: v4.00+",
|
|
280
|
+
`PlayResX: ${String(frame.width)}`,
|
|
281
|
+
`PlayResY: ${String(frame.height)}`,
|
|
282
|
+
"WrapStyle: 2",
|
|
283
|
+
"ScaledBorderAndShadow: yes",
|
|
284
|
+
"",
|
|
285
|
+
"[V4+ Styles]",
|
|
286
|
+
"Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding",
|
|
287
|
+
`Style: Design,Barlow,${String(Math.round(Math.min(frame.width, frame.height) * 0.04))},&H00FFFFFF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1`,
|
|
288
|
+
"",
|
|
289
|
+
"[Events]",
|
|
290
|
+
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text",
|
|
291
|
+
...events,
|
|
292
|
+
].join("\n")}\n`;
|
|
293
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// A design is a picture edited step by step ("edit") or a designed piece whose text Slopify
|
|
2
|
+
// lays out on the AI's artwork ("design"). Every step is one version of the picture.
|
|
3
|
+
export const designKinds = ["edit", "design"];
|
|
4
|
+
export const stepKinds = ["upload", "edit", "generate"];
|
|
5
|
+
export const stepStates = ["making", "ready", "failed"];
|
|
6
|
+
export const designFormats = [
|
|
7
|
+
"a4",
|
|
8
|
+
"a3",
|
|
9
|
+
"flyer",
|
|
10
|
+
"square",
|
|
11
|
+
"portrait",
|
|
12
|
+
"story",
|
|
13
|
+
"wide",
|
|
14
|
+
"brochure",
|
|
15
|
+
];
|
|
16
|
+
export const formatLabels = {
|
|
17
|
+
a4: "Poster A4",
|
|
18
|
+
a3: "Poster A3",
|
|
19
|
+
flyer: "Flyer A5",
|
|
20
|
+
square: "Square 1:1",
|
|
21
|
+
portrait: "Portrait 4:5",
|
|
22
|
+
story: "Story 9:16",
|
|
23
|
+
wide: "Wide 16:9",
|
|
24
|
+
brochure: "Brochure A4",
|
|
25
|
+
};
|
|
26
|
+
export const layoutPresets = ["top-title", "bottom-band", "centered", "left-column"];
|
|
27
|
+
export const scrims = ["none", "soft", "band"];
|
|
28
|
+
export const blockRoles = ["headline", "subhead", "details", "small"];
|
|
29
|
+
export const designNameMax = 200;
|
|
30
|
+
export const instructionMax = 4000;
|
|
31
|
+
export const promptMax = 4000;
|
|
32
|
+
export const blocksMax = 8;
|
|
33
|
+
export const blockTextMax = 500;
|
|
34
|
+
export const castPerDesignMax = 3;
|
|
35
|
+
export const daysBeforeMax = 90;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { documentSettingsSchema } from "../document/theme-schema.js";
|
|
3
|
+
const bytes = z.custom((value) => value instanceof Uint8Array);
|
|
4
|
+
const millimetres = z.number().positive().max(2000);
|
|
5
|
+
export const pdfJobSchema = z.discriminatedUnion("kind", [
|
|
6
|
+
z
|
|
7
|
+
.object({ kind: z.literal("poster"), png: bytes, widthMm: millimetres, heightMm: millimetres })
|
|
8
|
+
.strict(),
|
|
9
|
+
z
|
|
10
|
+
.object({
|
|
11
|
+
kind: z.literal("brochure"),
|
|
12
|
+
cover: bytes,
|
|
13
|
+
title: z.string().min(1).max(200),
|
|
14
|
+
markdown: z.string().min(1).max(100_000),
|
|
15
|
+
settings: documentSettingsSchema,
|
|
16
|
+
writtenOn: z.string(),
|
|
17
|
+
})
|
|
18
|
+
.strict(),
|
|
19
|
+
]);
|
|
20
|
+
export const pdfWorkerMessage = z.discriminatedUnion("type", [
|
|
21
|
+
z.object({ type: z.literal("done"), bytes }).strict(),
|
|
22
|
+
z.object({ type: z.literal("error"), message: z.string().max(4000) }).strict(),
|
|
23
|
+
]);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsPDF } from "jspdf";
|
|
2
|
+
import { renderDocument } from "../document/render.js";
|
|
3
|
+
import { resolvedDocumentTheme } from "../document/theme.js";
|
|
4
|
+
// The PDFs themselves, drawn by jsPDF. Called only inside the PDF worker (`pdf-worker.ts`):
|
|
5
|
+
// jsPDF is synchronous and a print page is large, so the server never runs it in its thread.
|
|
6
|
+
// One page of the given size, the drawn piece filling it edge to edge. No bleed or crop
|
|
7
|
+
// marks: the PNG is already 300 dpi of the page.
|
|
8
|
+
export function posterPdf(job) {
|
|
9
|
+
const doc = new jsPDF({
|
|
10
|
+
orientation: job.widthMm > job.heightMm ? "landscape" : "portrait",
|
|
11
|
+
unit: "mm",
|
|
12
|
+
format: [job.widthMm, job.heightMm],
|
|
13
|
+
compress: true,
|
|
14
|
+
});
|
|
15
|
+
doc.addImage(job.png, "PNG", 0, 0, job.widthMm, job.heightMm, undefined, "FAST");
|
|
16
|
+
return new Uint8Array(doc.output("arraybuffer"));
|
|
17
|
+
}
|
|
18
|
+
// The theme's document: the designed cover on its title page, then the brochure's pages.
|
|
19
|
+
export function brochurePdf(job, assets) {
|
|
20
|
+
const rendered = renderDocument({
|
|
21
|
+
title: job.title,
|
|
22
|
+
articleMarkdown: job.markdown,
|
|
23
|
+
researchNotes: null,
|
|
24
|
+
cover: job.cover,
|
|
25
|
+
theme: resolvedDocumentTheme(job.settings),
|
|
26
|
+
writtenOn: new Date(job.writtenOn),
|
|
27
|
+
assets,
|
|
28
|
+
});
|
|
29
|
+
return { bytes: rendered.bytes, pages: rendered.pages, cover: rendered.cover };
|
|
30
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { fork } from "node:child_process";
|
|
2
|
+
import { pdfWorkerMessage } from "./pdf-job.js";
|
|
3
|
+
// Draws one design PDF in a forked worker, so jsPDF's synchronous work never holds up the
|
|
4
|
+
// server. Bytes travel both ways over IPC with structured-clone serialization; a cancel kills
|
|
5
|
+
// the worker outright. The worker's address is injectable for tests.
|
|
6
|
+
export const pdfWorkerTimeoutMs = 5 * 60_000;
|
|
7
|
+
export function runPdfWorker(job, signal, worker = new URL("./pdf-worker.js", import.meta.url), timeoutMs = pdfWorkerTimeoutMs) {
|
|
8
|
+
signal.throwIfAborted();
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const options = {
|
|
11
|
+
stdio: ["ignore", "ignore", "pipe", "ipc"],
|
|
12
|
+
windowsHide: true,
|
|
13
|
+
execArgv: [],
|
|
14
|
+
serialization: "advanced",
|
|
15
|
+
};
|
|
16
|
+
const child = fork(worker, [], options);
|
|
17
|
+
let finishing = false;
|
|
18
|
+
let failure;
|
|
19
|
+
let output = new Uint8Array();
|
|
20
|
+
let stderr = "";
|
|
21
|
+
const timer = setTimeout(() => finish(new Error("Making the PDF took longer than 5 minutes, so it was stopped. Press Download PDF again.")), timeoutMs);
|
|
22
|
+
const finish = (error, bytes) => {
|
|
23
|
+
if (finishing)
|
|
24
|
+
return;
|
|
25
|
+
finishing = true;
|
|
26
|
+
failure = error;
|
|
27
|
+
output = bytes ?? new Uint8Array();
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
signal.removeEventListener("abort", abort);
|
|
30
|
+
child.kill("SIGKILL");
|
|
31
|
+
};
|
|
32
|
+
const abort = () => finish(new Error("Making the PDF was canceled."));
|
|
33
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
34
|
+
child.stderr?.on("data", (data) => {
|
|
35
|
+
stderr = (stderr + data.toString("utf8")).slice(-2000);
|
|
36
|
+
});
|
|
37
|
+
child.on("error", (error) => finish(error));
|
|
38
|
+
child.on("close", (code) => {
|
|
39
|
+
if (!finishing)
|
|
40
|
+
finish(new Error(`Slopify's PDF maker stopped (exit ${String(code)}). ${stderr}`.trim()));
|
|
41
|
+
if (failure !== undefined)
|
|
42
|
+
reject(failure);
|
|
43
|
+
else
|
|
44
|
+
resolve(output);
|
|
45
|
+
});
|
|
46
|
+
child.on("message", (raw) => {
|
|
47
|
+
if (finishing)
|
|
48
|
+
return;
|
|
49
|
+
const parsed = pdfWorkerMessage.safeParse(raw);
|
|
50
|
+
if (!parsed.success) {
|
|
51
|
+
finish(new Error("Slopify's PDF maker answered with something it can't read. Press Download PDF again."));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (parsed.data.type === "error")
|
|
55
|
+
finish(new Error(`The PDF couldn't be made: ${parsed.data.message}`));
|
|
56
|
+
else
|
|
57
|
+
finish(undefined, parsed.data.bytes);
|
|
58
|
+
});
|
|
59
|
+
if (signal.aborted) {
|
|
60
|
+
abort();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
child.send(job, (error) => {
|
|
64
|
+
if (error !== null)
|
|
65
|
+
finish(error);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { errorText } from "../../kernel/errors.js";
|
|
2
|
+
import { readDocumentAssets } from "../document/fonts.js";
|
|
3
|
+
import { pdfJobSchema } from "./pdf-job.js";
|
|
4
|
+
import { brochurePdf, posterPdf } from "./pdf-pages.js";
|
|
5
|
+
// The forked process a design's PDF is drawn in (`pdf-runner.ts`): one job in, its bytes or
|
|
6
|
+
// what went wrong out, then it exits.
|
|
7
|
+
function answer(message) {
|
|
8
|
+
process.send?.(message, () => process.exit(0));
|
|
9
|
+
}
|
|
10
|
+
process.once("message", (raw) => {
|
|
11
|
+
const parsed = pdfJobSchema.safeParse(raw);
|
|
12
|
+
if (!parsed.success) {
|
|
13
|
+
answer({ type: "error", message: "The PDF worker was sent a job it can't read." });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const job = parsed.data;
|
|
18
|
+
const bytes = job.kind === "poster" ? posterPdf(job) : brochurePdf(job, readDocumentAssets()).bytes;
|
|
19
|
+
answer({ type: "done", bytes });
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
answer({ type: "error", message: errorText(error).slice(0, 4000) });
|
|
23
|
+
}
|
|
24
|
+
});
|