@gentbajko/slopify 2.5.0 → 3.0.1
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/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +24 -41
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +31 -0
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +44 -5
- package/dist/adapters/host-cli/transport.js +7 -5
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +1 -1
- package/dist/adapters/image/codex.js +87 -22
- package/dist/adapters/image/fal.js +13 -5
- package/dist/adapters/image/google.js +11 -8
- package/dist/adapters/image/openai.js +14 -7
- package/dist/adapters/image/reference.js +34 -0
- package/dist/adapters/image/replicate.js +7 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +85 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +12 -7
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/gemini.js +206 -0
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/pcm-mp3.js +62 -0
- package/dist/adapters/tts/system.js +147 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +38 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/assets/style-preview/narration.mp3 +0 -0
- package/dist/assets/style-preview/tall-1.jpg +0 -0
- package/dist/assets/style-preview/tall-2.jpg +0 -0
- package/dist/assets/style-preview/tall-3.jpg +0 -0
- package/dist/assets/style-preview/wide-1.jpg +0 -0
- package/dist/assets/style-preview/wide-2.jpg +0 -0
- package/dist/assets/style-preview/wide-3.jpg +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +17 -14
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +2 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +118 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +72 -0
- package/dist/edge/autostart/service.js +90 -0
- package/dist/edge/cli-args.js +71 -0
- package/dist/edge/cli.js +64 -63
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +160 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +3 -1
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +38 -11
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/byte-range.js +42 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/diagnostics.js +1 -0
- package/dist/edge/http/draft-files.js +2 -4
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +11 -7
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +29 -5
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +212 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/project-templates.js +19 -2
- package/dist/edge/http/projects.js +37 -100
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +119 -3
- package/dist/edge/http/reviews.js +131 -0
- package/dist/edge/http/revision-files.js +3 -5
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +115 -6
- package/dist/edge/http/settings.js +84 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +43 -2
- package/dist/edge/http/studio.js +287 -0
- package/dist/edge/http/style-preview.js +61 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/tutorials.js +59 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +92 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/gemini-login.js +83 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +9 -1
- package/dist/host-cli/status.js +4 -2
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/db/migrations/0041-cast-hosts.sql +3 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +122 -14
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/model.js +10 -5
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/ports/system-speech.js +203 -0
- package/dist/kernel/runner/attempt.js +17 -8
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +40 -5
- package/dist/kernel/runner/index.js +39 -9
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +142 -7
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +277 -16
- package/dist/model-catalog.js +2 -0
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/3.0.1.md +105 -0
- package/dist/patch-notes/index.json +32 -0
- package/dist/slices/admission/model.js +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +80 -2
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +42 -6
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/folder.js +14 -6
- package/dist/slices/batch/index.js +7 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +221 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +152 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +5 -2
- package/dist/slices/control/providers.js +4 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +109 -28
- package/dist/slices/estimate/requests.js +68 -2
- package/dist/slices/eta/history.js +56 -0
- package/dist/slices/eta/model.js +63 -0
- package/dist/slices/eta/view.js +25 -0
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +7 -1
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +185 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +47 -3
- package/dist/slices/notifications/rules.js +48 -3
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +72 -0
- package/dist/slices/onboarding/full-video.js +81 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +85 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +144 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +127 -8
- package/dist/slices/play-drafts/readiness.js +4 -1
- package/dist/slices/play-drafts/repo.js +2 -0
- package/dist/slices/play-drafts/review-inputs.js +20 -3
- package/dist/slices/play-drafts/schema.js +89 -0
- package/dist/slices/play-drafts/service.js +4 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/project-templates/from-project.js +59 -6
- package/dist/slices/project-templates/next-chapter.js +94 -0
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/schema.js +7 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +18 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +46 -3
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +8 -4
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +67 -8
- package/dist/slices/rebuild/recipe-input-schema.js +35 -0
- package/dist/slices/rebuild/recipe-legacy.js +9 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +15 -1
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +23 -3
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +14 -2
- package/dist/slices/rebuild/recipe-visual.js +96 -33
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +125 -0
- package/dist/slices/rebuild/runtime-actions.js +5 -0
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +21 -2
- package/dist/slices/rebuild/runtime-local.js +148 -24
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +27 -0
- package/dist/slices/rebuild/runtime-provider.js +126 -18
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +267 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +44 -17
- package/dist/slices/rebuild/runtime-store.js +62 -11
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +3 -3
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +9 -2
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +118 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +5 -2
- package/dist/slices/revisions/adopt.js +3 -1
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +10 -0
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +11 -1
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +256 -0
- package/dist/slices/run-cost/pricing.js +95 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +222 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +68 -17
- package/dist/slices/schedules/schema.js +179 -1
- package/dist/slices/schedules/service.js +43 -4
- package/dist/slices/schedules/similar.js +128 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +523 -0
- package/dist/slices/settings/first-run.js +106 -0
- package/dist/slices/settings/health.js +247 -0
- package/dist/slices/settings/key-guides.js +138 -0
- package/dist/slices/settings/key-test.js +93 -0
- package/dist/slices/settings/keys.js +10 -5
- package/dist/slices/settings/model-reach.js +52 -0
- package/dist/slices/settings/model.js +55 -1
- package/dist/slices/settings/models.js +5 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/readiness.js +14 -2
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/tutorial-schema.js +5 -0
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +63 -13
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +87 -0
- package/dist/slices/storage/downloads.js +33 -1
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +14 -4
- package/dist/slices/storage/model.js +11 -0
- package/dist/slices/storage/portable.js +94 -12
- package/dist/slices/storage/reconcile.js +14 -1
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +30 -0
- package/dist/slices/studio/pack.js +211 -0
- package/dist/slices/studio/queue.js +69 -0
- package/dist/slices/studio/settings.js +170 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/narration.js +33 -0
- package/dist/slices/style-preview/render.js +148 -0
- package/dist/slices/style-preview/schema.js +59 -0
- package/dist/slices/style-preview/service.js +92 -0
- package/dist/slices/style-preview/settings.js +139 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/tutorials/anchors.js +57 -0
- package/dist/slices/tutorials/library.js +185 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +87 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +74 -0
- package/dist/slices/voices/grouping.js +59 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +218 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +84 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/tutorials/AI-CLIs.md +146 -0
- package/dist/tutorials/Backups.md +121 -0
- package/dist/tutorials/Calendar.md +103 -0
- package/dist/tutorials/Cast-Library.md +112 -0
- package/dist/tutorials/Channels.md +148 -0
- package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
- package/dist/tutorials/Docker.md +171 -0
- package/dist/tutorials/Document-Themes.md +207 -0
- package/dist/tutorials/Editing-a-Project.md +204 -0
- package/dist/tutorials/FAQ.md +134 -0
- package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
- package/dist/tutorials/Home-and-Projects.md +174 -0
- package/dist/tutorials/Home.md +119 -0
- package/dist/tutorials/Install.md +154 -0
- package/dist/tutorials/Intros-and-Outros.md +86 -0
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
- package/dist/tutorials/Library-Overview.md +75 -0
- package/dist/tutorials/Models.md +133 -0
- package/dist/tutorials/Multiple-Voices.md +161 -0
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
- package/dist/tutorials/Notifications.md +86 -0
- package/dist/tutorials/Other-Languages.md +110 -0
- package/dist/tutorials/PDF-Documents.md +100 -0
- package/dist/tutorials/Play-Images.md +135 -0
- package/dist/tutorials/Play-Narration.md +167 -0
- package/dist/tutorials/Play-Outputs.md +109 -0
- package/dist/tutorials/Play-Overview.md +216 -0
- package/dist/tutorials/Play-Title-and-Article.md +135 -0
- package/dist/tutorials/Play-Video-and-Style.md +143 -0
- package/dist/tutorials/Project-Page.md +228 -0
- package/dist/tutorials/Prompts.md +136 -0
- package/dist/tutorials/Providers-and-Keys.md +290 -0
- package/dist/tutorials/Publishing-to-YouTube.md +135 -0
- package/dist/tutorials/Recovery-and-Retries.md +141 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
- package/dist/tutorials/Schedules.md +154 -0
- package/dist/tutorials/Settings-Reference.md +258 -0
- package/dist/tutorials/Shorts.md +116 -0
- package/dist/tutorials/Start-at-Login.md +126 -0
- package/dist/tutorials/Studio-Extension.md +146 -0
- package/dist/tutorials/Templates.md +146 -0
- package/dist/tutorials/Trash.md +87 -0
- package/dist/tutorials/Troubleshooting.md +149 -0
- package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
- package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
- package/dist/tutorials/Video-Editing.md +135 -0
- package/dist/tutorials/Where-Your-Files-Live.md +149 -0
- package/dist/tutorials/YouTube-Description.md +132 -0
- package/dist/tutorials/Your-First-Short.md +125 -0
- package/dist/tutorials/_Footer.md +1 -0
- package/dist/tutorials/_Sidebar.md +69 -0
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-DBLSfqA-.css +1 -0
- package/dist/web/assets/index-DFdIhmm0.js +307 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -5
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-CktpxlJd.js +0 -171
- package/dist/web/assets/index-DB-vOnGU.css +0 -1
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -9,21 +9,32 @@ function atoms(source, from, to, spans) {
|
|
|
9
9
|
const span = spans.get(at);
|
|
10
10
|
if (span !== undefined) {
|
|
11
11
|
if (span.end > to || span.end <= at)
|
|
12
|
-
throw new Error("Slopify hit an internal error (a pronunciation fix spans more than one word).
|
|
12
|
+
throw new Error("Slopify hit an internal error (a pronunciation fix spans more than one word). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
13
13
|
result.push({ text: span.text, spokenText: source.slice(at, span.end) });
|
|
14
14
|
at = span.end;
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
17
|
const point = source.codePointAt(at);
|
|
18
18
|
if (point === undefined)
|
|
19
|
-
throw new Error("Slopify hit an internal error (a narration position is outside the article text).
|
|
19
|
+
throw new Error("Slopify hit an internal error (a narration position is outside the article text). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
20
20
|
const text = String.fromCodePoint(point);
|
|
21
|
-
|
|
21
|
+
// An empty span is a later word of a multi-word alias (its spoken form sits on the first
|
|
22
|
+
// word), so the whitespace in front of it would leave two spaces where one word was.
|
|
23
|
+
// It stays in the spoken (transcript) text, which the captions are aligned against.
|
|
24
|
+
result.push({ text: silencedBefore(source, at, spans) ? "" : text, spokenText: text });
|
|
22
25
|
at += text.length;
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
return result;
|
|
26
29
|
}
|
|
30
|
+
function silencedBefore(source, at, spans) {
|
|
31
|
+
if (!/\s/u.test(source[at] ?? ""))
|
|
32
|
+
return false;
|
|
33
|
+
let next = at;
|
|
34
|
+
while (next < source.length && /\s/u.test(source[next] ?? ""))
|
|
35
|
+
next += 1;
|
|
36
|
+
return spans.get(next)?.text === "";
|
|
37
|
+
}
|
|
27
38
|
function checkedSpans(source, pronunciation) {
|
|
28
39
|
const spans = new Map();
|
|
29
40
|
let end = 0;
|
|
@@ -34,7 +45,7 @@ function checkedSpans(source, pronunciation) {
|
|
|
34
45
|
span.end <= span.start ||
|
|
35
46
|
span.end > source.length ||
|
|
36
47
|
!/^[^\s\uD800-\uDFFF]+$/u.test(source.slice(span.start, span.end)))
|
|
37
|
-
throw new Error("Slopify hit an internal error (pronunciation fixes overlap or split a word).
|
|
48
|
+
throw new Error("Slopify hit an internal error (pronunciation fixes overlap or split a word). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
38
49
|
spans.set(span.start, span);
|
|
39
50
|
end = span.end;
|
|
40
51
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { redact } from "../../kernel/log.js";
|
|
2
|
-
import { noticeOf, noticeText, webhookBody } from "./rules.js";
|
|
2
|
+
import { noticeOf, noticeText, reviewNoticeText, topicsNoticeText, webhookBody } from "./rules.js";
|
|
3
3
|
import { sendFailureText } from "./send.js";
|
|
4
4
|
export function createRunNotifier(deps) {
|
|
5
5
|
// ceiling: one entry per project seen since boot, a few dozen bytes each.
|
|
@@ -15,19 +15,44 @@ export function createRunNotifier(deps) {
|
|
|
15
15
|
if (subject === undefined)
|
|
16
16
|
return;
|
|
17
17
|
const text = noticeText(kind, { ...subject, reason: reasons.get(projectId) });
|
|
18
|
+
post(url, { ...text, link: deps.link?.(projectId) }, kind, { projectId });
|
|
19
|
+
};
|
|
20
|
+
// Once per verdict: a review asked again for the same item replaces its verdict in place.
|
|
21
|
+
// ceiling: one id per flagged verdict since boot.
|
|
22
|
+
const flagged = new Set();
|
|
23
|
+
const notifyReview = (event) => {
|
|
24
|
+
if (flagged.has(event.verdictId))
|
|
25
|
+
return;
|
|
26
|
+
flagged.add(event.verdictId);
|
|
27
|
+
const url = deps.url();
|
|
28
|
+
if (url === null)
|
|
29
|
+
return;
|
|
30
|
+
const subject = deps.subject(event.projectId);
|
|
31
|
+
if (subject === undefined)
|
|
32
|
+
return;
|
|
33
|
+
const text = reviewNoticeText({
|
|
34
|
+
title: subject.title,
|
|
35
|
+
stage: event.stage,
|
|
36
|
+
reason: event.reason,
|
|
37
|
+
});
|
|
38
|
+
post(url, { ...text, link: deps.link?.(event.projectId) }, "review", {
|
|
39
|
+
projectId: event.projectId,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const post = (url, text, kind, about) => {
|
|
18
43
|
const body = redact(webhookBody(text));
|
|
19
44
|
const sending = deps
|
|
20
45
|
.send(url, body)
|
|
21
46
|
.then((result) => {
|
|
22
47
|
if (!result.ok)
|
|
23
48
|
deps.log.write("warn", "notification.failed", {
|
|
24
|
-
|
|
49
|
+
...about,
|
|
25
50
|
detail: `${kind} notification not delivered: ${sendFailureText(result)}`,
|
|
26
51
|
});
|
|
27
52
|
})
|
|
28
53
|
.catch((error) => {
|
|
29
54
|
deps.log.write("warn", "notification.failed", {
|
|
30
|
-
|
|
55
|
+
...about,
|
|
31
56
|
detail: `${kind} notification not delivered: ${error instanceof Error ? error.message : String(error)}`,
|
|
32
57
|
});
|
|
33
58
|
})
|
|
@@ -48,6 +73,10 @@ export function createRunNotifier(deps) {
|
|
|
48
73
|
reasons.delete(event.projectId);
|
|
49
74
|
return;
|
|
50
75
|
}
|
|
76
|
+
if (event.type === "review.flagged") {
|
|
77
|
+
notifyReview(event);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
51
80
|
if (event.type !== "project.state")
|
|
52
81
|
return;
|
|
53
82
|
const previous = states.get(event.projectId);
|
|
@@ -65,6 +94,21 @@ export function createRunNotifier(deps) {
|
|
|
65
94
|
});
|
|
66
95
|
}
|
|
67
96
|
},
|
|
97
|
+
observeTopics: (event) => {
|
|
98
|
+
if (closed || event.added === 0)
|
|
99
|
+
return;
|
|
100
|
+
try {
|
|
101
|
+
const url = deps.url();
|
|
102
|
+
if (url === null)
|
|
103
|
+
return;
|
|
104
|
+
post(url, topicsNoticeText(event), "topics", {});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
deps.log.write("warn", "notification.failed", {
|
|
108
|
+
detail: `topics notification skipped: ${error instanceof Error ? error.message : String(error)}`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
},
|
|
68
112
|
close: () => {
|
|
69
113
|
closed = true;
|
|
70
114
|
},
|
|
@@ -8,6 +8,8 @@ export function noticeOf(previous, next) {
|
|
|
8
8
|
switch (next) {
|
|
9
9
|
case "done":
|
|
10
10
|
return "ready";
|
|
11
|
+
case "partial":
|
|
12
|
+
return "partial";
|
|
11
13
|
case "failed":
|
|
12
14
|
return "failed";
|
|
13
15
|
case "pending":
|
|
@@ -16,6 +18,23 @@ export function noticeOf(previous, next) {
|
|
|
16
18
|
return undefined;
|
|
17
19
|
}
|
|
18
20
|
}
|
|
21
|
+
const reviewItemNames = {
|
|
22
|
+
article: "the article",
|
|
23
|
+
images: "an image",
|
|
24
|
+
narration: "the narration",
|
|
25
|
+
thumbnail: "the thumbnail",
|
|
26
|
+
shorts: "a short",
|
|
27
|
+
};
|
|
28
|
+
export function reviewNoticeText(subject) {
|
|
29
|
+
const title = subject.title.trim() === "" ? "Untitled project" : subject.title.trim();
|
|
30
|
+
const reason = shortReason(subject.reason);
|
|
31
|
+
const item = reviewItemNames[subject.stage] ?? "an item";
|
|
32
|
+
const headline = `Review needs a decision: ${title}`;
|
|
33
|
+
return {
|
|
34
|
+
headline: reason === undefined ? headline : `${headline} — ${reason}`,
|
|
35
|
+
detail: `The automatic review flagged ${item} and kept it. Open the project and press Overrule to keep it or Redo to make it again.`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
19
38
|
const reasonMax = 140;
|
|
20
39
|
// A provider's failure can run to paragraphs; a notification has a line.
|
|
21
40
|
export function shortReason(reason) {
|
|
@@ -34,6 +53,16 @@ export function noticeText(kind, subject) {
|
|
|
34
53
|
return subject.makesVideo
|
|
35
54
|
? { headline: `Video ready: ${title}`, detail: "Open the project to watch it." }
|
|
36
55
|
: { headline: `Run finished: ${title}`, detail: "Open the project to see its files." };
|
|
56
|
+
case "partial": {
|
|
57
|
+
const reason = shortReason(subject.reason);
|
|
58
|
+
const made = subject.makesVideo
|
|
59
|
+
? `Video ready with problems: ${title}`
|
|
60
|
+
: `Run finished with problems: ${title}`;
|
|
61
|
+
return {
|
|
62
|
+
headline: reason === undefined ? made : `${made} — ${reason}`,
|
|
63
|
+
detail: "Open the project to see which step failed and the button that fixes it.",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
37
66
|
case "failed": {
|
|
38
67
|
const reason = shortReason(subject.reason);
|
|
39
68
|
return {
|
|
@@ -44,18 +73,34 @@ export function noticeText(kind, subject) {
|
|
|
44
73
|
case "waiting":
|
|
45
74
|
return {
|
|
46
75
|
headline: `Waiting for you: ${title}`,
|
|
47
|
-
detail: "Open the project to review the held step or press
|
|
76
|
+
detail: "Open the project to review the held step or press Continue the run.",
|
|
48
77
|
};
|
|
49
78
|
}
|
|
50
79
|
}
|
|
80
|
+
export function topicsNoticeText(subject) {
|
|
81
|
+
const name = subject.scheduleName.trim() === "" ? "Untitled schedule" : subject.scheduleName.trim();
|
|
82
|
+
const count = subject.added;
|
|
83
|
+
const total = subject.waiting > count ? ` ${String(subject.waiting)} are waiting in all.` : "";
|
|
84
|
+
return {
|
|
85
|
+
headline: `${String(count)} new ${count === 1 ? "topic is" : "topics are"} waiting for you`,
|
|
86
|
+
detail: `Open Calendar → Suggested topics → ${name} to queue or reject ${count === 1 ? "it" : "them"}.${total}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
51
89
|
export const testNotice = {
|
|
52
90
|
headline: "Slopify test notification",
|
|
53
|
-
detail: "Notifications work. You'll get one when a run finishes, fails or waits for you.",
|
|
91
|
+
detail: "Notifications work. You'll get one when a run finishes, fails or waits for you, and when a review needs your decision.",
|
|
54
92
|
};
|
|
55
93
|
// The body a Notification URL receives: plain text, which ntfy shows as the message. Nothing
|
|
56
94
|
// but the project's title, what happened and the provider's own short reason goes in it.
|
|
57
95
|
export function webhookBody(text) {
|
|
58
|
-
return `${text.headline}\n${text.detail}\n`;
|
|
96
|
+
return `${text.headline}\n${text.detail}\n${text.link === undefined ? "" : `${text.link}\n`}`;
|
|
97
|
+
}
|
|
98
|
+
// Where a project opens in the browser, from the address the server listens on. A wildcard
|
|
99
|
+
// (0.0.0.0 in Docker) or loopback address is reached as localhost from the same machine.
|
|
100
|
+
export function projectLink(host, port, projectId) {
|
|
101
|
+
const wildcard = host === "0.0.0.0" || host === "::" || host === "" || host === "127.0.0.1";
|
|
102
|
+
const authority = wildcard ? "localhost" : host.includes(":") ? `[${host}]` : host;
|
|
103
|
+
return `http://${authority}:${String(port)}/projects/${encodeURIComponent(projectId)}`;
|
|
59
104
|
}
|
|
60
105
|
export const notificationUrlMax = 2048;
|
|
61
106
|
// Empty clears the setting. Otherwise one absolute http(s) URL with no user name or password:
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { copyFileSync, lstatSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
3
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
4
|
+
import { executionPlan } from "../rebuild/runtime-plan.js";
|
|
5
|
+
import { currentRevisionId } from "../revisions/repo.js";
|
|
6
|
+
import { getRevisionView } from "../revisions/view.js";
|
|
7
|
+
import { projectRows } from "../storage/backup-export.js";
|
|
8
|
+
import { projectTables } from "../storage/backup-format.js";
|
|
9
|
+
import { projectDir } from "../storage/layout.js";
|
|
10
|
+
// ceiling: one pass per layer of the step graph (text, narration, timing, picks, prompts,
|
|
11
|
+
// images, renders); far more than any plan has.
|
|
12
|
+
const rebindPasses = 40;
|
|
13
|
+
export function copyProject(deps, sourceId, title) {
|
|
14
|
+
const source = projectDir(deps.paths, sourceId);
|
|
15
|
+
const tables = projectTables.map((table) => [table, projectRows(deps.db, table, sourceId)]);
|
|
16
|
+
const ids = new Map([[sourceId, deps.ids.next()]]);
|
|
17
|
+
for (const [, rows] of tables)
|
|
18
|
+
for (const row of rows)
|
|
19
|
+
for (const [column, value] of Object.entries(row))
|
|
20
|
+
if ((column === "id" || column === "publication_id" || column === "admission_id") &&
|
|
21
|
+
typeof value === "string" &&
|
|
22
|
+
// ceiling: every id Slopify makes is a 26-character ULID or a 36-character UUID; a
|
|
23
|
+
// shorter value could match inside unrelated text.
|
|
24
|
+
value.length >= 16 &&
|
|
25
|
+
!ids.has(value))
|
|
26
|
+
ids.set(value, deps.ids.next());
|
|
27
|
+
const pattern = new RegExp([...ids.keys()]
|
|
28
|
+
.toSorted((left, right) => right.length - left.length)
|
|
29
|
+
.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
30
|
+
.join("|"), "g");
|
|
31
|
+
const remap = (text) => text.replace(pattern, (id) => ids.get(id) ?? id);
|
|
32
|
+
const projectId = ids.get(sourceId) ?? sourceId;
|
|
33
|
+
const target = projectDir(deps.paths, projectId);
|
|
34
|
+
const at = deps.clock.now().toISOString();
|
|
35
|
+
try {
|
|
36
|
+
for (const file of listFiles(source)) {
|
|
37
|
+
const to = join(target, remap(file));
|
|
38
|
+
mkdirSync(dirname(to), { recursive: true, mode: 0o700 });
|
|
39
|
+
copyFileSync(join(source, file), to);
|
|
40
|
+
}
|
|
41
|
+
transact(deps.db, () => {
|
|
42
|
+
deps.db.exec("PRAGMA defer_foreign_keys = ON");
|
|
43
|
+
for (const [table, rows] of tables)
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
const copied = Object.fromEntries(Object.entries(row).map(([column, value]) => [
|
|
46
|
+
column,
|
|
47
|
+
typeof value === "string" ? remap(value) : value,
|
|
48
|
+
]));
|
|
49
|
+
insert(deps.db, table, table === "projects" ? { ...copied, title, created_at: at, updated_at: at } : copied);
|
|
50
|
+
}
|
|
51
|
+
rebind(deps, projectId);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
rmSync(target, { recursive: true, force: true });
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
return { projectId };
|
|
59
|
+
}
|
|
60
|
+
// Re-stamps the copy's finished steps with the fingerprints its own ids give them.
|
|
61
|
+
function rebind(deps, projectId) {
|
|
62
|
+
const revisionId = currentRevisionId(deps.db, projectId);
|
|
63
|
+
if (revisionId === undefined)
|
|
64
|
+
return;
|
|
65
|
+
for (let pass = 0; pass < rebindPasses; pass++) {
|
|
66
|
+
const view = getRevisionView(deps, projectId, revisionId);
|
|
67
|
+
if (view === undefined)
|
|
68
|
+
return;
|
|
69
|
+
const plan = executionPlan(deps, view, deps.catalogue);
|
|
70
|
+
const changes = new Map();
|
|
71
|
+
for (const change of plan.changedInputs)
|
|
72
|
+
if (change.before !== null && change.after !== null && change.before !== change.after)
|
|
73
|
+
changes.set(change.before, change.after);
|
|
74
|
+
// A finished piece also records the request it answered, which covers the same ids.
|
|
75
|
+
for (const recipe of plan.recipes)
|
|
76
|
+
for (const piece of view.pieces)
|
|
77
|
+
if (piece.selected && piece.key === recipe.key && piece.piece.payload !== null) {
|
|
78
|
+
const request = requestOf(piece.piece.payload);
|
|
79
|
+
if (request !== undefined &&
|
|
80
|
+
recipe.requestFingerprint !== null &&
|
|
81
|
+
request !== recipe.requestFingerprint)
|
|
82
|
+
changes.set(request, recipe.requestFingerprint);
|
|
83
|
+
}
|
|
84
|
+
if (changes.size === 0) {
|
|
85
|
+
deps.db
|
|
86
|
+
.prepare("UPDATE project_revisions SET fingerprints=? WHERE id=?")
|
|
87
|
+
.run(JSON.stringify(Object.fromEntries(plan.recipes.map((row) => [row.key, row.fingerprint]))), revisionId);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const [before, after] of changes)
|
|
91
|
+
for (const [table, column] of [
|
|
92
|
+
["revision_outputs", "fingerprint"],
|
|
93
|
+
["revision_pieces", "fingerprint"],
|
|
94
|
+
["revision_outputs", "descriptor"],
|
|
95
|
+
["revision_pieces", "descriptor"],
|
|
96
|
+
["project_revisions", "content"],
|
|
97
|
+
])
|
|
98
|
+
deps.db
|
|
99
|
+
.prepare(`UPDATE ${table} SET ${column}=replace(${column},?,?) WHERE project_id=? AND instr(${column},?)>0`)
|
|
100
|
+
.run(before, after, projectId, before);
|
|
101
|
+
}
|
|
102
|
+
throw new Error("Slopify hit an internal error (the copy of the sample kept changing while its steps were matched up). Delete the copy and try Make my own copy again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
103
|
+
}
|
|
104
|
+
function requestOf(payload) {
|
|
105
|
+
try {
|
|
106
|
+
const value = JSON.parse(payload);
|
|
107
|
+
return typeof value === "object" &&
|
|
108
|
+
value !== null &&
|
|
109
|
+
"requestFingerprint" in value &&
|
|
110
|
+
typeof value.requestFingerprint === "string"
|
|
111
|
+
? value.requestFingerprint
|
|
112
|
+
: undefined;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function listFiles(root) {
|
|
119
|
+
const out = [];
|
|
120
|
+
const walk = (directory) => {
|
|
121
|
+
let names;
|
|
122
|
+
try {
|
|
123
|
+
names = readdirSync(directory);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const name of names) {
|
|
129
|
+
const path = join(directory, name);
|
|
130
|
+
const stat = lstatSync(path);
|
|
131
|
+
if (stat.isDirectory())
|
|
132
|
+
walk(path);
|
|
133
|
+
else if (stat.isFile())
|
|
134
|
+
out.push(relative(root, path).split(sep).join("/"));
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
walk(root);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
function insert(db, table, row) {
|
|
141
|
+
const columns = Object.keys(row);
|
|
142
|
+
db.prepare(`INSERT INTO ${table} (${columns.join(",")}) VALUES (${columns.map(() => "?").join(",")})`).run(...columns.map((column) => row[column] ?? null));
|
|
143
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readinessIsUsable } from "../../kernel/ports/model.js";
|
|
2
|
+
import { templateById } from "../project-templates/repo.js";
|
|
3
|
+
import { starterPacks } from "./packs.js";
|
|
4
|
+
import { sampleProjectIds } from "./sample.js";
|
|
5
|
+
import { firstRunDismissed, readPackRecords } from "./state.js";
|
|
6
|
+
// The first-run screen shows on a fresh install and never again once it is skipped or a real
|
|
7
|
+
// project (anything but the samples) exists. Reading it writes nothing: a real project that
|
|
8
|
+
// exists before the screen was recorded as done is reported as `settle`, and the client
|
|
9
|
+
// records it with POST /dismiss, so deleting every project later does not bring it back.
|
|
10
|
+
export function firstRunView(db, statuses) {
|
|
11
|
+
const samples = sampleProjectIds(db);
|
|
12
|
+
const real = db
|
|
13
|
+
.prepare("SELECT 1 FROM projects WHERE id IS NOT ? AND id IS NOT ? AND id IS NOT ? LIMIT 1")
|
|
14
|
+
.get(samples.library, samples.audiobook, samples.podcast) !== undefined;
|
|
15
|
+
const dismissed = firstRunDismissed(db);
|
|
16
|
+
const packs = readPackRecords(db);
|
|
17
|
+
return {
|
|
18
|
+
show: !real && !dismissed,
|
|
19
|
+
settle: real && !dismissed,
|
|
20
|
+
voice: firstVoice(statuses),
|
|
21
|
+
sampleProjectId: samples.library,
|
|
22
|
+
samples,
|
|
23
|
+
clis: detectedClis(statuses),
|
|
24
|
+
// A pack counts as added only with its template: the first short adds a pack's prompts and
|
|
25
|
+
// voice but no template, and Add pack must still be able to add that template later (or
|
|
26
|
+
// again, after it was deleted).
|
|
27
|
+
packs: starterPacks.map((pack) => {
|
|
28
|
+
const template = packs[pack.id]?.template;
|
|
29
|
+
const kept = template !== undefined && templateById(db, template) !== undefined;
|
|
30
|
+
return {
|
|
31
|
+
id: pack.id,
|
|
32
|
+
name: pack.name,
|
|
33
|
+
summary: pack.summary,
|
|
34
|
+
installed: kept,
|
|
35
|
+
templateId: kept ? template : null,
|
|
36
|
+
};
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Who would narrate a first short: a keyed voice provider when one has a key, else the
|
|
41
|
+
// computer's own voice when a speech program was found.
|
|
42
|
+
export function firstVoice(statuses) {
|
|
43
|
+
const keyed = statuses.find((row) => row.family === "tts" && row.readiness.kind === "keyed" && readinessIsUsable(row.readiness));
|
|
44
|
+
const system = statuses.find((row) => row.readiness.kind === "local")?.readiness;
|
|
45
|
+
const local = system?.kind === "local" ? system : undefined;
|
|
46
|
+
return {
|
|
47
|
+
keyed: keyed?.displayName ?? null,
|
|
48
|
+
system: {
|
|
49
|
+
available: local?.available === true,
|
|
50
|
+
engine: local?.engine ?? null,
|
|
51
|
+
issue: local?.issue ?? null,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// What this machine can make without a key: each agent CLI Slopify found, and whether it
|
|
56
|
+
// answers (installed, a supported version, signed in as far as the probe can tell).
|
|
57
|
+
export function detectedClis(statuses) {
|
|
58
|
+
return ["claude-code", "codex", "gemini"].map((id) => {
|
|
59
|
+
const status = statuses.find((row) => row.id === id);
|
|
60
|
+
const readiness = status?.readiness;
|
|
61
|
+
const cli = readiness?.kind === "cli" ? readiness : undefined;
|
|
62
|
+
return {
|
|
63
|
+
id,
|
|
64
|
+
name: status?.displayName ?? id,
|
|
65
|
+
installed: cli?.installed === true,
|
|
66
|
+
ready: cli?.installed === true && cli.issue === undefined,
|
|
67
|
+
version: cli?.version ?? null,
|
|
68
|
+
issue: cli?.issue ?? null,
|
|
69
|
+
draws: id === "codex",
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { projectById } from "../admission/repo.js";
|
|
2
|
+
import { usesShortMode } from "../admission/short-mode.js";
|
|
3
|
+
import { promptById } from "../library/repo.js";
|
|
4
|
+
import { createDraft } from "../play-drafts/service.js";
|
|
5
|
+
import { templateById } from "../project-templates/repo.js";
|
|
6
|
+
import { freshTemplateDraft } from "../project-templates/setup.js";
|
|
7
|
+
import { installPack } from "./install.js";
|
|
8
|
+
import { starterPacks, starterSet } from "./packs.js";
|
|
9
|
+
import { readPackRecords } from "./state.js";
|
|
10
|
+
import { packTemplate } from "./template.js";
|
|
11
|
+
// The pack whose short script the short was written from. A short made without a pack, or
|
|
12
|
+
// whose pack's prompts were since renamed or removed, is the starter set's.
|
|
13
|
+
export function shortPack(deps, articlePrompt) {
|
|
14
|
+
const records = readPackRecords(deps.db);
|
|
15
|
+
for (const pack of starterPacks) {
|
|
16
|
+
const id = records[pack.id]?.prompts.shortScript;
|
|
17
|
+
if (id !== undefined && promptById(deps.db, id)?.name === articlePrompt)
|
|
18
|
+
return pack;
|
|
19
|
+
}
|
|
20
|
+
return starterSet;
|
|
21
|
+
}
|
|
22
|
+
export function fullVideoDraft(deps, input) {
|
|
23
|
+
const project = projectById(deps.db, input.projectId);
|
|
24
|
+
if (project === undefined)
|
|
25
|
+
return { ok: false, reason: "not-found" };
|
|
26
|
+
const config = project.config;
|
|
27
|
+
if (!usesShortMode(config))
|
|
28
|
+
return { ok: false, reason: "not-short" };
|
|
29
|
+
const topic = config.values.topic?.trim() || project.title;
|
|
30
|
+
const pack = shortPack(deps, config.articlePrompt ?? "");
|
|
31
|
+
const document = packDocument(deps, pack);
|
|
32
|
+
if (document === undefined)
|
|
33
|
+
return { ok: false, reason: "not-found" };
|
|
34
|
+
const provider = (choice, fallback) => choice === undefined || fallback.provider !== ""
|
|
35
|
+
? fallback
|
|
36
|
+
: {
|
|
37
|
+
provider: choice.provider,
|
|
38
|
+
model: choice.model,
|
|
39
|
+
...(choice.thinking === undefined ? {} : { thinking: choice.thinking }),
|
|
40
|
+
};
|
|
41
|
+
const form = document.form;
|
|
42
|
+
return createDraft(deps, {
|
|
43
|
+
id: input.draftId,
|
|
44
|
+
document: {
|
|
45
|
+
...document,
|
|
46
|
+
form: {
|
|
47
|
+
...form,
|
|
48
|
+
title: project.title,
|
|
49
|
+
values: { ...form.values, topic },
|
|
50
|
+
llm: provider(config.llm, form.llm),
|
|
51
|
+
images: provider(config.images, form.images),
|
|
52
|
+
audio: config.audio === undefined
|
|
53
|
+
? form.audio
|
|
54
|
+
: {
|
|
55
|
+
...form.audio,
|
|
56
|
+
provider: config.audio.provider,
|
|
57
|
+
model: config.audio.model,
|
|
58
|
+
voice: config.audio.voice,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// A pack's own template, as Templates → Apply to Play opens it. The starter set has no long
|
|
65
|
+
// video prompt of its own and no template, so it is its settings without one saved.
|
|
66
|
+
function packDocument(deps, pack) {
|
|
67
|
+
const withTemplate = pack.id !== starterSet.id;
|
|
68
|
+
const installed = installPack(deps, pack.id, { template: withTemplate });
|
|
69
|
+
if (!installed.ok)
|
|
70
|
+
return undefined;
|
|
71
|
+
const { templateId } = installed.value;
|
|
72
|
+
if (!withTemplate || templateId === null)
|
|
73
|
+
return packTemplate(pack, installed.value.prompts, installed.value.voice ?? pack.voice.voiceId);
|
|
74
|
+
const template = templateById(deps.db, templateId);
|
|
75
|
+
if (template === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
return freshTemplateDraft(deps, template.document, {
|
|
78
|
+
id: template.id,
|
|
79
|
+
version: template.version,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
2
|
+
import { detectSlots } from "../admission/substitute.js";
|
|
3
|
+
import { nameMax } from "../library/model.js";
|
|
4
|
+
import { insertPrompt, promptById, promptByName } from "../library/repo.js";
|
|
5
|
+
import { templateById, templateSummaries } from "../project-templates/repo.js";
|
|
6
|
+
import { createTemplate } from "../project-templates/service.js";
|
|
7
|
+
import { insertVoice, listVoices } from "../settings/repo.js";
|
|
8
|
+
import { packById } from "./packs.js";
|
|
9
|
+
import { readPackRecords, writePackRecord } from "./state.js";
|
|
10
|
+
import { packTemplate } from "./template.js";
|
|
11
|
+
// Installs a pack into the library. Idempotent: an item the pack installed before, and that
|
|
12
|
+
// still exists, is kept as it is, even if it was edited since. A user's own prompt, voice or
|
|
13
|
+
// template is never overwritten: a prompt with the pack's name and the pack's exact text is
|
|
14
|
+
// used as it is, and any other one with that name makes the pack's copy come in as
|
|
15
|
+
// "<name> (2)". `template: false` installs the prompts and the voice only (what the
|
|
16
|
+
// 60-second short needs).
|
|
17
|
+
export function installPack(deps, packId, options = { template: true }) {
|
|
18
|
+
const pack = packById(packId);
|
|
19
|
+
if (pack === undefined)
|
|
20
|
+
return { ok: false, reason: "not-found" };
|
|
21
|
+
return transact(deps.db, () => {
|
|
22
|
+
const before = readPackRecords(deps.db)[pack.id];
|
|
23
|
+
let added = false;
|
|
24
|
+
const at = deps.clock.now().toISOString();
|
|
25
|
+
const prompts = {};
|
|
26
|
+
const names = {};
|
|
27
|
+
for (const prompt of pack.prompts) {
|
|
28
|
+
const kept = before?.prompts[prompt.key];
|
|
29
|
+
const existing = kept === undefined ? undefined : promptById(deps.db, kept);
|
|
30
|
+
if (existing !== undefined) {
|
|
31
|
+
prompts[prompt.key] = existing.id;
|
|
32
|
+
names[prompt.key] = existing.name;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const placed = placePrompt(deps, prompt.kind, prompt.name, prompt.body, at);
|
|
36
|
+
added ||= placed.added;
|
|
37
|
+
prompts[prompt.key] = placed.prompt.id;
|
|
38
|
+
names[prompt.key] = placed.prompt.name;
|
|
39
|
+
}
|
|
40
|
+
const voice = placeVoice(deps, pack, before);
|
|
41
|
+
added ||= voice.added;
|
|
42
|
+
let template = before?.template;
|
|
43
|
+
if (options.template && (template === undefined || !templateById(deps.db, template))) {
|
|
44
|
+
const id = deps.uuid();
|
|
45
|
+
const created = createTemplate(deps, {
|
|
46
|
+
id,
|
|
47
|
+
name: freeTemplateName(deps, `${pack.name} starter`),
|
|
48
|
+
document: packTemplate(pack, names, voice.voiceId),
|
|
49
|
+
});
|
|
50
|
+
if (!created.ok)
|
|
51
|
+
throw new Error(`Slopify hit an internal error (the ${pack.name} pack's template was refused: ${created.reason}). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
52
|
+
template = id;
|
|
53
|
+
added = true;
|
|
54
|
+
}
|
|
55
|
+
const record = {
|
|
56
|
+
installedAt: before?.installedAt ?? at,
|
|
57
|
+
prompts,
|
|
58
|
+
voice: voice.id,
|
|
59
|
+
...(template === undefined ? {} : { template }),
|
|
60
|
+
};
|
|
61
|
+
writePackRecord(deps.db, pack.id, record);
|
|
62
|
+
return {
|
|
63
|
+
ok: true,
|
|
64
|
+
value: {
|
|
65
|
+
packId: pack.id,
|
|
66
|
+
added,
|
|
67
|
+
prompts: names,
|
|
68
|
+
templateId: template ?? null,
|
|
69
|
+
voice: voice.voiceId,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function placePrompt(deps, kind, name, body, at) {
|
|
75
|
+
for (let n = 1;; n += 1) {
|
|
76
|
+
const suffix = n === 1 ? "" : ` (${String(n)})`;
|
|
77
|
+
const candidate = `${name.slice(0, nameMax - suffix.length).trimEnd()}${suffix}`;
|
|
78
|
+
const taken = promptByName(deps.db, kind, candidate);
|
|
79
|
+
if (taken?.body === body)
|
|
80
|
+
return { prompt: taken, added: false };
|
|
81
|
+
if (taken !== undefined)
|
|
82
|
+
continue;
|
|
83
|
+
const prompt = {
|
|
84
|
+
id: deps.ids.next(),
|
|
85
|
+
kind,
|
|
86
|
+
name: candidate,
|
|
87
|
+
body,
|
|
88
|
+
slots: detectSlots(body).names,
|
|
89
|
+
updatedAt: at,
|
|
90
|
+
};
|
|
91
|
+
insertPrompt(deps.db, prompt);
|
|
92
|
+
return { prompt, added: true };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// The suggested voice as a saved voice, so Play's voice list offers it. One the user already
|
|
96
|
+
// saved under the same voice ID is used as it is, name and all.
|
|
97
|
+
function placeVoice(deps, pack, before) {
|
|
98
|
+
const voices = listVoices(deps.db);
|
|
99
|
+
const same = voices.find((voice) => (before?.voice !== undefined && voice.id === before.voice) ||
|
|
100
|
+
(voice.provider === pack.voice.provider && voice.voiceId === pack.voice.voiceId));
|
|
101
|
+
if (same !== undefined)
|
|
102
|
+
return { id: same.id, voiceId: same.voiceId, added: false };
|
|
103
|
+
const id = deps.ids.next();
|
|
104
|
+
insertVoice(deps.db, {
|
|
105
|
+
id,
|
|
106
|
+
provider: pack.voice.provider,
|
|
107
|
+
name: pack.voice.name,
|
|
108
|
+
voiceId: pack.voice.voiceId,
|
|
109
|
+
});
|
|
110
|
+
return { id, voiceId: pack.voice.voiceId, added: true };
|
|
111
|
+
}
|
|
112
|
+
function freeTemplateName(deps, name) {
|
|
113
|
+
const taken = new Set(templateSummaries(deps.db).map((row) => row.name.toLowerCase()));
|
|
114
|
+
for (let n = 1;; n += 1) {
|
|
115
|
+
const candidate = n === 1 ? name : `${name} (${String(n)})`;
|
|
116
|
+
if (!taken.has(candidate.toLowerCase()))
|
|
117
|
+
return candidate;
|
|
118
|
+
}
|
|
119
|
+
}
|