@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,176 @@
|
|
|
1
|
+
import { projectById } from "../admission/repo.js";
|
|
2
|
+
import { render } from "../admission/substitute.js";
|
|
3
|
+
import { fillWith } from "../keywords/fill.js";
|
|
4
|
+
import { keywordOf } from "../keywords/specs.js";
|
|
5
|
+
import { factValues } from "../keywords/values.js";
|
|
6
|
+
import { defaultReddit } from "./defaults.js";
|
|
7
|
+
import { mediaWarnings, redditWarnings, scheduleFor } from "./fit.js";
|
|
8
|
+
import { composeUrls, limitsFor, redditSubmitUrl } from "./limits.js";
|
|
9
|
+
import { mediaFor } from "./media.js";
|
|
10
|
+
import { platformLabels, socialPlatforms, } from "./model.js";
|
|
11
|
+
import { postsOf } from "./repo.js";
|
|
12
|
+
import { postSources } from "./sources.js";
|
|
13
|
+
import { countFor, fitText, hashtagsFor } from "./text.js";
|
|
14
|
+
// The settings a site's post is made with: the project's own pick, else its template's. Off
|
|
15
|
+
// for this project, or a site neither turned on, has none.
|
|
16
|
+
export function choiceOf(settings, record, platform) {
|
|
17
|
+
const own = record?.choice ?? null;
|
|
18
|
+
if (own !== null)
|
|
19
|
+
return "off" in own ? undefined : { choice: own, chosenHere: true };
|
|
20
|
+
const base = settings?.[platform];
|
|
21
|
+
return base === undefined ? undefined : { choice: base, chosenHere: false };
|
|
22
|
+
}
|
|
23
|
+
// Every site the project posts to, each with its post as Prepare upload shows it and the
|
|
24
|
+
// extension fills it. A project with no posting settings and no picks of its own has none.
|
|
25
|
+
export function postPacks(deps, projectId, now) {
|
|
26
|
+
const project = projectById(deps.db, projectId);
|
|
27
|
+
if (project === undefined)
|
|
28
|
+
return { ok: false, reason: "unknown-project" };
|
|
29
|
+
const records = postsOf(deps.db, projectId);
|
|
30
|
+
const settings = project.config.social;
|
|
31
|
+
const wanted = socialPlatforms.filter((platform) => choiceOf(settings, records.find((one) => one.platform === platform), platform) !== undefined);
|
|
32
|
+
if (wanted.length === 0)
|
|
33
|
+
return { ok: true, packs: { projectId, packs: [] } };
|
|
34
|
+
const sources = postSources(deps, projectId);
|
|
35
|
+
if (sources === undefined)
|
|
36
|
+
return { ok: false, reason: "unknown-project" };
|
|
37
|
+
const packs = wanted.flatMap((platform) => {
|
|
38
|
+
const record = records.find((one) => one.platform === platform && one.item === 0);
|
|
39
|
+
const pack = buildPack(sources, platform, record, now);
|
|
40
|
+
return pack === undefined ? [] : [pack];
|
|
41
|
+
});
|
|
42
|
+
return { ok: true, packs: { projectId, packs } };
|
|
43
|
+
}
|
|
44
|
+
export function packFor(deps, projectId, platform, now) {
|
|
45
|
+
const result = postPacks(deps, projectId, now);
|
|
46
|
+
return result.ok ? result.packs.packs.find((pack) => pack.platform === platform) : undefined;
|
|
47
|
+
}
|
|
48
|
+
function bodyFor(sources, choice, platform) {
|
|
49
|
+
switch (choice.text) {
|
|
50
|
+
case "description":
|
|
51
|
+
return sources.description;
|
|
52
|
+
case "summary":
|
|
53
|
+
return sources.summary;
|
|
54
|
+
case "title":
|
|
55
|
+
return sources.title;
|
|
56
|
+
case "prompt":
|
|
57
|
+
return sources.written[platform] ?? "";
|
|
58
|
+
case "custom":
|
|
59
|
+
return customText(sources, choice.custom ?? "");
|
|
60
|
+
case "none":
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// The person's own words with their fields and the keywords Slopify fills: the facts copied
|
|
65
|
+
// when the run started, and the summary. A keyword with no value leaves its line out.
|
|
66
|
+
function customText(sources, custom) {
|
|
67
|
+
const own = render(custom, sources.config.values);
|
|
68
|
+
const values = {
|
|
69
|
+
...factValues(sources.config.keywordFacts, sources.title, "description"),
|
|
70
|
+
Slopify_Summary: sources.summary,
|
|
71
|
+
};
|
|
72
|
+
const filled = fillWith(own, (name) => keywordOf(name)?.name === name
|
|
73
|
+
? Object.hasOwn(values, name)
|
|
74
|
+
? (values[name] ?? "")
|
|
75
|
+
: ""
|
|
76
|
+
: undefined);
|
|
77
|
+
return filled.ok ? filled.text.trim() : own.trim();
|
|
78
|
+
}
|
|
79
|
+
function buildPack(sources, platform, record, now) {
|
|
80
|
+
const picked = choiceOf(sources.config.social, record, platform);
|
|
81
|
+
if (picked === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
const { choice } = picked;
|
|
84
|
+
const site = platformLabels[platform];
|
|
85
|
+
const tier = sources.channel.xTier;
|
|
86
|
+
const limits = limitsFor(platform, tier);
|
|
87
|
+
const warnings = [];
|
|
88
|
+
const { media, warnings: mediaMissing } = mediaFor(platform, choice.media, sources.media);
|
|
89
|
+
warnings.push(...mediaMissing, ...mediaWarnings(platform, media, tier));
|
|
90
|
+
const body = bodyFor(sources, choice, platform);
|
|
91
|
+
if (choice.text !== "none" && body === "")
|
|
92
|
+
warnings.push({ code: "text-missing", sentence: missingText(choice, site) });
|
|
93
|
+
const link = choice.link ? sources.youtubeLink : null;
|
|
94
|
+
if (choice.link && link === null)
|
|
95
|
+
warnings.push({
|
|
96
|
+
code: "link-missing",
|
|
97
|
+
sentence: "The YouTube link isn't known yet, so the post goes without it. It is added once the video is on YouTube (Prepare upload → Fill in YouTube Studio, or paste the video's link there).",
|
|
98
|
+
});
|
|
99
|
+
const hashtags = hashtagsFor([sources.tags, sources.shortHashtags], Math.min(choice.hashtags, limits.hashtags));
|
|
100
|
+
const fitted = fitText({ body, link, hashtags }, limits, site);
|
|
101
|
+
if (fitted.notice !== undefined)
|
|
102
|
+
warnings.push({ code: "text-cut", sentence: fitted.notice });
|
|
103
|
+
const edit = record?.textEdit ?? null;
|
|
104
|
+
const text = edit ?? fitted.text;
|
|
105
|
+
const used = countFor(text, limits.unit);
|
|
106
|
+
if (edit !== null && used > limits.text)
|
|
107
|
+
warnings.push({
|
|
108
|
+
code: "text-too-long",
|
|
109
|
+
sentence: `The text is ${String(used)} characters; ${site} takes ${String(limits.text)}. Shorten it in the post's text box, or press Reset to go back to the written text.`,
|
|
110
|
+
});
|
|
111
|
+
const main = media.find((one) => one.kind === "short" || one.kind === "video");
|
|
112
|
+
const shortNumber = main?.kind === "short" ? (choice.media.short ?? 1) : undefined;
|
|
113
|
+
const item = shortNumber === undefined
|
|
114
|
+
? sources.youtube.items[0]
|
|
115
|
+
: sources.youtube.items.find((one) => one.kind === "short" && one.short === shortNumber);
|
|
116
|
+
const release = scheduleFor(platform, item?.scheduleAt ?? null, now);
|
|
117
|
+
warnings.push(...release.warnings);
|
|
118
|
+
const reddit = platform === "reddit"
|
|
119
|
+
? (record?.reddit ?? defaultReddit(sources.title.slice(0, 300)))
|
|
120
|
+
: undefined;
|
|
121
|
+
if (reddit !== undefined)
|
|
122
|
+
warnings.push(...redditWarnings(reddit));
|
|
123
|
+
return {
|
|
124
|
+
projectId: sources.projectId,
|
|
125
|
+
projectTitle: sources.title,
|
|
126
|
+
platform,
|
|
127
|
+
item: 0,
|
|
128
|
+
choice,
|
|
129
|
+
chosenHere: picked.chosenHere,
|
|
130
|
+
text,
|
|
131
|
+
generated: fitted.text,
|
|
132
|
+
edited: edit !== null,
|
|
133
|
+
count: { used, max: limits.text, unit: limits.unit },
|
|
134
|
+
hashtags,
|
|
135
|
+
link,
|
|
136
|
+
media,
|
|
137
|
+
aiLabel: limits.aiLabel && media.length > 0,
|
|
138
|
+
schedule: release.schedule,
|
|
139
|
+
...(reddit === undefined ? {} : { reddit }),
|
|
140
|
+
...(platform === "x" ? { xTier: tier } : {}),
|
|
141
|
+
composeUrl: reddit === undefined
|
|
142
|
+
? composeUrls[platform]
|
|
143
|
+
: redditSubmitUrl({
|
|
144
|
+
subreddit: reddit.subreddit,
|
|
145
|
+
title: reddit.title,
|
|
146
|
+
...(reddit.kind === "text" ? { text } : {}),
|
|
147
|
+
...(reddit.kind === "link" && sources.youtubeLink !== null
|
|
148
|
+
? { url: sources.youtubeLink }
|
|
149
|
+
: {}),
|
|
150
|
+
}),
|
|
151
|
+
warnings,
|
|
152
|
+
status: {
|
|
153
|
+
state: record?.state ?? "none",
|
|
154
|
+
url: record?.url ?? null,
|
|
155
|
+
message: record?.message ?? null,
|
|
156
|
+
at: record === undefined
|
|
157
|
+
? null
|
|
158
|
+
: (record.postedAt ?? record.scheduledAt ?? record.filledAt ?? record.waitingAt),
|
|
159
|
+
by: record?.by ?? null,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function missingText(choice, site) {
|
|
164
|
+
switch (choice.text) {
|
|
165
|
+
case "prompt":
|
|
166
|
+
return `The ${site} post isn't written yet. It is written by the post prompt after the video's description; let the run finish, or use Continue the run on the project page.`;
|
|
167
|
+
case "description":
|
|
168
|
+
case "summary":
|
|
169
|
+
return `There is no description to post yet. Let the Video stage write the YouTube description, or pick another text source for ${site}.`;
|
|
170
|
+
case "custom":
|
|
171
|
+
return `The post's own text is empty. Type it in the post's settings, or pick another text source for ${site}.`;
|
|
172
|
+
case "title":
|
|
173
|
+
case "none":
|
|
174
|
+
return `The post has no text. Pick a text source for ${site}.`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The request that writes a post for another site with a Library prompt of kind "post" (or the
|
|
2
|
+
// built-in one), and the check its answer must pass. Pure.
|
|
3
|
+
import { limitsFor } from "./limits.js";
|
|
4
|
+
import { platformLabels } from "./model.js";
|
|
5
|
+
import { countFor } from "./text.js";
|
|
6
|
+
// Changing it changes every post written with the built-in prompt, so it ships with a new
|
|
7
|
+
// recipe operation version.
|
|
8
|
+
export const defaultPostPrompt = "Write one post that makes people want to watch the video. Use the article's own facts, open with its most striking one, and write the way people write on this site.";
|
|
9
|
+
// A post written by a prompt is held to the site's limit, and never longer than this: a post,
|
|
10
|
+
// not an article.
|
|
11
|
+
const writtenMax = 3000;
|
|
12
|
+
// The limit a written post must keep: the site's free limit (X Premium's longer posts are the
|
|
13
|
+
// person's to write), at most `writtenMax`.
|
|
14
|
+
export function postTarget(platform) {
|
|
15
|
+
const limits = limitsFor(platform);
|
|
16
|
+
return { platform, max: Math.min(limits.text, writtenMax), unit: limits.unit };
|
|
17
|
+
}
|
|
18
|
+
export function postMessages(input) {
|
|
19
|
+
const site = platformLabels[input.target.platform];
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
role: "system",
|
|
23
|
+
content: [
|
|
24
|
+
`You write one ${site} post about a narrated video, from its article.`,
|
|
25
|
+
input.instruction.trim() === "" ? defaultPostPrompt : input.instruction.trim(),
|
|
26
|
+
`Keep it within ${String(input.target.max)} characters as ${site} counts them.`,
|
|
27
|
+
"Leave out hashtags and links: they are added after you.",
|
|
28
|
+
"Answer with the post's text and nothing else.",
|
|
29
|
+
].join("\n\n"),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
role: "user",
|
|
33
|
+
content: [`Video: ${input.title}`, `Article:\n\n${input.article}`].join("\n\n"),
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
// The post as it is kept: without quotes around it.
|
|
38
|
+
export function postText(answer) {
|
|
39
|
+
const trimmed = answer.trim();
|
|
40
|
+
const quoted = /^(["“'])([\s\S]*)(["”'])$/u.exec(trimmed);
|
|
41
|
+
return (quoted?.[2] ?? trimmed).trim();
|
|
42
|
+
}
|
|
43
|
+
// Why an answer can't be kept, or undefined when it can. A failed check is a failed attempt,
|
|
44
|
+
// which the provider wrapper asks again while attempts remain.
|
|
45
|
+
export function checkPost(answer, target) {
|
|
46
|
+
const site = platformLabels[target.platform];
|
|
47
|
+
const text = postText(answer);
|
|
48
|
+
const fix = "Use Try again; if it keeps happening, shorten what the post prompt asks for in Library → Prompts → Post, or choose another model in Edit project → Providers.";
|
|
49
|
+
if (text === "")
|
|
50
|
+
return `The AI model sent back an empty ${site} post. ${fix}`;
|
|
51
|
+
const used = countFor(text, target.unit);
|
|
52
|
+
if (used > target.max)
|
|
53
|
+
return `The AI model's ${site} post is ${String(used)} characters; it may be at most ${String(target.max)}. ${fix}`;
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { platformLabels } from "./model.js";
|
|
2
|
+
import { postsIn, writeProgress } from "./repo.js";
|
|
3
|
+
// A post waits for the extension at most this long after Fill in <site> was pressed, unless
|
|
4
|
+
// its site has no scheduler and it waits for its release time (`due.ts`).
|
|
5
|
+
export const waitingMs = 24 * 60 * 60 * 1000;
|
|
6
|
+
// Posts left waiting longer than a day stop waiting, with the sentence that says why.
|
|
7
|
+
export function expireWaiting(db, now, waitsForRelease) {
|
|
8
|
+
for (const post of postsIn(db, ["waiting"])) {
|
|
9
|
+
if (waitsForRelease(post))
|
|
10
|
+
continue;
|
|
11
|
+
const age = now.getTime() - Date.parse(post.waitingAt ?? "");
|
|
12
|
+
if (Number.isFinite(age) && age <= waitingMs)
|
|
13
|
+
continue;
|
|
14
|
+
const site = platformLabels[post.platform];
|
|
15
|
+
writeProgress(db, post, {
|
|
16
|
+
...post,
|
|
17
|
+
state: "failed",
|
|
18
|
+
message: `Fill in ${site} waited a day without the Slopify Studio extension taking it. Open ${site} in the browser where the extension is paired and allowed to post there, then press Fill in ${site} again.`,
|
|
19
|
+
}, now);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// The oldest waiting post for a site that can be filled now (of `projectId` when the tab Slopify
|
|
23
|
+
// opened names one); it becomes "filling" so another tab of the site doesn't take it too.
|
|
24
|
+
export function takeNext(db, platform, now, readyNow, projectId) {
|
|
25
|
+
const waiting = postsIn(db, ["waiting"], platform).filter((post) => (projectId === undefined || post.projectId === projectId) && readyNow(post));
|
|
26
|
+
const [first] = waiting;
|
|
27
|
+
if (first === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
writeProgress(db, first, { ...first, state: "filling", message: null }, now);
|
|
30
|
+
return { post: first, waiting: waiting.length - 1 };
|
|
31
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { postStates, socialPlatforms, } from "./model.js";
|
|
2
|
+
import { projectPostChoiceSchema, redditFieldsSchema } from "./schema.js";
|
|
3
|
+
import { noProgress } from "./state.js";
|
|
4
|
+
const columns = "project_id,platform,item,choice_json,text_edit,text_base,reddit_json,state,waiting_at,filled_at,scheduled_at,posted_at,url,message,by,updated_at";
|
|
5
|
+
const text = (value) => (typeof value === "string" ? value : null);
|
|
6
|
+
function json(value, parse) {
|
|
7
|
+
if (typeof value !== "string")
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return parse(JSON.parse(value)) ?? null;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function toRecord(row) {
|
|
17
|
+
const platform = socialPlatforms.find((one) => one === row.platform);
|
|
18
|
+
const state = postStates.find((one) => one === row.state) ?? "none";
|
|
19
|
+
if (platform === undefined || typeof row.project_id !== "string")
|
|
20
|
+
return undefined;
|
|
21
|
+
const by = row.by === "extension" || row.by === "person" ? row.by : null;
|
|
22
|
+
return {
|
|
23
|
+
projectId: row.project_id,
|
|
24
|
+
platform,
|
|
25
|
+
item: typeof row.item === "number" ? row.item : 0,
|
|
26
|
+
choice: json(row.choice_json, (raw) => {
|
|
27
|
+
const parsed = projectPostChoiceSchema.safeParse(raw);
|
|
28
|
+
return parsed.success ? parsed.data : undefined;
|
|
29
|
+
}),
|
|
30
|
+
textEdit: text(row.text_edit),
|
|
31
|
+
textBase: text(row.text_base),
|
|
32
|
+
reddit: json(row.reddit_json, (raw) => {
|
|
33
|
+
const parsed = redditFieldsSchema.safeParse(raw);
|
|
34
|
+
return parsed.success ? parsed.data : undefined;
|
|
35
|
+
}),
|
|
36
|
+
state,
|
|
37
|
+
waitingAt: text(row.waiting_at),
|
|
38
|
+
filledAt: text(row.filled_at),
|
|
39
|
+
scheduledAt: text(row.scheduled_at),
|
|
40
|
+
postedAt: text(row.posted_at),
|
|
41
|
+
url: text(row.url),
|
|
42
|
+
message: text(row.message),
|
|
43
|
+
by,
|
|
44
|
+
updatedAt: text(row.updated_at),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function blank(key) {
|
|
48
|
+
return {
|
|
49
|
+
...noProgress,
|
|
50
|
+
projectId: key.projectId,
|
|
51
|
+
platform: key.platform,
|
|
52
|
+
item: key.item ?? 0,
|
|
53
|
+
choice: null,
|
|
54
|
+
textEdit: null,
|
|
55
|
+
textBase: null,
|
|
56
|
+
reddit: null,
|
|
57
|
+
updatedAt: null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function readPost(db, key) {
|
|
61
|
+
const row = db
|
|
62
|
+
.prepare(`SELECT ${columns} FROM social_posts WHERE project_id=? AND platform=? AND item=?`)
|
|
63
|
+
.get(key.projectId, key.platform, key.item ?? 0);
|
|
64
|
+
return (row === undefined ? undefined : toRecord(row)) ?? blank(key);
|
|
65
|
+
}
|
|
66
|
+
export function postsOf(db, projectId) {
|
|
67
|
+
return db
|
|
68
|
+
.prepare(`SELECT ${columns} FROM social_posts WHERE project_id=? ORDER BY platform, item`)
|
|
69
|
+
.all(projectId)
|
|
70
|
+
.flatMap((row) => toRecord(row) ?? []);
|
|
71
|
+
}
|
|
72
|
+
// Every post in one of these states, oldest first (by when it was asked to fill).
|
|
73
|
+
export function postsIn(db, states, platform) {
|
|
74
|
+
const marks = states.map(() => "?").join(",");
|
|
75
|
+
const params = [...states];
|
|
76
|
+
let where = `state IN (${marks})`;
|
|
77
|
+
if (platform !== undefined) {
|
|
78
|
+
where += " AND platform=?";
|
|
79
|
+
params.push(platform);
|
|
80
|
+
}
|
|
81
|
+
return db
|
|
82
|
+
.prepare(`SELECT ${columns} FROM social_posts WHERE ${where} ORDER BY coalesce(waiting_at, updated_at), rowid`)
|
|
83
|
+
.all(...params)
|
|
84
|
+
.flatMap((row) => toRecord(row) ?? []);
|
|
85
|
+
}
|
|
86
|
+
const writable = [
|
|
87
|
+
"choice_json",
|
|
88
|
+
"text_edit",
|
|
89
|
+
"text_base",
|
|
90
|
+
"reddit_json",
|
|
91
|
+
"state",
|
|
92
|
+
"waiting_at",
|
|
93
|
+
"filled_at",
|
|
94
|
+
"scheduled_at",
|
|
95
|
+
"posted_at",
|
|
96
|
+
"url",
|
|
97
|
+
"message",
|
|
98
|
+
"by",
|
|
99
|
+
];
|
|
100
|
+
function upsert(db, key, fields, now) {
|
|
101
|
+
const names = writable.filter((name) => Object.hasOwn(fields, name));
|
|
102
|
+
const values = names.map((name) => fields[name] ?? null);
|
|
103
|
+
const updates = [
|
|
104
|
+
...names.map((name) => `${name}=excluded.${name}`),
|
|
105
|
+
"updated_at=excluded.updated_at",
|
|
106
|
+
];
|
|
107
|
+
db.prepare(`INSERT INTO social_posts (project_id,platform,item,${[...names, "updated_at"].join(",")})
|
|
108
|
+
VALUES (?,?,?,${[...names, "updated_at"].map(() => "?").join(",")})
|
|
109
|
+
ON CONFLICT(project_id,platform,item) DO UPDATE SET ${updates.join(",")}`).run(key.projectId, key.platform, key.item ?? 0, ...values, now.toISOString());
|
|
110
|
+
}
|
|
111
|
+
export function writeChoice(db, key, choice, now) {
|
|
112
|
+
upsert(db, key, { choice_json: choice === null ? null : JSON.stringify(choice) }, now);
|
|
113
|
+
}
|
|
114
|
+
// The person's text and the generated text it was edited from; null goes back to the
|
|
115
|
+
// generated text.
|
|
116
|
+
export function writeTextEdit(db, key, edit, now) {
|
|
117
|
+
upsert(db, key, { text_edit: edit?.text ?? null, text_base: edit?.base ?? null }, now);
|
|
118
|
+
}
|
|
119
|
+
export function writeReddit(db, key, fields, now) {
|
|
120
|
+
upsert(db, key, { reddit_json: JSON.stringify(fields) }, now);
|
|
121
|
+
}
|
|
122
|
+
export function writeProgress(db, key, progress, now) {
|
|
123
|
+
upsert(db, key, {
|
|
124
|
+
state: progress.state,
|
|
125
|
+
waiting_at: progress.waitingAt,
|
|
126
|
+
filled_at: progress.filledAt,
|
|
127
|
+
scheduled_at: progress.scheduledAt,
|
|
128
|
+
posted_at: progress.postedAt,
|
|
129
|
+
url: progress.url,
|
|
130
|
+
message: progress.message,
|
|
131
|
+
by: progress.by,
|
|
132
|
+
}, now);
|
|
133
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { coverCrop } from "../designs/frames.js";
|
|
5
|
+
import { pictureSize } from "../designs/picture-size.js";
|
|
6
|
+
// Pictures cut to a site's shape (Instagram's 4:5), made the first time a post asks for them
|
|
7
|
+
// and kept: free, local, in a child ffmpeg off the server's thread. Never a video: re-encoding
|
|
8
|
+
// a whole video would take as long as rendering it, so a video keeps its shape and the pack
|
|
9
|
+
// warns instead.
|
|
10
|
+
// Raised when the cut changes, so every kept copy is made again.
|
|
11
|
+
const reshapeVersion = 1;
|
|
12
|
+
const frames = {
|
|
13
|
+
"1:1": { width: 1080, height: 1080 },
|
|
14
|
+
"4:5": { width: 1080, height: 1350 },
|
|
15
|
+
"9:16": { width: 1080, height: 1920 },
|
|
16
|
+
"16:9": { width: 1920, height: 1080 },
|
|
17
|
+
};
|
|
18
|
+
export const socialDir = (paths, projectId) => join(paths.dataDir, "social", projectId);
|
|
19
|
+
// The ffmpeg arguments: scaled to cover the frame, cut around the centre, saved as a JPEG. A
|
|
20
|
+
// picture whose size can't be read is cut by ffmpeg's own cover fit, which centres the same way.
|
|
21
|
+
export function reshapeArgs(input) {
|
|
22
|
+
const frame = frames[input.shape];
|
|
23
|
+
const size = `${String(frame.width)}:${String(frame.height)}`;
|
|
24
|
+
const chain = input.size === undefined
|
|
25
|
+
? [`scale=${size}:force_original_aspect_ratio=increase:flags=lanczos`, `crop=${size}`]
|
|
26
|
+
: ((crop) => [
|
|
27
|
+
`scale=${String(crop.scaleWidth)}:${String(crop.scaleHeight)}:flags=lanczos`,
|
|
28
|
+
`crop=${size}:${String(crop.x)}:${String(crop.y)}`,
|
|
29
|
+
])(coverCrop(input.size, frame, { x: 0.5, y: 0.5 }));
|
|
30
|
+
return [
|
|
31
|
+
...["-hide_banner", "-nostdin", "-loglevel", "error", "-y", "-i", input.source],
|
|
32
|
+
...["-vf", [...chain, "format=yuvj420p"].join(","), "-frames:v", "1", "-q:v", "2"],
|
|
33
|
+
input.output,
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
export function createReshaper(deps) {
|
|
37
|
+
let queue = Promise.resolve();
|
|
38
|
+
const oneAtATime = (work) => {
|
|
39
|
+
const next = queue.then(work, work);
|
|
40
|
+
queue = next.catch(() => undefined);
|
|
41
|
+
return next;
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
picture: (projectId, source, shape, signal) => oneAtATime(async () => {
|
|
45
|
+
const stat = statSync(source, { throwIfNoEntry: false });
|
|
46
|
+
if (stat === undefined || !stat.isFile())
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
sentence: "The picture isn't on disk any more. Reload Prepare upload; if it is still missing, make the project's images again.",
|
|
50
|
+
};
|
|
51
|
+
const key = createHash("sha256")
|
|
52
|
+
.update(JSON.stringify([reshapeVersion, source, stat.size, stat.mtimeMs, shape]))
|
|
53
|
+
.digest("hex")
|
|
54
|
+
.slice(0, 32);
|
|
55
|
+
const dir = socialDir(deps.paths, projectId);
|
|
56
|
+
const target = join(dir, `${key}.jpg`);
|
|
57
|
+
if (existsSync(target))
|
|
58
|
+
return { ok: true, path: target };
|
|
59
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
60
|
+
const work = mkdtempSync(join(dir, ".work-"));
|
|
61
|
+
try {
|
|
62
|
+
await deps.runFfmpeg({
|
|
63
|
+
bin: deps.ffmpeg,
|
|
64
|
+
cwd: work,
|
|
65
|
+
args: reshapeArgs({
|
|
66
|
+
source,
|
|
67
|
+
size: pictureSize(readFileSync(source)),
|
|
68
|
+
shape,
|
|
69
|
+
output: "out.jpg",
|
|
70
|
+
}),
|
|
71
|
+
signal,
|
|
72
|
+
log: deps.log,
|
|
73
|
+
onProgress: () => undefined,
|
|
74
|
+
});
|
|
75
|
+
renameSync(join(work, "out.jpg"), target);
|
|
76
|
+
return { ok: true, path: target };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
deps.log.write("warn", "social.reshape", {
|
|
80
|
+
projectId,
|
|
81
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
sentence: `The picture couldn't be cut to ${shape} because ffmpeg stopped with an error. Reload Prepare upload to try again; if it fails again, use Download diagnostics in Settings and report it.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
rmSync(work, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// A project's kept copies, removed with the project.
|
|
95
|
+
export function dropReshaped(paths, projectId) {
|
|
96
|
+
rmSync(socialDir(paths, projectId), { recursive: true, force: true });
|
|
97
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { socialPlatforms } from "./model.js";
|
|
2
|
+
// Whether any site's post is written by a post prompt: a paid call to the project's text model.
|
|
3
|
+
export function usesPostPrompt(draft) {
|
|
4
|
+
return socialPlatforms.some((platform) => draft.social?.[platform]?.text === "prompt");
|
|
5
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { postTextSources, redditPostKinds, reportedStates, socialPlatforms, xTiers, } from "./model.js";
|
|
3
|
+
export const promptNameMax = 200;
|
|
4
|
+
export const customTextMax = 5000;
|
|
5
|
+
export const postTextMax = 50_000;
|
|
6
|
+
export const hashtagsMax = 30;
|
|
7
|
+
export const picturesMax = 35;
|
|
8
|
+
export const designsMax = 10;
|
|
9
|
+
const designId = z
|
|
10
|
+
.string()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(64)
|
|
13
|
+
.regex(/^[A-Za-z0-9_-]+$/);
|
|
14
|
+
export const postMediaSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
video: z.enum(["long", "short"]).optional(),
|
|
17
|
+
short: z.number().int().min(1).max(99).optional(),
|
|
18
|
+
thumbnail: z.boolean().optional(),
|
|
19
|
+
pictures: z.number().int().min(0).max(picturesMax).optional(),
|
|
20
|
+
designs: z.array(designId).max(designsMax).optional(),
|
|
21
|
+
})
|
|
22
|
+
.strict();
|
|
23
|
+
export const postChoiceSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
text: z.enum(postTextSources),
|
|
26
|
+
prompt: z.string().max(promptNameMax).optional(),
|
|
27
|
+
custom: z.string().max(customTextMax).optional(),
|
|
28
|
+
media: postMediaSchema,
|
|
29
|
+
hashtags: z.number().int().min(0).max(hashtagsMax),
|
|
30
|
+
link: z.boolean(),
|
|
31
|
+
})
|
|
32
|
+
.strict();
|
|
33
|
+
// `config.social` (and Play's `form.social`): absent is no posting.
|
|
34
|
+
export const socialSettingsSchema = z.partialRecord(z.enum(socialPlatforms), postChoiceSchema);
|
|
35
|
+
export const projectPostChoiceSchema = z.union([
|
|
36
|
+
postChoiceSchema,
|
|
37
|
+
z.object({ off: z.literal(true) }).strict(),
|
|
38
|
+
]);
|
|
39
|
+
export const subredditMax = 21;
|
|
40
|
+
export const redditFieldsSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
subreddit: z
|
|
43
|
+
.string()
|
|
44
|
+
.max(subredditMax + 2)
|
|
45
|
+
.transform((name) => name.trim().replace(/^\/?r\//u, "")),
|
|
46
|
+
title: z.string().max(300),
|
|
47
|
+
flair: z.string().max(64),
|
|
48
|
+
kind: z.enum(redditPostKinds),
|
|
49
|
+
})
|
|
50
|
+
.strict();
|
|
51
|
+
export const platformSchema = z.enum(socialPlatforms);
|
|
52
|
+
export const xTierSchema = z.enum(xTiers);
|
|
53
|
+
// The page's bodies (`edge/http/social.ts`).
|
|
54
|
+
export const choiceBody = z.object({ choice: projectPostChoiceSchema.nullable() }).strict();
|
|
55
|
+
export const textBody = z
|
|
56
|
+
.object({ text: z.string().max(postTextMax), base: z.string().max(postTextMax) })
|
|
57
|
+
.strict();
|
|
58
|
+
export const redditBody = z.object({ reddit: redditFieldsSchema }).strict();
|
|
59
|
+
export const linkBody = z.object({ url: z.string().max(500).nullable() }).strict();
|
|
60
|
+
// The extension's report after a fill. Unknown keys are dropped, so a newer extension's
|
|
61
|
+
// report still reaches an older Slopify.
|
|
62
|
+
export const resultBody = z.object({
|
|
63
|
+
projectId: z.string().min(1).max(64),
|
|
64
|
+
platform: platformSchema,
|
|
65
|
+
item: z.number().int().min(0).max(99).optional(),
|
|
66
|
+
state: z.enum(reportedStates),
|
|
67
|
+
url: z.string().max(500).optional(),
|
|
68
|
+
message: z.string().max(2000).optional(),
|
|
69
|
+
at: z.iso.datetime().optional(),
|
|
70
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { findDownload } from "../storage/downloads.js";
|
|
3
|
+
import { parseMediaFile } from "./media.js";
|
|
4
|
+
import { postPacks } from "./pack.js";
|
|
5
|
+
const gone = "That file isn't in the project's posts any more. Reload Prepare upload and press Fill again.";
|
|
6
|
+
// One file of a project's posts, only when one of its posts lists it: the asset as it is, a
|
|
7
|
+
// picture cut to its site's shape (made now the first time), or a design drawn in its format.
|
|
8
|
+
export async function serveMedia(deps, projectId, file, now, signal) {
|
|
9
|
+
const result = postPacks(deps, projectId, now);
|
|
10
|
+
const listed = result.ok
|
|
11
|
+
? result.packs.packs.flatMap((pack) => pack.media).find((one) => one.file === file)
|
|
12
|
+
: undefined;
|
|
13
|
+
const ref = parseMediaFile(file);
|
|
14
|
+
if (listed === undefined || ref === undefined)
|
|
15
|
+
return { ok: false, status: 404, sentence: gone };
|
|
16
|
+
const done = (path) => {
|
|
17
|
+
const stat = statSync(path, { throwIfNoEntry: false });
|
|
18
|
+
return stat?.isFile() === true
|
|
19
|
+
? {
|
|
20
|
+
ok: true,
|
|
21
|
+
path,
|
|
22
|
+
bytes: stat.size,
|
|
23
|
+
contentType: listed.contentType,
|
|
24
|
+
filename: listed.filename,
|
|
25
|
+
}
|
|
26
|
+
: { ok: false, status: 404, sentence: gone };
|
|
27
|
+
};
|
|
28
|
+
if (ref.kind === "design") {
|
|
29
|
+
if (deps.renderDesign === undefined)
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
status: 503,
|
|
33
|
+
sentence: "Designs can't be drawn in this copy of Slopify. Restart Slopify; if it stays, use Download diagnostics in Settings and report it.",
|
|
34
|
+
};
|
|
35
|
+
const drawn = await deps.renderDesign(ref.id, ref.format, signal);
|
|
36
|
+
return drawn.ok ? done(drawn.path) : { ok: false, status: 404, sentence: drawn.sentence };
|
|
37
|
+
}
|
|
38
|
+
const found = findDownload(deps, projectId, ref.asset);
|
|
39
|
+
if (!found.ok)
|
|
40
|
+
return { ok: false, status: 404, sentence: gone };
|
|
41
|
+
if (ref.shape === "original")
|
|
42
|
+
return done(found.download.path);
|
|
43
|
+
if (deps.reshaper === undefined)
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
status: 503,
|
|
47
|
+
sentence: "Pictures can't be cut to the site's shape in this copy of Slopify. Restart Slopify; if it stays, use Download diagnostics in Settings and report it.",
|
|
48
|
+
};
|
|
49
|
+
const cut = await deps.reshaper.picture(projectId, found.download.path, ref.shape, signal);
|
|
50
|
+
return cut.ok ? done(cut.path) : { ok: false, status: 503, sentence: cut.sentence };
|
|
51
|
+
}
|