@gentbajko/slopify 3.25.9 → 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/4.0.0.md +51 -0
- package/dist/patch-notes/index.json +6 -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 +25 -45
- 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,79 @@
|
|
|
1
|
+
// The width and height a PNG's IHDR or a JPEG's start-of-frame states; undefined for anything
|
|
2
|
+
// truncated or not one of the two.
|
|
3
|
+
export function pictureSize(bytes) {
|
|
4
|
+
const size = bytes[0] === 0x89 && bytes[1] === 0x50
|
|
5
|
+
? pngSize(bytes)
|
|
6
|
+
: bytes[0] === 0xff
|
|
7
|
+
? jpegSize(bytes)
|
|
8
|
+
: undefined;
|
|
9
|
+
return size !== undefined && size.width > 0 && size.height > 0 ? size : undefined;
|
|
10
|
+
}
|
|
11
|
+
function pngSize(bytes) {
|
|
12
|
+
if (bytes.length < 24)
|
|
13
|
+
return undefined;
|
|
14
|
+
const ihdr = String.fromCharCode(...bytes.subarray(12, 16));
|
|
15
|
+
if (ihdr !== "IHDR")
|
|
16
|
+
return undefined;
|
|
17
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
18
|
+
return { width: view.getUint32(16), height: view.getUint32(20) };
|
|
19
|
+
}
|
|
20
|
+
// Markers that carry no length: start of image, TEM and the restart markers.
|
|
21
|
+
const standalone = (marker) => marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7);
|
|
22
|
+
// Every SOFn except DHT (C4), JPG (C8) and DAC (CC), which share the range.
|
|
23
|
+
const startOfFrame = (marker) => marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
|
|
24
|
+
function jpegSize(bytes) {
|
|
25
|
+
if (bytes[1] !== 0xd8)
|
|
26
|
+
return undefined;
|
|
27
|
+
let at = 2;
|
|
28
|
+
while (at + 1 < bytes.length) {
|
|
29
|
+
if (bytes[at] !== 0xff)
|
|
30
|
+
return undefined;
|
|
31
|
+
let marker = bytes[at + 1] ?? 0;
|
|
32
|
+
while (marker === 0xff && at + 2 < bytes.length) {
|
|
33
|
+
at += 1;
|
|
34
|
+
marker = bytes[at + 1] ?? 0;
|
|
35
|
+
}
|
|
36
|
+
if (standalone(marker)) {
|
|
37
|
+
at += 2;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// End of image or start of scan before any frame: no size to read.
|
|
41
|
+
if (marker === 0xd9 || marker === 0xda)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (at + 3 >= bytes.length)
|
|
44
|
+
return undefined;
|
|
45
|
+
const length = ((bytes[at + 2] ?? 0) << 8) | (bytes[at + 3] ?? 0);
|
|
46
|
+
if (length < 2)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (startOfFrame(marker)) {
|
|
49
|
+
if (at + 8 >= bytes.length || length < 7)
|
|
50
|
+
return undefined;
|
|
51
|
+
return {
|
|
52
|
+
height: ((bytes[at + 5] ?? 0) << 8) | (bytes[at + 6] ?? 0),
|
|
53
|
+
width: ((bytes[at + 7] ?? 0) << 8) | (bytes[at + 8] ?? 0),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
at += 2 + length;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const shapes = [
|
|
61
|
+
{ format: "16:9", ratio: 16 / 9 },
|
|
62
|
+
{ format: "1:1", ratio: 1 },
|
|
63
|
+
{ format: "9:16", ratio: 9 / 16 },
|
|
64
|
+
];
|
|
65
|
+
// The provider shape closest to the picture's, measured on a log scale so 2:1 and 1:2 are
|
|
66
|
+
// equally far from square.
|
|
67
|
+
export function nearestFormat(size) {
|
|
68
|
+
const ratio = Math.log(size.width / size.height);
|
|
69
|
+
let best = { format: "1:1", ratio: 1 };
|
|
70
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
71
|
+
for (const shape of shapes) {
|
|
72
|
+
const d = Math.abs(Math.log(shape.ratio) - ratio);
|
|
73
|
+
if (d < distance) {
|
|
74
|
+
best = shape;
|
|
75
|
+
distance = d;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return best.format;
|
|
79
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { castImageMaxBytes, sniffImage } from "../channels/images.js";
|
|
3
|
+
import { imageBlob } from "../channels/repo.js";
|
|
4
|
+
import { stagingPath } from "../storage/layout.js";
|
|
5
|
+
import { stagedFileById } from "../storage/repo.js";
|
|
6
|
+
import { pictureSize } from "./picture-size.js";
|
|
7
|
+
export function readPicture(bytes) {
|
|
8
|
+
if (bytes.byteLength > castImageMaxBytes)
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
reason: "not-an-image",
|
|
12
|
+
message: `This picture is larger than ${String(castImageMaxBytes / 1024 / 1024)} MB. Save it smaller (for example as a JPEG) and upload it again.`,
|
|
13
|
+
};
|
|
14
|
+
const mime = sniffImage(bytes);
|
|
15
|
+
const size = mime === undefined ? undefined : pictureSize(bytes);
|
|
16
|
+
if (mime === undefined || size === undefined)
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
reason: "not-an-image",
|
|
20
|
+
message: "This file is not a PNG or JPEG picture Slopify can read. Upload a PNG or JPEG file.",
|
|
21
|
+
};
|
|
22
|
+
return { ok: true, value: { bytes, mime, ...size } };
|
|
23
|
+
}
|
|
24
|
+
export function startingPicture(deps, input) {
|
|
25
|
+
if (input.stagedFileId !== undefined) {
|
|
26
|
+
const staged = stagedFileById(deps.db, input.stagedFileId);
|
|
27
|
+
if (staged === undefined)
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
reason: "upload-gone",
|
|
31
|
+
message: "The uploaded picture is gone. Upload it again.",
|
|
32
|
+
};
|
|
33
|
+
if (staged.state !== "staged")
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
reason: "still-uploading",
|
|
37
|
+
message: "The picture is still uploading. Wait for it to finish, then try again.",
|
|
38
|
+
};
|
|
39
|
+
if (staged.bytes > castImageMaxBytes)
|
|
40
|
+
return readPicture(new Uint8Array(castImageMaxBytes + 1));
|
|
41
|
+
const read = readPicture(new Uint8Array(readFileSync(stagingPath(deps.paths, staged.path))));
|
|
42
|
+
return read.ok ? { ok: true, value: { ...read.value, stagedId: staged.id } } : read;
|
|
43
|
+
}
|
|
44
|
+
if (input.fromStepSha !== undefined) {
|
|
45
|
+
const blob = imageBlob(deps.db, input.fromStepSha);
|
|
46
|
+
if (blob === undefined)
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: "not-found",
|
|
50
|
+
message: "That picture is no longer kept. Choose another version, or upload the picture again.",
|
|
51
|
+
};
|
|
52
|
+
return readPicture(blob.bytes);
|
|
53
|
+
}
|
|
54
|
+
return { ok: true, value: undefined };
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { estimateRequests } from "../estimate/requests.js";
|
|
2
|
+
// What one paid picture of a design costs: an edit (which sends pictures, so its price is a
|
|
3
|
+
// range) or the artwork. Quoting records nothing.
|
|
4
|
+
export function quoteDesignImage(catalogue, request) {
|
|
5
|
+
return estimateRequests([
|
|
6
|
+
{
|
|
7
|
+
kind: "image",
|
|
8
|
+
provider: request.provider,
|
|
9
|
+
model: request.model,
|
|
10
|
+
stage: request.purpose === "picture-edit" ? "Picture edit" : "Artwork",
|
|
11
|
+
variable: request.sendsPictures,
|
|
12
|
+
},
|
|
13
|
+
], catalogue);
|
|
14
|
+
}
|
|
15
|
+
// A paid step starts only when what it costs now is no more than what the person was shown,
|
|
16
|
+
// give or take half a cent of rounding, and a price nobody knows only once they accepted that.
|
|
17
|
+
export function costChanged(quote, acknowledgedHigh, acknowledgedUnknown = false) {
|
|
18
|
+
return quote.high > acknowledgedHigh + 0.005 || (quote.unknown > 0 && !acknowledgedUnknown);
|
|
19
|
+
}
|
|
20
|
+
export function costChangedMessage(quote, what) {
|
|
21
|
+
if (quote.unknown > 0)
|
|
22
|
+
return `The price changed since you looked: ${what} has no known price now, so nothing was started. Check the button, which says the price is unknown, and press it again to make it anyway.`;
|
|
23
|
+
return `The price changed since you looked: ${what} now costs up to $${quote.high.toFixed(2)}. Check the new price on the button and press it again.`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The ffmpeg arguments that draw a piece and shrink a picture.
|
|
2
|
+
export function renderArgs(input) {
|
|
3
|
+
const { crop, frame } = input;
|
|
4
|
+
const chain = [
|
|
5
|
+
`scale=${String(crop.scaleWidth)}:${String(crop.scaleHeight)}:flags=lanczos`,
|
|
6
|
+
`crop=${String(frame.width)}:${String(frame.height)}:${String(crop.x)}:${String(crop.y)}`,
|
|
7
|
+
...(input.text ? ["ass=filename=design.ass:fontsdir=fonts"] : []),
|
|
8
|
+
"format=rgb24",
|
|
9
|
+
];
|
|
10
|
+
return [
|
|
11
|
+
...["-hide_banner", "-nostdin", "-loglevel", "error", "-y", "-i", input.art],
|
|
12
|
+
...[
|
|
13
|
+
"-filter_complex",
|
|
14
|
+
`[0:v]${chain.join(",")}[v]`,
|
|
15
|
+
"-map",
|
|
16
|
+
"[v]",
|
|
17
|
+
"-frames:v",
|
|
18
|
+
"1",
|
|
19
|
+
input.output,
|
|
20
|
+
],
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
// What a picture is shrunk to before it is sent to a provider again: at most 4096 pixels on
|
|
24
|
+
// its long side, as a JPEG.
|
|
25
|
+
export function shrinkArgs(input, output) {
|
|
26
|
+
return [
|
|
27
|
+
...["-hide_banner", "-nostdin", "-loglevel", "error", "-y", "-i", input],
|
|
28
|
+
...[
|
|
29
|
+
"-vf",
|
|
30
|
+
"scale=w='min(iw,4096)':h='min(ih,4096)':force_original_aspect_ratio=decrease",
|
|
31
|
+
"-q:v",
|
|
32
|
+
"3",
|
|
33
|
+
output,
|
|
34
|
+
],
|
|
35
|
+
];
|
|
36
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { imageBlob } from "../channels/repo.js";
|
|
2
|
+
import { fillDocument, missingSentence, missingValues } from "./fields.js";
|
|
3
|
+
import { formatLabels } from "./model.js";
|
|
4
|
+
import { designDocumentSchema } from "./schema.js";
|
|
5
|
+
import { designView } from "./service.js";
|
|
6
|
+
export function prepare(db, designId, format) {
|
|
7
|
+
const design = designView(db, designId);
|
|
8
|
+
if (design === undefined)
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
reason: "not-found",
|
|
12
|
+
message: "This design no longer exists. Open Library → Designs to choose another.",
|
|
13
|
+
};
|
|
14
|
+
const parsed = designDocumentSchema.safeParse(design.document);
|
|
15
|
+
if (!parsed.success)
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
reason: "invalid",
|
|
19
|
+
message: "This picture has no formats yet. Press Turn into a designed piece to add text and formats, or download the picture itself.",
|
|
20
|
+
};
|
|
21
|
+
if (!parsed.data.formats.some((one) => one.format === format))
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
reason: "invalid",
|
|
25
|
+
message: `${formatLabels[format]} isn't ticked for this design. Tick it under Formats, then download again.`,
|
|
26
|
+
};
|
|
27
|
+
const head = design.steps.find((step) => step.id === design.headStepId);
|
|
28
|
+
const blob = head === undefined || head.sha256 === null ? undefined : imageBlob(db, head.sha256);
|
|
29
|
+
if (head === undefined ||
|
|
30
|
+
blob === undefined ||
|
|
31
|
+
head.width === null ||
|
|
32
|
+
head.height === null ||
|
|
33
|
+
head.sha256 === null)
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
reason: "not-ready",
|
|
37
|
+
message: "This design has no artwork yet. Press Make to draw it, then download again.",
|
|
38
|
+
};
|
|
39
|
+
const missing = missingValues({ ...parsed.data, picture: { ...parsed.data.picture, prompt: "" } }, design.values);
|
|
40
|
+
if (missing.length > 0)
|
|
41
|
+
return { ok: false, reason: "missing-values", message: missingSentence(missing) };
|
|
42
|
+
return {
|
|
43
|
+
ok: true,
|
|
44
|
+
value: {
|
|
45
|
+
doc: parsed.data,
|
|
46
|
+
values: design.values,
|
|
47
|
+
art: { ...blob, width: head.width, height: head.height, sha256: head.sha256 },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function fileName(doc, values, format) {
|
|
52
|
+
const headline = fillDocument(doc, values).text.blocks.find((block) => block.text.trim() !== "")?.text ??
|
|
53
|
+
"design";
|
|
54
|
+
const safe = headline
|
|
55
|
+
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
|
56
|
+
.replace(/^-+|-+$/g, "")
|
|
57
|
+
.slice(0, 60) || "design";
|
|
58
|
+
return `${safe}-${format}`;
|
|
59
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { basename, join } from "node:path";
|
|
5
|
+
import { errorText } from "../../kernel/errors.js";
|
|
6
|
+
import { fontAdvances } from "../fonts/sfnt.js";
|
|
7
|
+
import { checkText } from "./check-text.js";
|
|
8
|
+
import { fillDocument } from "./fields.js";
|
|
9
|
+
import { coverCrop, frames } from "./frames.js";
|
|
10
|
+
import { layoutText } from "./layout.js";
|
|
11
|
+
import { formatLabels } from "./model.js";
|
|
12
|
+
import { renderArgs, shrinkArgs } from "./render-args.js";
|
|
13
|
+
import { fileName, prepare } from "./render-input.js";
|
|
14
|
+
// A finished piece, drawn on demand: the artwork cover-cropped to the format's frame and the
|
|
15
|
+
// text layer set over it by libass, in a child ffmpeg (never this thread); print formats then
|
|
16
|
+
// go to the PDF worker. One render at a time. Files are a cache under the data dir, named by
|
|
17
|
+
// the design and a hash of everything drawn, so the same piece is drawn once; they are never
|
|
18
|
+
// backed up and are deleted with the design.
|
|
19
|
+
// 2: text measured with the font's own widths.
|
|
20
|
+
export const rendererVersion = 2;
|
|
21
|
+
export const rendersDir = (paths) => join(paths.dataDir, "design-renders");
|
|
22
|
+
export function createDesignRenderer(deps) {
|
|
23
|
+
let queue = Promise.resolve();
|
|
24
|
+
const oneAtATime = (work) => {
|
|
25
|
+
const next = queue.then(work, work);
|
|
26
|
+
queue = next.catch(() => undefined);
|
|
27
|
+
return next;
|
|
28
|
+
};
|
|
29
|
+
const png = async (prepared, designId, format, signal) => {
|
|
30
|
+
const { doc, art, values } = prepared;
|
|
31
|
+
const frame = frames[format];
|
|
32
|
+
const entry = doc.formats.find((one) => one.format === format);
|
|
33
|
+
const focus = entry?.focus ?? { x: 0.5, y: 0.5 };
|
|
34
|
+
const textLayer = !doc.picture.aiText;
|
|
35
|
+
const fontIds = textLayer ? [...new Set(doc.text.blocks.map((block) => block.fontId))] : [];
|
|
36
|
+
const fonts = new Map();
|
|
37
|
+
for (const id of fontIds) {
|
|
38
|
+
try {
|
|
39
|
+
fonts.set(id, await deps.font(id));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
deps.log.write("warn", "designs.render.font", {
|
|
43
|
+
detail: `${designId}: ${errorText(error)}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const filled = fillDocument(doc, values);
|
|
48
|
+
const key = createHash("sha256")
|
|
49
|
+
.update(JSON.stringify({
|
|
50
|
+
rendererVersion,
|
|
51
|
+
art: art.sha256,
|
|
52
|
+
format,
|
|
53
|
+
focus,
|
|
54
|
+
text: textLayer ? filled.text : null,
|
|
55
|
+
fonts: [...fonts].map(([id, font]) => [id, font.path, font.faceIndex]),
|
|
56
|
+
}))
|
|
57
|
+
.digest("hex")
|
|
58
|
+
.slice(0, 32);
|
|
59
|
+
const dir = join(rendersDir(deps.paths), designId);
|
|
60
|
+
const target = join(dir, `${format}-${key}.png`);
|
|
61
|
+
// Drawn before from the very same text, fonts and artwork, so it passed the checks then.
|
|
62
|
+
if (existsSync(target))
|
|
63
|
+
return { ok: true, value: target };
|
|
64
|
+
const checkFonts = {};
|
|
65
|
+
for (const [id, font] of fonts)
|
|
66
|
+
checkFonts[id] = {
|
|
67
|
+
name: font.name,
|
|
68
|
+
bytes: new Uint8Array(await readFile(font.path)),
|
|
69
|
+
faceIndex: font.faceIndex,
|
|
70
|
+
};
|
|
71
|
+
// Measured with the font's own widths; a font that can't be read is estimated.
|
|
72
|
+
const layoutFonts = Object.fromEntries([...fonts].map(([id, font]) => {
|
|
73
|
+
const bytes = checkFonts[id]?.bytes;
|
|
74
|
+
const advance = bytes === undefined ? undefined : fontAdvances(bytes, font.faceIndex);
|
|
75
|
+
return [id, { assName: font.assName, advance }];
|
|
76
|
+
}));
|
|
77
|
+
const layout = layoutText(frame, doc.text, values, layoutFonts);
|
|
78
|
+
if (textLayer) {
|
|
79
|
+
const problem = checkText({
|
|
80
|
+
text: doc.text,
|
|
81
|
+
values,
|
|
82
|
+
layout,
|
|
83
|
+
fonts: checkFonts,
|
|
84
|
+
formatLabel: formatLabels[format],
|
|
85
|
+
})[0];
|
|
86
|
+
if (problem !== undefined)
|
|
87
|
+
return { ok: false, reason: "invalid", message: problem.sentence };
|
|
88
|
+
}
|
|
89
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
90
|
+
const work = mkdtempSync(join(dir, ".work-"));
|
|
91
|
+
try {
|
|
92
|
+
const artName = art.mime === "image/jpeg" ? "art.jpg" : "art.png";
|
|
93
|
+
writeFileSync(join(work, artName), art.bytes, { mode: 0o600 });
|
|
94
|
+
if (textLayer) {
|
|
95
|
+
mkdirSync(join(work, "fonts"), { mode: 0o700 });
|
|
96
|
+
for (const [index, font] of [...fonts.values()].entries())
|
|
97
|
+
copyFileSync(font.path, join(work, "fonts", `font-${String(index)}${font.extension}`));
|
|
98
|
+
writeFileSync(join(work, "design.ass"), layout.ass, { mode: 0o600 });
|
|
99
|
+
}
|
|
100
|
+
await deps.runFfmpeg({
|
|
101
|
+
bin: deps.ffmpeg,
|
|
102
|
+
cwd: work,
|
|
103
|
+
args: renderArgs({
|
|
104
|
+
art: artName,
|
|
105
|
+
crop: coverCrop(art, frame, focus),
|
|
106
|
+
frame,
|
|
107
|
+
text: textLayer,
|
|
108
|
+
output: "out.png",
|
|
109
|
+
}),
|
|
110
|
+
signal,
|
|
111
|
+
log: deps.log,
|
|
112
|
+
onProgress: () => undefined,
|
|
113
|
+
});
|
|
114
|
+
renameSync(join(work, "out.png"), target);
|
|
115
|
+
dropOlderDrawings(dir, format, target);
|
|
116
|
+
return { ok: true, value: target };
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
rmSync(work, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
shrink: (bytes, signal) => oneAtATime(async () => {
|
|
124
|
+
const dir = rendersDir(deps.paths);
|
|
125
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
126
|
+
const work = mkdtempSync(join(dir, ".work-"));
|
|
127
|
+
try {
|
|
128
|
+
writeFileSync(join(work, "in"), bytes, { mode: 0o600 });
|
|
129
|
+
await deps.runFfmpeg({
|
|
130
|
+
bin: deps.ffmpeg,
|
|
131
|
+
cwd: work,
|
|
132
|
+
args: shrinkArgs("in", "out.jpg"),
|
|
133
|
+
signal,
|
|
134
|
+
log: deps.log,
|
|
135
|
+
onProgress: () => undefined,
|
|
136
|
+
});
|
|
137
|
+
return new Uint8Array(readFileSync(join(work, "out.jpg")));
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
rmSync(work, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
}),
|
|
143
|
+
render: (designId, format, type, signal) => oneAtATime(async () => {
|
|
144
|
+
const prepared = prepare(deps.db, designId, format);
|
|
145
|
+
if (!prepared.ok)
|
|
146
|
+
return prepared;
|
|
147
|
+
const frame = frames[format];
|
|
148
|
+
if (type === "pdf" && frame.pdf === undefined)
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
reason: "invalid",
|
|
152
|
+
message: `${formatLabels[format]} is a screen size, so it downloads as a PNG. Press Download PNG.`,
|
|
153
|
+
};
|
|
154
|
+
if (type === "png" && format === "brochure")
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
reason: "invalid",
|
|
158
|
+
message: "A brochure downloads as a PDF. Press Download PDF.",
|
|
159
|
+
};
|
|
160
|
+
const drawn = await png(prepared.value, designId, format, signal);
|
|
161
|
+
if (!drawn.ok)
|
|
162
|
+
return drawn;
|
|
163
|
+
const name = fileName(prepared.value.doc, prepared.value.values, format);
|
|
164
|
+
if (type === "png")
|
|
165
|
+
return { ok: true, value: { path: drawn.value, mime: "image/png", name: `${name}.png` } };
|
|
166
|
+
const pdfPath = drawn.value.replace(/\.png$/, ".pdf");
|
|
167
|
+
if (!existsSync(pdfPath)) {
|
|
168
|
+
if (deps.pdf === undefined)
|
|
169
|
+
throw new Error("design PDFs need the PDF worker");
|
|
170
|
+
const page = frame.pdf;
|
|
171
|
+
const filled = fillDocument(prepared.value.doc, prepared.value.values);
|
|
172
|
+
const bytes = await deps.pdf(format === "brochure" && filled.brochure !== undefined
|
|
173
|
+
? {
|
|
174
|
+
kind: "brochure",
|
|
175
|
+
cover: new Uint8Array(readFileSync(drawn.value)),
|
|
176
|
+
title: filled.brochure.title,
|
|
177
|
+
markdown: filled.brochure.body,
|
|
178
|
+
settings: filled.brochure.settings,
|
|
179
|
+
writtenOn: deps.clock.now().toISOString(),
|
|
180
|
+
}
|
|
181
|
+
: {
|
|
182
|
+
kind: "poster",
|
|
183
|
+
png: new Uint8Array(readFileSync(drawn.value)),
|
|
184
|
+
widthMm: page?.widthMm ?? 210,
|
|
185
|
+
heightMm: page?.heightMm ?? 297,
|
|
186
|
+
}, signal);
|
|
187
|
+
const partial = `${pdfPath}.part`;
|
|
188
|
+
writeFileSync(partial, bytes, { mode: 0o600 });
|
|
189
|
+
renameSync(partial, pdfPath);
|
|
190
|
+
}
|
|
191
|
+
return { ok: true, value: { path: pdfPath, mime: "application/pdf", name: `${name}.pdf` } };
|
|
192
|
+
}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// What this format looked like before its newest drawing (PNG and PDF): nothing asks for those
|
|
196
|
+
// again, so the cache holds one drawing per format.
|
|
197
|
+
function dropOlderDrawings(dir, format, target) {
|
|
198
|
+
const keep = basename(target, ".png");
|
|
199
|
+
const drawing = new RegExp(`^(${format}-[a-f0-9]{32})\\.(png|pdf)$`);
|
|
200
|
+
for (const name of readdirSync(dir)) {
|
|
201
|
+
const stem = drawing.exec(name)?.[1];
|
|
202
|
+
if (stem !== undefined && stem !== keep)
|
|
203
|
+
rmSync(join(dir, name), { force: true });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Every drawn file of a design, removed when it is purged. Design ids are letters, digits,
|
|
207
|
+
// dashes and underscores (`requests.ts`), so one is always a single folder name.
|
|
208
|
+
export function deleteRenders(paths, designId) {
|
|
209
|
+
const dir = join(rendersDir(paths), designId);
|
|
210
|
+
if (!/^[A-Za-z0-9_-]+$/.test(designId) || !existsSync(dir))
|
|
211
|
+
return 0;
|
|
212
|
+
const count = readdirSync(dir).length;
|
|
213
|
+
rmSync(dir, { recursive: true, force: true });
|
|
214
|
+
return count;
|
|
215
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { designKinds } from "./model.js";
|
|
3
|
+
// Rows of `designs` (migration 0070). Every list leaves out deleted designs
|
|
4
|
+
// and the hidden samples a design template's editor redraws (`sample_of`).
|
|
5
|
+
const designRow = z.object({
|
|
6
|
+
id: z.string(),
|
|
7
|
+
kind: z.enum(designKinds),
|
|
8
|
+
name: z.string(),
|
|
9
|
+
channel_id: z.string(),
|
|
10
|
+
project_id: z.string().nullable(),
|
|
11
|
+
template_id: z.string().nullable(),
|
|
12
|
+
template_version: z.number().nullable(),
|
|
13
|
+
schedule_id: z.string().nullable(),
|
|
14
|
+
schedule_run_id: z.string().nullable(),
|
|
15
|
+
document_json: z.string().nullable(),
|
|
16
|
+
values_json: z.string(),
|
|
17
|
+
head_step_id: z.string().nullable(),
|
|
18
|
+
sample_of: z.string().nullable(),
|
|
19
|
+
created_at: z.string(),
|
|
20
|
+
updated_at: z.string(),
|
|
21
|
+
deleted_at: z.string().nullable(),
|
|
22
|
+
});
|
|
23
|
+
const valuesSchema = z.record(z.string(), z.string());
|
|
24
|
+
export const notDesignSample = "sample_of IS NULL";
|
|
25
|
+
function toDesign(raw) {
|
|
26
|
+
const row = designRow.parse(raw);
|
|
27
|
+
return {
|
|
28
|
+
id: row.id,
|
|
29
|
+
kind: row.kind,
|
|
30
|
+
name: row.name,
|
|
31
|
+
channelId: row.channel_id,
|
|
32
|
+
projectId: row.project_id,
|
|
33
|
+
templateId: row.template_id,
|
|
34
|
+
templateVersion: row.template_version,
|
|
35
|
+
scheduleId: row.schedule_id,
|
|
36
|
+
scheduleRunId: row.schedule_run_id,
|
|
37
|
+
document: row.document_json === null ? null : JSON.parse(row.document_json),
|
|
38
|
+
values: valuesSchema.catch({}).parse(JSON.parse(row.values_json)),
|
|
39
|
+
headStepId: row.head_step_id,
|
|
40
|
+
sampleOf: row.sample_of,
|
|
41
|
+
createdAt: row.created_at,
|
|
42
|
+
updatedAt: row.updated_at,
|
|
43
|
+
deletedAt: row.deleted_at,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function insertDesign(db, row) {
|
|
47
|
+
db.prepare(`INSERT INTO designs (id,kind,name,channel_id,project_id,template_id,template_version,schedule_id,schedule_run_id,document_json,values_json,sample_of,mutation_id,mutation_hash,created_at,updated_at)
|
|
48
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(row.id, row.kind, row.name, row.channelId, row.projectId, row.templateId ?? null, row.templateVersion ?? null, row.scheduleId ?? null, row.scheduleRunId ?? null, row.document === null || row.document === undefined ? null : JSON.stringify(row.document), JSON.stringify(row.values), row.sampleOf ?? null, row.mutationId, row.mutationHash, row.at, row.at);
|
|
49
|
+
}
|
|
50
|
+
// Deleted designs included: the trash and a mutation replay read them too.
|
|
51
|
+
export function designById(db, id) {
|
|
52
|
+
const row = db.prepare("SELECT * FROM designs WHERE id=?").get(id);
|
|
53
|
+
return row === undefined ? undefined : toDesign(row);
|
|
54
|
+
}
|
|
55
|
+
export function designByMutation(db, mutationId) {
|
|
56
|
+
const row = db.prepare("SELECT * FROM designs WHERE mutation_id=?").get(mutationId);
|
|
57
|
+
return row === undefined
|
|
58
|
+
? undefined
|
|
59
|
+
: {
|
|
60
|
+
design: toDesign(row),
|
|
61
|
+
hash: row.mutation_hash === null ? null : String(row.mutation_hash),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function listDesigns(db, filter = {}) {
|
|
65
|
+
const where = [`deleted_at IS NULL`, notDesignSample];
|
|
66
|
+
const params = [];
|
|
67
|
+
if (filter.projectId !== undefined) {
|
|
68
|
+
where.push("project_id=?");
|
|
69
|
+
params.push(filter.projectId);
|
|
70
|
+
}
|
|
71
|
+
return db
|
|
72
|
+
.prepare(`SELECT * FROM designs WHERE ${where.join(" AND ")} ORDER BY updated_at DESC, id`)
|
|
73
|
+
.all(...params)
|
|
74
|
+
.map(toDesign);
|
|
75
|
+
}
|
|
76
|
+
// Only when the row still has `baseUpdatedAt`; false when someone changed it meanwhile.
|
|
77
|
+
export function updateDesign(db, id, baseUpdatedAt, changes, at) {
|
|
78
|
+
const sets = ["updated_at=?"];
|
|
79
|
+
const params = [at];
|
|
80
|
+
const set = (column, value) => {
|
|
81
|
+
sets.push(`${column}=?`);
|
|
82
|
+
params.push(value);
|
|
83
|
+
};
|
|
84
|
+
if (changes.name !== undefined)
|
|
85
|
+
set("name", changes.name);
|
|
86
|
+
if (changes.kind !== undefined)
|
|
87
|
+
set("kind", changes.kind);
|
|
88
|
+
if (changes.projectId !== undefined)
|
|
89
|
+
set("project_id", changes.projectId);
|
|
90
|
+
if (changes.document !== undefined)
|
|
91
|
+
set("document_json", JSON.stringify(changes.document));
|
|
92
|
+
if (changes.values !== undefined)
|
|
93
|
+
set("values_json", JSON.stringify(changes.values));
|
|
94
|
+
if (changes.headStepId !== undefined)
|
|
95
|
+
set("head_step_id", changes.headStepId);
|
|
96
|
+
const guard = baseUpdatedAt === undefined ? "" : " AND updated_at=?";
|
|
97
|
+
const result = db
|
|
98
|
+
.prepare(`UPDATE designs SET ${sets.join(",")} WHERE id=? AND deleted_at IS NULL${guard}`)
|
|
99
|
+
.run(...params, id, ...(baseUpdatedAt === undefined ? [] : [baseUpdatedAt]));
|
|
100
|
+
return Number(result.changes) > 0;
|
|
101
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { castPerDesignMax, designKinds, designNameMax, instructionMax } from "./model.js";
|
|
3
|
+
import { designDocumentSchema } from "./schema.js";
|
|
4
|
+
// What the design routes and tools take. Browser-safe, like schema.ts.
|
|
5
|
+
// Letters, digits, dashes and underscores: a design id names its folder of drawn files.
|
|
6
|
+
const id = z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.max(64)
|
|
10
|
+
.regex(/^[A-Za-z0-9_-]+$/);
|
|
11
|
+
const mutationId = z.string().min(8).max(128);
|
|
12
|
+
const sha256 = z.string().regex(/^[a-f0-9]{64}$/);
|
|
13
|
+
export const designValuesSchema = z
|
|
14
|
+
.record(z.string().min(1).max(100), z.string().max(2000))
|
|
15
|
+
.refine((values) => Object.keys(values).length <= 100, {
|
|
16
|
+
message: "A design has at most 100 fields.",
|
|
17
|
+
});
|
|
18
|
+
export const createDesignSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
id,
|
|
21
|
+
mutationId,
|
|
22
|
+
kind: z.enum(designKinds),
|
|
23
|
+
name: z.string().trim().min(1).max(designNameMax),
|
|
24
|
+
channelId: id.optional(),
|
|
25
|
+
projectId: id.optional(),
|
|
26
|
+
// The picture to start from: an upload, or a version another design already has.
|
|
27
|
+
stagedFileId: id.optional(),
|
|
28
|
+
fromStepSha: sha256.optional(),
|
|
29
|
+
document: designDocumentSchema.optional(),
|
|
30
|
+
values: designValuesSchema.optional(),
|
|
31
|
+
})
|
|
32
|
+
.strict()
|
|
33
|
+
.refine((input) => input.stagedFileId === undefined || input.fromStepSha === undefined, {
|
|
34
|
+
message: "Start from an upload or from an earlier version, not both.",
|
|
35
|
+
});
|
|
36
|
+
export const updateDesignSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
baseUpdatedAt: z.string().min(1).max(64),
|
|
39
|
+
name: z.string().trim().min(1).max(designNameMax).optional(),
|
|
40
|
+
projectId: id.nullable().optional(),
|
|
41
|
+
document: designDocumentSchema.optional(),
|
|
42
|
+
values: designValuesSchema.optional(),
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
export const quoteEditSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
provider: z.string().min(1).max(64),
|
|
48
|
+
model: z.string().min(1).max(200),
|
|
49
|
+
castMemberIds: z.array(id).max(castPerDesignMax).optional(),
|
|
50
|
+
})
|
|
51
|
+
.strict();
|
|
52
|
+
export const editPictureSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
mutationId,
|
|
55
|
+
fromStepId: id,
|
|
56
|
+
instruction: z.string().trim().min(1).max(instructionMax),
|
|
57
|
+
provider: z.string().min(1).max(64),
|
|
58
|
+
model: z.string().min(1).max(200),
|
|
59
|
+
castMemberIds: z.array(id).max(castPerDesignMax).optional(),
|
|
60
|
+
// The highest cost the person saw on the button, in dollars; 0 for plan usage.
|
|
61
|
+
acknowledgedHigh: z.number().min(0).max(1000),
|
|
62
|
+
// That the button said the price is unknown, and the person pressed it anyway.
|
|
63
|
+
acknowledgedUnknown: z.boolean().optional(),
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
66
|
+
export const makeArtworkSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
mutationId,
|
|
69
|
+
acknowledgedHigh: z.number().min(0).max(1000),
|
|
70
|
+
acknowledgedUnknown: z.boolean().optional(),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
// Try again asks the provider again, which is paid again: at the price the button showed.
|
|
74
|
+
export const retryStepSchema = z
|
|
75
|
+
.object({
|
|
76
|
+
acknowledgedHigh: z.number().min(0).max(1000),
|
|
77
|
+
acknowledgedUnknown: z.boolean().optional(),
|
|
78
|
+
})
|
|
79
|
+
.strict();
|
|
80
|
+
export const setHeadSchema = z.object({ stepId: id }).strict();
|