@gentbajko/slopify 2.5.0 → 3.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/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +3 -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 +36 -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/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/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/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/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +13 -13
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -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 +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- 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 +154 -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/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +58 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- 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 +152 -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/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +84 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -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 +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +9 -1
- 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/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 +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +17 -8
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- 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 +272 -16
- 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/index.json +26 -0
- package/dist/slices/admission/model.js +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +79 -1
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +41 -5
- 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 +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -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 +2 -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 +60 -2
- 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 +183 -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 +21 -3
- package/dist/slices/notifications/rules.js +22 -1
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -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 +125 -6
- package/dist/slices/play-drafts/readiness.js +3 -0
- 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 +50 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -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 +62 -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 +110 -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 +237 -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 +39 -10
- 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/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 +106 -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 +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +43 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +6 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- 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 +54 -5
- 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 +79 -0
- 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 +92 -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 +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -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/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 +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -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 +69 -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/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -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-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- 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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { titleMax } from "../admission/rules.js";
|
|
2
|
+
import { defaultSentencePauseSeconds } from "../narration/pauses-model.js";
|
|
3
|
+
const textClis = ["claude-code", "codex", "gemini"];
|
|
4
|
+
const keyedImages = ["openai-image", "google-image", "fal", "replicate"];
|
|
5
|
+
const keyedVoices = ["openai-tts", "elevenlabs", "cartesia", "inworld"];
|
|
6
|
+
// The first model of these providers is the one a first short uses, unless one of these is
|
|
7
|
+
// listed: a fast, capable default rather than the slowest or the smallest.
|
|
8
|
+
const preferred = {
|
|
9
|
+
"claude-code": /sonnet/i,
|
|
10
|
+
"openai-tts": /^gpt-4o-mini-tts$/,
|
|
11
|
+
};
|
|
12
|
+
function ready(status) {
|
|
13
|
+
if (status === undefined)
|
|
14
|
+
return false;
|
|
15
|
+
const readiness = status.readiness;
|
|
16
|
+
return readiness.kind === "cli"
|
|
17
|
+
? readiness.installed && readiness.issue === undefined
|
|
18
|
+
: readiness.hasKey;
|
|
19
|
+
}
|
|
20
|
+
async function firstModel(modelsFor, provider, family) {
|
|
21
|
+
let models;
|
|
22
|
+
try {
|
|
23
|
+
models = await modelsFor(provider, family);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const liked = preferred[provider];
|
|
29
|
+
return ((liked === undefined ? undefined : models.find((model) => liked.test(model.id)))?.id ??
|
|
30
|
+
models[0]?.id);
|
|
31
|
+
}
|
|
32
|
+
export async function planShortProviders(input) {
|
|
33
|
+
const status = (id) => input.statuses.find((row) => row.id === id);
|
|
34
|
+
const gaps = [];
|
|
35
|
+
let llm;
|
|
36
|
+
for (const id of [...textClis, "openrouter"]) {
|
|
37
|
+
if (!ready(status(id)))
|
|
38
|
+
continue;
|
|
39
|
+
const model = await firstModel(input.modelsFor, id, "llm");
|
|
40
|
+
if (model !== undefined) {
|
|
41
|
+
llm = { provider: id, model };
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (llm === undefined)
|
|
46
|
+
gaps.push({
|
|
47
|
+
need: "text",
|
|
48
|
+
message: "No text model is ready. Install Claude Code, Codex or Gemini CLI and sign in to it (Settings → Providers shows what Slopify found), or add an OpenRouter key in Settings → Providers.",
|
|
49
|
+
});
|
|
50
|
+
let images;
|
|
51
|
+
for (const id of ["codex-image", ...keyedImages]) {
|
|
52
|
+
if (!ready(status(id)))
|
|
53
|
+
continue;
|
|
54
|
+
const model = await firstModel(input.modelsFor, id, "image");
|
|
55
|
+
if (model !== undefined) {
|
|
56
|
+
images = { provider: id, model };
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (images === undefined)
|
|
61
|
+
gaps.push({
|
|
62
|
+
need: "images",
|
|
63
|
+
message: "No image model is ready. Install Codex CLI and sign in to it, or add an OpenAI, Google, fal.ai or Replicate key in Settings → Providers.",
|
|
64
|
+
});
|
|
65
|
+
let audio;
|
|
66
|
+
const saved = input.voices.find((voice) => ready(status(voice.provider)));
|
|
67
|
+
const provider = saved?.provider ??
|
|
68
|
+
(ready(status(input.suggested.provider)) ? input.suggested.provider : undefined);
|
|
69
|
+
if (provider !== undefined) {
|
|
70
|
+
const model = await firstModel(input.modelsFor, provider, "tts");
|
|
71
|
+
if (model !== undefined)
|
|
72
|
+
audio = { provider, model, voice: saved?.voiceId ?? input.suggested.voiceId };
|
|
73
|
+
}
|
|
74
|
+
if (audio === undefined)
|
|
75
|
+
gaps.push({
|
|
76
|
+
need: "voice",
|
|
77
|
+
message: keyedVoices.some((id) => ready(status(id)))
|
|
78
|
+
? "Your voice provider has no saved voice yet. Add one in Settings → Voices, then try again."
|
|
79
|
+
: "No voice is ready to narrate the short: command-line tools can't speak. Add an OpenAI, ElevenLabs, Cartesia or Inworld key in Settings → Providers (with ElevenLabs, Cartesia or Inworld, also save a voice in Settings → Voices).",
|
|
80
|
+
});
|
|
81
|
+
return llm === undefined || images === undefined || audio === undefined
|
|
82
|
+
? { ok: false, gaps }
|
|
83
|
+
: { ok: true, providers: { llm, audio, images } };
|
|
84
|
+
}
|
|
85
|
+
// The short-mode draft for a topic, using the pack's (or starter set's) script and scene
|
|
86
|
+
// prompts under the names they have in the library.
|
|
87
|
+
export function shortDraft(input) {
|
|
88
|
+
const topic = input.topic.trim().replace(/\s+/g, " ");
|
|
89
|
+
const title = `${topic.charAt(0).toUpperCase()}${topic.slice(1)}`.slice(0, titleMax);
|
|
90
|
+
const { style } = input.pack;
|
|
91
|
+
return {
|
|
92
|
+
mode: "short",
|
|
93
|
+
title,
|
|
94
|
+
format: "9:16",
|
|
95
|
+
sources: {
|
|
96
|
+
research: "off",
|
|
97
|
+
article: "generate",
|
|
98
|
+
audio: "generate",
|
|
99
|
+
images: "generate",
|
|
100
|
+
thumbnail: "off",
|
|
101
|
+
video: "generate",
|
|
102
|
+
document: "off",
|
|
103
|
+
},
|
|
104
|
+
llm: input.providers.llm,
|
|
105
|
+
audio: input.providers.audio,
|
|
106
|
+
images: input.providers.images,
|
|
107
|
+
articlePrompt: input.names.shortScript ?? "",
|
|
108
|
+
imagePrompts: [{ name: input.names.image ?? "", number: shortImages }],
|
|
109
|
+
values: { topic },
|
|
110
|
+
provided: {},
|
|
111
|
+
silenceGapSeconds: 0.5,
|
|
112
|
+
imageSeconds: shortImageSeconds,
|
|
113
|
+
zoomPercent: style.zoomPercent,
|
|
114
|
+
motionStyle: style.motionStyle,
|
|
115
|
+
edgeSilenceSeconds: 0.5,
|
|
116
|
+
...(input.loudness === undefined ? {} : { loudness: input.loudness }),
|
|
117
|
+
sentencePauseSeconds: defaultSentencePauseSeconds,
|
|
118
|
+
subtitles: {
|
|
119
|
+
mode: "off",
|
|
120
|
+
language: "en",
|
|
121
|
+
fontId: "default",
|
|
122
|
+
fontSize: style.captions.fontSize,
|
|
123
|
+
position: style.captions.position,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Four images of fifteen seconds each: a minute of narration.
|
|
128
|
+
export const shortImages = 4;
|
|
129
|
+
export const shortImageSeconds = 15;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createReadStream, existsSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { importBackup } from "../storage/backup-import.js";
|
|
4
|
+
import { deleteProject } from "../storage/delete-project.js";
|
|
5
|
+
import { projectTitle } from "../storage/repo.js";
|
|
6
|
+
import { copyProject } from "./copy.js";
|
|
7
|
+
import { sampleIds } from "./model.js";
|
|
8
|
+
import { readSampleRecord, writeSampleRecord } from "./state.js";
|
|
9
|
+
// The bundled samples: finished projects that ship with the app as backup archives
|
|
10
|
+
// (`assets/sample/`, built by `src/sample-build/`) - "The Library of Alexandria" (video,
|
|
11
|
+
// shorts, article, PDF, description, captions, images), an audiobook read by a narrator and two
|
|
12
|
+
// character voices, and a two-host podcast. Each is imported on first launch, can be played
|
|
13
|
+
// and explored for free, and is read-only: the HTTP edge refuses every change to it, so nothing
|
|
14
|
+
// on it can ever reach a paid provider. "Make my own copy" clones one into an ordinary project;
|
|
15
|
+
// Settings → Restore samples brings back the originals.
|
|
16
|
+
const bundled = (file) => fileURLToPath(new URL(`../../assets/sample/${file}`, import.meta.url));
|
|
17
|
+
export const sampleArchives = {
|
|
18
|
+
library: bundled("sample-project.tar"),
|
|
19
|
+
audiobook: bundled("sample-audiobook.tar"),
|
|
20
|
+
podcast: bundled("sample-podcast.tar"),
|
|
21
|
+
};
|
|
22
|
+
export const sampleArchive = sampleArchives.library;
|
|
23
|
+
const archiveOf = (deps, id) => deps.archives?.[id] ?? sampleArchives[id];
|
|
24
|
+
function recordedProject(db, id) {
|
|
25
|
+
const record = readSampleRecord(db, id);
|
|
26
|
+
return record !== undefined && projectTitle(db, record.projectId) !== undefined
|
|
27
|
+
? record.projectId
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
// The Library of Alexandria's project, while it is in Projects.
|
|
31
|
+
export function sampleProjectId(db) {
|
|
32
|
+
return recordedProject(db, "library");
|
|
33
|
+
}
|
|
34
|
+
export function sampleProjectIds(db) {
|
|
35
|
+
return {
|
|
36
|
+
library: recordedProject(db, "library") ?? null,
|
|
37
|
+
audiobook: recordedProject(db, "audiobook") ?? null,
|
|
38
|
+
podcast: recordedProject(db, "podcast") ?? null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function isSampleProject(db, projectId) {
|
|
42
|
+
return sampleIds.some((id) => readSampleRecord(db, id)?.projectId === projectId);
|
|
43
|
+
}
|
|
44
|
+
// First launch only, for each sample: once one was seeded, deleting it keeps it deleted until
|
|
45
|
+
// Restore samples is pressed. A sample added in a later version is seeded on the first launch
|
|
46
|
+
// of that version.
|
|
47
|
+
export async function seedSamples(deps) {
|
|
48
|
+
const outcomes = {};
|
|
49
|
+
for (const id of sampleIds) {
|
|
50
|
+
if (readSampleRecord(deps.db, id) !== undefined) {
|
|
51
|
+
outcomes[id] = "already-seeded";
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const archive = archiveOf(deps, id);
|
|
55
|
+
if (!existsSync(archive)) {
|
|
56
|
+
deps.log.write("warn", "sample.seed", {
|
|
57
|
+
detail: `A bundled sample project is missing from this install (${archive}).`,
|
|
58
|
+
});
|
|
59
|
+
outcomes[id] = "missing";
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
await importSample(deps, id, archive);
|
|
63
|
+
outcomes[id] = "seeded";
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
library: outcomes.library ?? "missing",
|
|
67
|
+
audiobook: outcomes.audiobook ?? "missing",
|
|
68
|
+
podcast: outcomes.podcast ?? "missing",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Puts back every sample as it shipped: one that was deleted comes back, one that is there is
|
|
72
|
+
// replaced by the original. Copies made of them are not touched.
|
|
73
|
+
export async function restoreSamples(deps) {
|
|
74
|
+
const missing = sampleIds.filter((id) => !existsSync(archiveOf(deps, id)));
|
|
75
|
+
if (missing.length > 0)
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 500,
|
|
79
|
+
detail: "The sample projects aren't all part of this install, so they can't be restored. Update Slopify (npx @gentbajko/slopify@latest, or pull the latest Docker image) and try Restore samples again.",
|
|
80
|
+
};
|
|
81
|
+
for (const id of sampleIds) {
|
|
82
|
+
// The recorded sample, even when it waits in Settings → Trash: its rows would stop the
|
|
83
|
+
// archive's copy (same ids) from coming in, so it is removed for good first.
|
|
84
|
+
const current = readSampleRecord(deps.db, id)?.projectId;
|
|
85
|
+
if (current !== undefined) {
|
|
86
|
+
const removed = deleteProject({
|
|
87
|
+
db: deps.db,
|
|
88
|
+
paths: deps.paths,
|
|
89
|
+
log: deps.log,
|
|
90
|
+
...(deps.hasInflight === undefined ? {} : { hasInflight: deps.hasInflight }),
|
|
91
|
+
}, current);
|
|
92
|
+
if (!removed.ok && removed.reason !== "no-project")
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
status: removed.reason === "running" ? 409 : 500,
|
|
96
|
+
detail: removed.reason === "running"
|
|
97
|
+
? "A sample is still being worked on, so it can't be replaced. Wait for it to finish, then press Restore samples again."
|
|
98
|
+
: "Some of an old sample's files couldn't be removed. Close any program using files in its project folder, then press Restore samples again.",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
await importSample(deps, id, archiveOf(deps, id));
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, samples: sampleProjectIds(deps.db) };
|
|
104
|
+
}
|
|
105
|
+
async function importSample(deps, id, archive) {
|
|
106
|
+
const summary = await importBackup(deps, createReadStream(archive));
|
|
107
|
+
const skipped = summary.projects.skipped[0];
|
|
108
|
+
const project = summary.projects.imported[0] ??
|
|
109
|
+
(skipped !== undefined && projectTitle(deps.db, skipped.id) !== undefined
|
|
110
|
+
? skipped
|
|
111
|
+
: undefined);
|
|
112
|
+
if (project === undefined)
|
|
113
|
+
throw new Error(`Slopify couldn't add a sample project${skipped === undefined ? "" : ` (${skipped.reason})`}. Reinstall Slopify, then try Restore samples in Settings → Backup & storage; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
114
|
+
writeSampleRecord(deps.db, { projectId: project.id, seededAt: deps.clock.now().toISOString() }, id);
|
|
115
|
+
return project.id;
|
|
116
|
+
}
|
|
117
|
+
export function copySample(deps, projectId) {
|
|
118
|
+
if (!isSampleProject(deps.db, projectId))
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
status: 404,
|
|
122
|
+
detail: "Only a sample project can be copied this way. Open the sample from Projects and press Make my own copy there.",
|
|
123
|
+
};
|
|
124
|
+
const title = projectTitle(deps.db, projectId);
|
|
125
|
+
if (title === undefined)
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
status: 404,
|
|
129
|
+
detail: "That sample project is no longer here. Bring it back with Settings → Backup & storage → Restore samples, then press Make my own copy.",
|
|
130
|
+
};
|
|
131
|
+
return { ok: true, ...copyProject(deps, projectId, `${title} (my copy)`) };
|
|
132
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readSetting, writeSetting } from "../settings/repo.js";
|
|
3
|
+
// What the first five minutes remember, in the settings table: whether the first-run screen
|
|
4
|
+
// was dismissed, which project is the bundled sample, and which library rows each installed
|
|
5
|
+
// starter pack became. None of these keys is in a portable backup's settings, so another
|
|
6
|
+
// install decides its own first run.
|
|
7
|
+
export const dismissedKey = "onboarding.dismissed";
|
|
8
|
+
export const sampleKey = "onboarding.sample";
|
|
9
|
+
export const packsKey = "onboarding.packs";
|
|
10
|
+
export const shortRequestsKey = "onboarding.short-requests";
|
|
11
|
+
const sampleRecord = z.object({
|
|
12
|
+
projectId: z.string(),
|
|
13
|
+
seededAt: z.string(),
|
|
14
|
+
});
|
|
15
|
+
const packRecord = z.object({
|
|
16
|
+
installedAt: z.string(),
|
|
17
|
+
prompts: z.record(z.string(), z.string()),
|
|
18
|
+
voice: z.string().optional(),
|
|
19
|
+
template: z.string().optional(),
|
|
20
|
+
});
|
|
21
|
+
const packRecords = z.record(z.string(), packRecord);
|
|
22
|
+
// A request id → the project it started, so a repeated "Make a 60-second short" (a double
|
|
23
|
+
// click, a retried request) returns the same project instead of starting another.
|
|
24
|
+
const shortRequests = z.record(z.string(), z.string());
|
|
25
|
+
// ceiling: only a retry of a recent click needs its answer.
|
|
26
|
+
const shortRequestsKept = 20;
|
|
27
|
+
function readJson(db, key, schema) {
|
|
28
|
+
const raw = readSetting(db, key);
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const parsed = schema.safeParse(JSON.parse(raw));
|
|
33
|
+
return parsed.success ? parsed.data : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function firstRunDismissed(db) {
|
|
40
|
+
return readSetting(db, dismissedKey) !== undefined;
|
|
41
|
+
}
|
|
42
|
+
export function dismissFirstRun(db, at) {
|
|
43
|
+
if (!firstRunDismissed(db))
|
|
44
|
+
writeSetting(db, dismissedKey, at);
|
|
45
|
+
}
|
|
46
|
+
// Each bundled sample is remembered on its own ("The Library of Alexandria" under the key it
|
|
47
|
+
// always had), so a demo added in a later version is seeded once on an install that already
|
|
48
|
+
// has the first.
|
|
49
|
+
const sampleKeyOf = (id) => (id === "library" ? sampleKey : `${sampleKey}.${id}`);
|
|
50
|
+
export function readSampleRecord(db, id = "library") {
|
|
51
|
+
return readJson(db, sampleKeyOf(id), sampleRecord);
|
|
52
|
+
}
|
|
53
|
+
export function writeSampleRecord(db, record, id = "library") {
|
|
54
|
+
writeSetting(db, sampleKeyOf(id), JSON.stringify(record));
|
|
55
|
+
}
|
|
56
|
+
export function readPackRecords(db) {
|
|
57
|
+
return readJson(db, packsKey, packRecords) ?? {};
|
|
58
|
+
}
|
|
59
|
+
export function writePackRecord(db, packId, record) {
|
|
60
|
+
writeSetting(db, packsKey, JSON.stringify({ ...readPackRecords(db), [packId]: record }));
|
|
61
|
+
}
|
|
62
|
+
export function shortRequestProject(db, requestId) {
|
|
63
|
+
return readJson(db, shortRequestsKey, shortRequests)?.[requestId];
|
|
64
|
+
}
|
|
65
|
+
export function recordShortRequest(db, requestId, projectId) {
|
|
66
|
+
const kept = Object.entries(readJson(db, shortRequestsKey, shortRequests) ?? {}).slice(-(shortRequestsKept - 1));
|
|
67
|
+
writeSetting(db, shortRequestsKey, JSON.stringify(Object.fromEntries([...kept, [requestId, projectId]])));
|
|
68
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { defaultEdgeSilenceSeconds } from "../admission/rules.js";
|
|
2
|
+
import { playDraftDocumentSchema } from "../play-drafts/schema.js";
|
|
3
|
+
// A pack's Play template: its prompts ticked, its voice picked, its style set, and the text and
|
|
4
|
+
// image providers left for the user to choose, since which of them is installed or keyed
|
|
5
|
+
// differs per machine.
|
|
6
|
+
export function packTemplate(pack, names, voiceId) {
|
|
7
|
+
const { style } = pack;
|
|
8
|
+
const name = (key) => names[key] ?? "";
|
|
9
|
+
return playDraftDocumentSchema.parse({
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
form: {
|
|
12
|
+
title: "",
|
|
13
|
+
format: "16:9",
|
|
14
|
+
sources: {
|
|
15
|
+
research: style.research ? "generate" : "off",
|
|
16
|
+
article: "generate",
|
|
17
|
+
audio: "generate",
|
|
18
|
+
images: "generate",
|
|
19
|
+
thumbnail: name("thumbnail") === "" ? "off" : "from_prompt",
|
|
20
|
+
video: "generate",
|
|
21
|
+
document: "off",
|
|
22
|
+
},
|
|
23
|
+
llm: { provider: "", model: "" },
|
|
24
|
+
audio: {
|
|
25
|
+
provider: pack.voice.provider,
|
|
26
|
+
model: pack.voice.model,
|
|
27
|
+
voice: voiceId,
|
|
28
|
+
usePronunciationGlossary: true,
|
|
29
|
+
shareGlossary: true,
|
|
30
|
+
describeFigures: true,
|
|
31
|
+
},
|
|
32
|
+
images: { provider: "", model: "" },
|
|
33
|
+
articlePrompt: name("article"),
|
|
34
|
+
narrationPrompt: "",
|
|
35
|
+
youtubeDescription: name("description") !== "",
|
|
36
|
+
showFigures: true,
|
|
37
|
+
descriptionPrompt: name("description"),
|
|
38
|
+
shorts: {
|
|
39
|
+
enabled: name("shorts") !== "",
|
|
40
|
+
count: "2",
|
|
41
|
+
minSeconds: "45",
|
|
42
|
+
maxSeconds: "90",
|
|
43
|
+
prompt: name("shorts"),
|
|
44
|
+
imagePrompt: name("image"),
|
|
45
|
+
titleOnScreen: true,
|
|
46
|
+
},
|
|
47
|
+
imagePrompts: name("image") === "" ? [] : [{ name: name("image"), number: String(style.imagesPerVideo) }],
|
|
48
|
+
thumbnailPrompt: name("thumbnail"),
|
|
49
|
+
intro: "",
|
|
50
|
+
outro: "",
|
|
51
|
+
chunking: { mode: "whole", words: "500", characters: "3000" },
|
|
52
|
+
subtitles: {
|
|
53
|
+
mode: "burn-in",
|
|
54
|
+
language: "en",
|
|
55
|
+
fontId: "default",
|
|
56
|
+
fontSize: String(style.captions.fontSize),
|
|
57
|
+
position: style.captions.position,
|
|
58
|
+
},
|
|
59
|
+
imageSeconds: String(style.imageSeconds),
|
|
60
|
+
edgeSilenceSeconds: String(defaultEdgeSilenceSeconds),
|
|
61
|
+
zoomPercent: String(style.zoomPercent),
|
|
62
|
+
motionStyle: style.motionStyle,
|
|
63
|
+
videoEdit: style.videoEdit,
|
|
64
|
+
values: {},
|
|
65
|
+
provided: { research: "", article: "", audio: null, images: [], thumbnail: null },
|
|
66
|
+
},
|
|
67
|
+
section: "content",
|
|
68
|
+
variants: [],
|
|
69
|
+
expectedWords: String(style.expectedWords),
|
|
70
|
+
previewText: "Every story begins with a word.",
|
|
71
|
+
fontUpload: null,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
// The patch notes the app shows in Settings → Patch notes: Markdown files kept in the
|
|
7
|
+
// repository's docs/patch-notes/ and copied into the build beside the compiled code
|
|
8
|
+
// (scripts/copy-assets.mjs). index.json lists them with a title and a date; each note is
|
|
9
|
+
// `<id>.md` beside it. Nothing here writes: the notes are part of the release.
|
|
10
|
+
export const patchNoteIdPattern = /^[0-9A-Za-z][0-9A-Za-z.-]{0,63}$/;
|
|
11
|
+
const entry = z
|
|
12
|
+
.object({
|
|
13
|
+
id: z.string().regex(patchNoteIdPattern),
|
|
14
|
+
title: z.string().min(1).max(200),
|
|
15
|
+
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
16
|
+
// The one version a note is for ("3.0.0"), or the stretch it covers ("2.0.1 to 3.0.0").
|
|
17
|
+
version: z.string().max(40).optional(),
|
|
18
|
+
range: z.string().max(80).optional(),
|
|
19
|
+
})
|
|
20
|
+
.strict();
|
|
21
|
+
const index = z.array(entry).max(500);
|
|
22
|
+
// dist/patch-notes when running the build; the repository's docs/patch-notes when running
|
|
23
|
+
// from source (tests, `tsx`).
|
|
24
|
+
export function bundledPatchNotesDir() {
|
|
25
|
+
const candidates = ["../../patch-notes/", "../../../../../docs/patch-notes/"].map((path) => fileURLToPath(new URL(path, import.meta.url)));
|
|
26
|
+
return candidates.find((dir) => existsSync(join(dir, "index.json"))) ?? candidates[0];
|
|
27
|
+
}
|
|
28
|
+
export class PatchNotesMissing extends Error {
|
|
29
|
+
}
|
|
30
|
+
// Newest first by date; notes of the same day keep the index's order.
|
|
31
|
+
export async function listPatchNotes(dir) {
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
raw = await readFile(join(dir, "index.json"), "utf8");
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code === "ENOENT")
|
|
38
|
+
throw new PatchNotesMissing("This copy of Slopify has no patch notes: its docs/patch-notes/index.json is missing. Reinstall Slopify (or run npm run build) and reload the page.");
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
let json;
|
|
42
|
+
try {
|
|
43
|
+
json = JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!(error instanceof SyntaxError))
|
|
47
|
+
throw error;
|
|
48
|
+
json = undefined;
|
|
49
|
+
}
|
|
50
|
+
const parsed = index.safeParse(json);
|
|
51
|
+
if (!parsed.success)
|
|
52
|
+
throw new PatchNotesMissing("This copy of Slopify has a damaged patch notes list (docs/patch-notes/index.json). Reinstall Slopify (or fix the file and run npm run build) and reload the page.");
|
|
53
|
+
return parsed.data
|
|
54
|
+
.map((note, order) => ({ note, order }))
|
|
55
|
+
.sort((a, b) => b.note.date.localeCompare(a.note.date) || a.order - b.order)
|
|
56
|
+
.map(({ note }) => note);
|
|
57
|
+
}
|
|
58
|
+
// A note's Markdown, or undefined when the index does not list it.
|
|
59
|
+
export async function readPatchNote(dir, id) {
|
|
60
|
+
if (!patchNoteIdPattern.test(id))
|
|
61
|
+
return undefined;
|
|
62
|
+
const notes = await listPatchNotes(dir);
|
|
63
|
+
if (!notes.some((note) => note.id === id))
|
|
64
|
+
return undefined;
|
|
65
|
+
try {
|
|
66
|
+
return await readFile(join(dir, `${id}.md`), "utf8");
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.code === "ENOENT")
|
|
70
|
+
return undefined;
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
2
|
+
import { readSetting, writeSetting } from "../settings/repo.js";
|
|
3
|
+
import { machineOf } from "../telemetry/repo.js";
|
|
4
|
+
import { listPatchNotes } from "./library.js";
|
|
5
|
+
// The patch notes open by themselves once, on the first start of a version that has notes of
|
|
6
|
+
// its own, after an update. The newest version whose notes this install has closed (or read
|
|
7
|
+
// from the What's new tour) lives in one settings key, so it is once per install rather than
|
|
8
|
+
// once per browser. Backups leave it out, like the tour's key.
|
|
9
|
+
export const patchNotesSeenKey = "patchNotes.seenVersion";
|
|
10
|
+
function releaseOf(version) {
|
|
11
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
12
|
+
return match === null ? undefined : [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
13
|
+
}
|
|
14
|
+
// Whether `a` is a later release than `b`; false when either is not a release number.
|
|
15
|
+
export function isNewerRelease(a, b) {
|
|
16
|
+
const left = releaseOf(a);
|
|
17
|
+
const right = releaseOf(b);
|
|
18
|
+
if (left === undefined || right === undefined)
|
|
19
|
+
return false;
|
|
20
|
+
for (const at of [0, 1, 2])
|
|
21
|
+
if (left[at] !== right[at])
|
|
22
|
+
return left[at] > right[at];
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// The newest version whose notes were seen; undefined when none was recorded, null when the
|
|
26
|
+
// stored value is damaged.
|
|
27
|
+
function seenVersion(db) {
|
|
28
|
+
const raw = readSetting(db, patchNotesSeenKey);
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
let value;
|
|
32
|
+
try {
|
|
33
|
+
value = JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!(error instanceof SyntaxError))
|
|
37
|
+
throw error;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return typeof value === "string" && releaseOf(value) !== undefined ? value : null;
|
|
41
|
+
}
|
|
42
|
+
// Telling an update from a fresh install works as for the What's new tour
|
|
43
|
+
// (slices/settings/whats-new.ts): before the first close, the machine row, written once when
|
|
44
|
+
// the first-run notice is dismissed, keeps the version that was running then. A fresh install
|
|
45
|
+
// has no machine yet or one stamped with the running version, so its notes do not open.
|
|
46
|
+
export function duePatchNote(db, version, notes) {
|
|
47
|
+
const note = notes.find((item) => item.version === version);
|
|
48
|
+
if (note === undefined)
|
|
49
|
+
return null;
|
|
50
|
+
const seen = seenVersion(db);
|
|
51
|
+
// A damaged value is not a reason to open the notes on every launch.
|
|
52
|
+
if (seen === null)
|
|
53
|
+
return null;
|
|
54
|
+
if (seen !== undefined)
|
|
55
|
+
return isNewerRelease(version, seen) ? note.id : null;
|
|
56
|
+
const machine = machineOf(db);
|
|
57
|
+
if (machine === undefined)
|
|
58
|
+
return null;
|
|
59
|
+
return isNewerRelease(version, machine.appVersion) ? note.id : null;
|
|
60
|
+
}
|
|
61
|
+
export async function readPatchNotes(db, dir, version) {
|
|
62
|
+
const notes = await listPatchNotes(dir);
|
|
63
|
+
const current = notes.find((note) => note.version === version) ?? notes[0];
|
|
64
|
+
return { version, current: current?.id ?? null, due: duePatchNote(db, version, notes), notes };
|
|
65
|
+
}
|
|
66
|
+
// Closing the notes, or the What's new tour that stands in for them on a major update,
|
|
67
|
+
// records the running version. Idempotent, and never lowers the value, so running an older
|
|
68
|
+
// version for a while and updating again does not bring back notes already closed.
|
|
69
|
+
export function markPatchNotesSeen(db, version) {
|
|
70
|
+
if (releaseOf(version) === undefined)
|
|
71
|
+
return;
|
|
72
|
+
transact(db, () => {
|
|
73
|
+
const seen = seenVersion(db);
|
|
74
|
+
if (seen === undefined || seen === null || isNewerRelease(version, seen))
|
|
75
|
+
writeSetting(db, patchNotesSeenKey, JSON.stringify(version));
|
|
76
|
+
});
|
|
77
|
+
}
|