@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
|
@@ -3,13 +3,15 @@ import { copyFileSync, createReadStream, existsSync, mkdirSync, mkdtempSync, rea
|
|
|
3
3
|
import { join, relative, sep } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { subtitleModelDir } from "../../kernel/paths.js";
|
|
6
|
+
import { projectLanguage } from "../../kernel/ports/languages.js";
|
|
6
7
|
import { projectById, setStageProgress } from "../admission/repo.js";
|
|
8
|
+
import { captionFont, captionFontDeps } from "../fonts/coverage.js";
|
|
7
9
|
import { resolveFont } from "../fonts/index.js";
|
|
8
10
|
import { outputPath, projectDir } from "../storage/layout.js";
|
|
9
11
|
import { outputsOf } from "../storage/repo.js";
|
|
10
12
|
import { spoken } from "../video/plan.js";
|
|
11
13
|
import { captionCues, serializeAss, serializeSrt, serializeVtt } from "./captions.js";
|
|
12
|
-
import { defaultSubtitles } from "./model.js";
|
|
14
|
+
import { defaultSubtitles, timingOperation } from "./model.js";
|
|
13
15
|
import { spokenText } from "./transcript.js";
|
|
14
16
|
export const subtitleRoles = [
|
|
15
17
|
"subtitles_srt",
|
|
@@ -52,24 +54,27 @@ export async function prepareSubtitles(deps, context, audio, frame) {
|
|
|
52
54
|
setStageProgress(deps.db, context.stage.id, 0, 100);
|
|
53
55
|
context.emit({ type: "stage.progress", projectId, stage: "video", current: 0, total: 100 });
|
|
54
56
|
if (audio.length === 0)
|
|
55
|
-
throw new Error("Captions need narration audio, but this project has none. Turn captions off in Edit project → Subtitles, or turn narration on, then
|
|
57
|
+
throw new Error("Captions need narration audio, but this project has none. Turn captions off in Edit project → Subtitles, or turn narration on, then Try again.");
|
|
56
58
|
const outputs = outputsOf(deps.db, projectId);
|
|
57
59
|
const segments = audio.map((segment) => ({
|
|
58
60
|
...segment,
|
|
59
61
|
text: spoken(segment.kind) ? spokenText(deps, projectId, segment.kind, outputs) : "",
|
|
60
62
|
}));
|
|
61
|
-
const
|
|
63
|
+
const language = projectLanguage(project.config);
|
|
64
|
+
const key = await timingKey(segments, language, context.signal);
|
|
62
65
|
const cache = readCache(deps, projectId, outputs);
|
|
63
66
|
const dir = projectDir(deps.paths, projectId);
|
|
64
67
|
const directory = mkdtempSync(join(dir, "captions-"));
|
|
65
68
|
try {
|
|
66
|
-
const font = await snapshotFont(deps, projectId, outputs, config.fontId, cache, directory);
|
|
69
|
+
const font = await snapshotFont(deps, projectId, outputs, config.fontId, cache, directory, language);
|
|
67
70
|
const omissions = cache?.key === key ? [...cache.omissions] : [];
|
|
68
|
-
const words = cache?.key === key
|
|
71
|
+
const words = cache?.key === key
|
|
72
|
+
? cache.words
|
|
73
|
+
: await alignSegments(deps, context, segments, omissions, language);
|
|
69
74
|
context.signal.throwIfAborted();
|
|
70
75
|
const cues = captionCues(words);
|
|
71
76
|
if (cues.length === 0)
|
|
72
|
-
throw new Error("None of the narration could be matched to the article text, so captions can't be timed.
|
|
77
|
+
throw new Error("None of the narration could be matched to the article text, so captions can't be timed. The narration must be in the project language set in Edit project → Language; if you uploaded your own audio, make sure it reads the article text, then Try again.");
|
|
73
78
|
writeFileSync(join(directory, "subtitles.srt"), serializeSrt(cues), { mode: 0o600 });
|
|
74
79
|
writeFileSync(join(directory, "subtitles.vtt"), serializeVtt(cues), { mode: 0o600 });
|
|
75
80
|
writeFileSync(join(directory, "subtitles.ass"), serializeAss(cues, {
|
|
@@ -77,6 +82,8 @@ export async function prepareSubtitles(deps, context, audio, frame) {
|
|
|
77
82
|
fontSize: config.fontSize,
|
|
78
83
|
fontName: font.assName,
|
|
79
84
|
position: config.position,
|
|
85
|
+
color: config.color,
|
|
86
|
+
outlineColor: config.outlineColor,
|
|
80
87
|
}), { mode: 0o600 });
|
|
81
88
|
writeFileSync(join(directory, "subtitles.json"), JSON.stringify({ key, words, font, omissions }), {
|
|
82
89
|
mode: 0o600,
|
|
@@ -103,9 +110,10 @@ export async function prepareSubtitles(deps, context, audio, frame) {
|
|
|
103
110
|
throw error;
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
|
-
async function timingKey(segments, signal) {
|
|
113
|
+
async function timingKey(segments, language, signal) {
|
|
107
114
|
// Bump when alignment normalization/model changes. Hash file contents, not timestamps.
|
|
108
|
-
|
|
115
|
+
// English hashes the operation it always did (`timingOperation`).
|
|
116
|
+
const hash = createHash("sha256").update(timingOperation(language));
|
|
109
117
|
for (const segment of segments) {
|
|
110
118
|
signal.throwIfAborted();
|
|
111
119
|
hash.update(JSON.stringify({ kind: segment.kind, seconds: segment.seconds, text: segment.text }));
|
|
@@ -117,9 +125,9 @@ async function timingKey(segments, signal) {
|
|
|
117
125
|
}
|
|
118
126
|
return hash.digest("hex");
|
|
119
127
|
}
|
|
120
|
-
async function alignSegments(deps, context, segments, omissions) {
|
|
128
|
+
async function alignSegments(deps, context, segments, omissions, language) {
|
|
121
129
|
if (deps.alignSubtitles === undefined)
|
|
122
|
-
throw new Error("Slopify hit an internal error (the caption timing tool is missing from this build).
|
|
130
|
+
throw new Error("Slopify hit an internal error (the caption timing tool is missing from this build). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
123
131
|
const words = [];
|
|
124
132
|
const total = segments.reduce((sum, segment) => sum + segment.seconds, 0);
|
|
125
133
|
let offset = 0;
|
|
@@ -130,7 +138,8 @@ async function alignSegments(deps, context, segments, omissions) {
|
|
|
130
138
|
audioPath: segment.path,
|
|
131
139
|
onOmission: (omission) => omissions.push({ ...omission, start: omission.start + offset }),
|
|
132
140
|
text: segment.text,
|
|
133
|
-
cacheDir: subtitleModelDir(deps.paths.dataDir),
|
|
141
|
+
cacheDir: subtitleModelDir(deps.paths.dataDir, language),
|
|
142
|
+
...(language === "en" ? {} : { language }),
|
|
134
143
|
ffmpeg: deps.ffmpeg,
|
|
135
144
|
signal: context.signal,
|
|
136
145
|
onProgress: (current, maximum) => {
|
|
@@ -147,7 +156,7 @@ async function alignSegments(deps, context, segments, omissions) {
|
|
|
147
156
|
});
|
|
148
157
|
for (const word of aligned) {
|
|
149
158
|
if (word.end > segment.seconds + 0.1)
|
|
150
|
-
throw new Error("Caption timing came out longer than the narration audio.
|
|
159
|
+
throw new Error("Caption timing came out longer than the narration audio. Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
151
160
|
words.push({
|
|
152
161
|
...word,
|
|
153
162
|
start: word.start + offset,
|
|
@@ -170,12 +179,14 @@ function readCache(deps, projectId, outputs) {
|
|
|
170
179
|
return undefined;
|
|
171
180
|
}
|
|
172
181
|
}
|
|
173
|
-
async function snapshotFont(deps, projectId, outputs, id, cache, directory) {
|
|
182
|
+
async function snapshotFont(deps, projectId, outputs, id, cache, directory, language) {
|
|
174
183
|
const previous = outputs.find((one) => one.role === "subtitle_font");
|
|
175
184
|
const previousPath = previous === undefined ? undefined : outputPath(deps.paths, projectId, previous.path);
|
|
176
|
-
const saved =
|
|
177
|
-
?
|
|
178
|
-
:
|
|
185
|
+
const saved = language !== "en"
|
|
186
|
+
? await captionFont(captionFontDeps(deps.paths), id, language)
|
|
187
|
+
: cache?.font.id === id && previousPath !== undefined && existsSync(previousPath)
|
|
188
|
+
? { ...cache.font, path: previousPath }
|
|
189
|
+
: await resolveFont(deps.paths, id);
|
|
179
190
|
mkdirSync(join(directory, "fonts"), { mode: 0o700 });
|
|
180
191
|
copyFileSync(saved.path, join(directory, "fonts", `selected${saved.extension}`));
|
|
181
192
|
return { id: saved.id, name: saved.name, assName: saved.assName, extension: saved.extension };
|
|
@@ -27,7 +27,7 @@ export function spokenText(deps, projectId, kind, outputs) {
|
|
|
27
27
|
return segment.text;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
throw new Error(`The saved text of the ${kind === "body" ? "article" : kind} is missing, and captions need the text spoken in the narration. Use
|
|
30
|
+
throw new Error(`The saved text of the ${kind === "body" ? "article" : kind} is missing, and captions need the text spoken in the narration. Use More → Write the article again in the Article section, then Try again.`);
|
|
31
31
|
}
|
|
32
32
|
function payload(raw) {
|
|
33
33
|
return textPayload.parse(JSON.parse(raw ?? "null"));
|
|
@@ -22,13 +22,13 @@ export async function runThumbnail(deps, context, providers) {
|
|
|
22
22
|
if (source !== "from_prompt" && source !== "prompt_by_llm") {
|
|
23
23
|
// Off is `skipped` and Provide is `provided` at project creation,
|
|
24
24
|
// so the runner never starts this stage for either.
|
|
25
|
-
throw new Error(`Slopify hit an internal error (the thumbnail stage cannot run with its source set to ${source}).
|
|
25
|
+
throw new Error(`Slopify hit an internal error (the thumbnail stage cannot run with its source set to ${source}). Try again; if it happens again, use Download diagnostics in Settings and report it.`);
|
|
26
26
|
}
|
|
27
27
|
const choice = project.config.images;
|
|
28
28
|
if (choice === undefined) {
|
|
29
29
|
// Admission requires the image provider whenever the thumbnail is generated, so reaching
|
|
30
30
|
// here is a bug in admission rather than the user's.
|
|
31
|
-
throw new Error("No image model is set for the thumbnail. Choose one in Edit project → Providers, then
|
|
31
|
+
throw new Error("No image model is set for the thumbnail. Choose one in Edit project → Providers, then Try again.");
|
|
32
32
|
}
|
|
33
33
|
const result = source === "from_prompt" ? undefined : await byLlm(deps, context, providers, project);
|
|
34
34
|
if (result !== undefined && !result.ok)
|
|
@@ -60,7 +60,7 @@ export async function runThumbnail(deps, context, providers) {
|
|
|
60
60
|
function fromTemplate(project) {
|
|
61
61
|
const rendered = project.config.rendered.thumbnailPrompt;
|
|
62
62
|
if (rendered === undefined) {
|
|
63
|
-
throw new Error("Slopify hit an internal error (the thumbnail prompt was never filled in).
|
|
63
|
+
throw new Error("Slopify hit an internal error (the thumbnail prompt was never filled in). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
64
64
|
}
|
|
65
65
|
return rendered;
|
|
66
66
|
}
|
|
@@ -83,7 +83,7 @@ async function byLlm(deps, context, providers, project) {
|
|
|
83
83
|
}
|
|
84
84
|
if (llm === undefined) {
|
|
85
85
|
// Admission requires the LLM row when the thumbnail source is Prompt by LLM.
|
|
86
|
-
throw new Error("No AI model is set for writing the thumbnail prompt. Choose one in Edit project → Providers, then
|
|
86
|
+
throw new Error("No AI model is set for writing the thumbnail prompt. Choose one in Edit project → Providers, then Try again.");
|
|
87
87
|
}
|
|
88
88
|
const messages = thumbnailMessages(brief(deps, project));
|
|
89
89
|
const answer = await providers.llm({
|
|
@@ -117,7 +117,7 @@ function named(choice) {
|
|
|
117
117
|
function brief(deps, project) {
|
|
118
118
|
const instruction = project.config.rendered.thumbnailPrompt;
|
|
119
119
|
if (instruction === undefined) {
|
|
120
|
-
throw new Error("Slopify hit an internal error (the thumbnail prompt was never filled in).
|
|
120
|
+
throw new Error("Slopify hit an internal error (the thumbnail prompt was never filled in). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
121
121
|
}
|
|
122
122
|
return {
|
|
123
123
|
instruction,
|
|
@@ -133,7 +133,7 @@ function brief(deps, project) {
|
|
|
133
133
|
function articleText(deps, projectId) {
|
|
134
134
|
const article = outputsOf(deps.db, projectId).find((output) => output.role === "article_txt");
|
|
135
135
|
if (article === undefined) {
|
|
136
|
-
throw new Error("The thumbnail prompt is written from the article, but the article isn't ready. Let the Article stage finish, then
|
|
136
|
+
throw new Error("The thumbnail prompt is written from the article, but the article isn't ready. Let the Article stage finish, then Try again.");
|
|
137
137
|
}
|
|
138
138
|
return readFileSync(outputPath(deps.paths, projectId, article.path), "utf8");
|
|
139
139
|
}
|
|
@@ -212,7 +212,7 @@ function instructionsText(messages) {
|
|
|
212
212
|
}
|
|
213
213
|
function payloadOf(piece) {
|
|
214
214
|
if (piece.payload === null) {
|
|
215
|
-
throw new Error("Slopify hit an internal error (the saved thumbnail prompt is empty).
|
|
215
|
+
throw new Error("Slopify hit an internal error (the saved thumbnail prompt is empty). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
216
216
|
}
|
|
217
217
|
return writtenPayload.parse(JSON.parse(piece.payload));
|
|
218
218
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Settings → Trash. Deleting a project, a Library prompt or intro/outro, a template or a
|
|
3
|
+
// schedule stamps it instead of removing it; it stays here this long, then the daily purge
|
|
4
|
+
// removes it for good (a project's folder with it).
|
|
5
|
+
export const trashDays = 30;
|
|
6
|
+
export const trashMs = trashDays * 24 * 60 * 60_000;
|
|
7
|
+
export const trashKinds = ["project", "prompt", "entry", "template", "schedule"];
|
|
8
|
+
export const trashItemSchema = z.object({
|
|
9
|
+
kind: z.enum(trashKinds),
|
|
10
|
+
id: z.string(),
|
|
11
|
+
name: z.string(),
|
|
12
|
+
// What sort of prompt ("article", "image"…) or entry ("intro", "outro"); null otherwise.
|
|
13
|
+
detail: z.string().nullable(),
|
|
14
|
+
deletedAt: z.string(),
|
|
15
|
+
// When the daily purge removes it for good.
|
|
16
|
+
purgeAt: z.string(),
|
|
17
|
+
daysLeft: z.number().int().nonnegative(),
|
|
18
|
+
});
|
|
19
|
+
export const restoredSchema = z.object({
|
|
20
|
+
kind: z.enum(trashKinds),
|
|
21
|
+
id: z.string(),
|
|
22
|
+
name: z.string(),
|
|
23
|
+
// The name it came back under when a live item had taken the old one meanwhile.
|
|
24
|
+
renamedFrom: z.string().nullable(),
|
|
25
|
+
});
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
3
|
+
import { deleteVersions, recordVersion } from "../library/history.js";
|
|
4
|
+
import { nameMax } from "../library/model.js";
|
|
5
|
+
import { entryById, promptById } from "../library/repo.js";
|
|
6
|
+
import { deleteProject, projectBusy } from "../storage/delete-project.js";
|
|
7
|
+
import { trashKinds, trashMs, } from "./model.js";
|
|
8
|
+
// Settings → Trash: what was deleted in the last 30 days, put back or removed for good.
|
|
9
|
+
//
|
|
10
|
+
// Each kind keeps its stamp where it lives: a project in `project_trash` (the projects table is
|
|
11
|
+
// read positionally in many places), a prompt, entry or template in its own `deleted_at`, and a
|
|
12
|
+
// schedule in the `deleted_at` it always had as the tombstone that keeps its run history. A
|
|
13
|
+
// schedule leaves the trash by getting `purged_at`; its row and history stay, as before.
|
|
14
|
+
const dayMs = 24 * 60 * 60_000;
|
|
15
|
+
// A template's name column allows this much (0008's CHECK on project_template_revisions).
|
|
16
|
+
const templateNameMax = 200;
|
|
17
|
+
const row = z.object({
|
|
18
|
+
id: z.string(),
|
|
19
|
+
name: z.string(),
|
|
20
|
+
detail: z.string().nullable(),
|
|
21
|
+
deleted_at: z.string(),
|
|
22
|
+
});
|
|
23
|
+
// Every kind in one statement, newest deletion first.
|
|
24
|
+
const trashedRows = `
|
|
25
|
+
SELECT 'project' AS kind, p.id, p.title AS name, NULL AS detail, t.deleted_at
|
|
26
|
+
FROM project_trash t JOIN projects p ON p.id = t.project_id
|
|
27
|
+
UNION ALL SELECT 'prompt', id, name, kind, deleted_at FROM prompts WHERE deleted_at IS NOT NULL
|
|
28
|
+
UNION ALL SELECT 'entry', id, name, category, deleted_at FROM entries WHERE deleted_at IS NOT NULL
|
|
29
|
+
UNION ALL SELECT 'template', t.id, r.name, NULL, t.deleted_at
|
|
30
|
+
FROM project_templates t
|
|
31
|
+
JOIN project_template_revisions r ON r.template_id = t.id AND r.version = t.head_version
|
|
32
|
+
WHERE t.deleted_at IS NOT NULL
|
|
33
|
+
UNION ALL SELECT 'schedule', id, name, NULL, deleted_at FROM schedules
|
|
34
|
+
WHERE deleted_at IS NOT NULL AND purged_at IS NULL`;
|
|
35
|
+
export function listTrash(deps) {
|
|
36
|
+
const now = deps.clock.now().valueOf();
|
|
37
|
+
return deps.db
|
|
38
|
+
.prepare(`SELECT * FROM (${trashedRows}) ORDER BY deleted_at DESC, kind, id`)
|
|
39
|
+
.all()
|
|
40
|
+
.map((raw) => {
|
|
41
|
+
const parsed = row.extend({ kind: z.enum(trashKinds) }).parse(raw);
|
|
42
|
+
const purgeAt = Date.parse(parsed.deleted_at) + trashMs;
|
|
43
|
+
return {
|
|
44
|
+
kind: parsed.kind,
|
|
45
|
+
id: parsed.id,
|
|
46
|
+
name: parsed.name,
|
|
47
|
+
detail: parsed.detail,
|
|
48
|
+
deletedAt: parsed.deleted_at,
|
|
49
|
+
purgeAt: new Date(purgeAt).toISOString(),
|
|
50
|
+
daysLeft: Math.max(0, Math.ceil((purgeAt - now) / dayMs)),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// 07 Projects' Delete. Refused while the run is going, as removing it always was: the work
|
|
55
|
+
// has to be stopped by the user first. A project waiting its turn (a queued batch item, a
|
|
56
|
+
// checkpoint, work left over from a restart) simply stops being picked up: the runner holds a
|
|
57
|
+
// project in the trash like a paused one and the batch queue moves on past it.
|
|
58
|
+
export function trashProject(deps, projectId) {
|
|
59
|
+
return transact(deps.db, () => {
|
|
60
|
+
const found = deps.db
|
|
61
|
+
.prepare("SELECT (SELECT 1 FROM project_trash t WHERE t.project_id = p.id) AS trashed FROM projects p WHERE p.id = ?")
|
|
62
|
+
.get(projectId);
|
|
63
|
+
if (found === undefined || found.trashed === 1)
|
|
64
|
+
return { ok: false, reason: "not-found" };
|
|
65
|
+
if (projectBusy(deps, projectId))
|
|
66
|
+
return { ok: false, reason: "running" };
|
|
67
|
+
deps.db
|
|
68
|
+
.prepare("INSERT INTO project_trash (project_id, deleted_at) VALUES (?, ?)")
|
|
69
|
+
.run(projectId, deps.clock.now().toISOString());
|
|
70
|
+
return { ok: true, value: null };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Puts the item back where it was. A prompt, entry or template whose name a live one has
|
|
74
|
+
// taken meanwhile comes back as "Name (restored)", then "Name (restored 2)"…
|
|
75
|
+
//
|
|
76
|
+
// A schedule comes back paused with no next run, whatever it was when deleted (only a
|
|
77
|
+
// canceled or completed one can be deleted): nothing runs until Resume is pressed on
|
|
78
|
+
// Schedules, which picks the next occurrence from then. Its template must be live: one in the
|
|
79
|
+
// trash is restored first, and one removed for good leaves the schedule nothing to run.
|
|
80
|
+
export function restoreItem(deps, kind, id) {
|
|
81
|
+
return transact(deps.db, () => {
|
|
82
|
+
switch (kind) {
|
|
83
|
+
case "project":
|
|
84
|
+
return restoreProject(deps.db, id);
|
|
85
|
+
case "prompt":
|
|
86
|
+
return restoreLibrary(deps, "prompt", id);
|
|
87
|
+
case "entry":
|
|
88
|
+
return restoreLibrary(deps, "entry", id);
|
|
89
|
+
case "template":
|
|
90
|
+
return restoreTemplate(deps, id);
|
|
91
|
+
case "schedule":
|
|
92
|
+
return restoreSchedule(deps, id);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function restoreProject(db, id) {
|
|
97
|
+
const found = db
|
|
98
|
+
.prepare("SELECT p.title FROM project_trash t JOIN projects p ON p.id = t.project_id WHERE t.project_id = ?")
|
|
99
|
+
.get(id);
|
|
100
|
+
if (typeof found?.title !== "string")
|
|
101
|
+
return { ok: false, reason: "not-found" };
|
|
102
|
+
db.prepare("DELETE FROM project_trash WHERE project_id = ?").run(id);
|
|
103
|
+
return { ok: true, value: { kind: "project", id, name: found.title, renamedFrom: null } };
|
|
104
|
+
}
|
|
105
|
+
function restoreLibrary(deps, itemKind, id) {
|
|
106
|
+
const table = itemKind === "prompt" ? "prompts" : "entries";
|
|
107
|
+
const group = itemKind === "prompt" ? "kind" : "category";
|
|
108
|
+
const found = deps.db
|
|
109
|
+
.prepare(`SELECT name, ${group} AS grp FROM ${table} WHERE id = ? AND deleted_at IS NOT NULL`)
|
|
110
|
+
.get(id);
|
|
111
|
+
if (typeof found?.name !== "string" || typeof found.grp !== "string")
|
|
112
|
+
return { ok: false, reason: "not-found" };
|
|
113
|
+
const taken = deps.db.prepare(`SELECT 1 FROM ${table} WHERE ${group} = ? AND lower(name) = lower(?) AND deleted_at IS NULL`);
|
|
114
|
+
const grp = found.grp;
|
|
115
|
+
const name = freeName(found.name, nameMax, (candidate) => taken.get(grp, candidate) !== undefined);
|
|
116
|
+
const renamed = name !== found.name;
|
|
117
|
+
deps.db
|
|
118
|
+
.prepare(`UPDATE ${table} SET deleted_at = NULL, name = ?, updated_at = ${renamed ? "?" : "updated_at"} WHERE id = ?`)
|
|
119
|
+
.run(...(renamed ? [name, deps.clock.now().toISOString(), id] : [name, id]));
|
|
120
|
+
// A rename shows in History as a version of its own.
|
|
121
|
+
if (renamed) {
|
|
122
|
+
const item = itemKind === "prompt" ? promptById(deps.db, id) : entryById(deps.db, id);
|
|
123
|
+
if (item !== undefined)
|
|
124
|
+
recordVersion(deps.db, itemKind, item);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
value: { kind: itemKind, id, name, renamedFrom: renamed ? found.name : null },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function restoreTemplate(deps, id) {
|
|
132
|
+
const found = deps.db
|
|
133
|
+
.prepare(`SELECT r.name, r.version, r.document_json FROM project_templates t
|
|
134
|
+
JOIN project_template_revisions r ON r.template_id = t.id AND r.version = t.head_version
|
|
135
|
+
WHERE t.id = ? AND t.deleted_at IS NOT NULL`)
|
|
136
|
+
.get(id);
|
|
137
|
+
if (typeof found?.name !== "string" ||
|
|
138
|
+
typeof found.version !== "number" ||
|
|
139
|
+
typeof found.document_json !== "string")
|
|
140
|
+
return { ok: false, reason: "not-found" };
|
|
141
|
+
const taken = deps.db.prepare(`SELECT 1 FROM project_templates t
|
|
142
|
+
JOIN project_template_revisions r ON r.template_id = t.id AND r.version = t.head_version
|
|
143
|
+
WHERE t.deleted_at IS NULL AND lower(r.name) = lower(?)`);
|
|
144
|
+
const name = freeName(found.name, templateNameMax, (candidate) => taken.get(candidate) !== undefined);
|
|
145
|
+
deps.db.prepare("UPDATE project_templates SET deleted_at = NULL WHERE id = ?").run(id);
|
|
146
|
+
// A rename is a new version, as a rename in the template editor is; the versions a schedule
|
|
147
|
+
// or a draft names stay as they were.
|
|
148
|
+
if (name !== found.name) {
|
|
149
|
+
const version = found.version + 1;
|
|
150
|
+
deps.db
|
|
151
|
+
.prepare("INSERT INTO project_template_revisions(template_id,version,name,document_json,created_at) VALUES (?,?,?,?,?)")
|
|
152
|
+
.run(id, version, name, found.document_json, deps.clock.now().toISOString());
|
|
153
|
+
deps.db.prepare("UPDATE project_templates SET head_version = ? WHERE id = ?").run(version, id);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
value: {
|
|
158
|
+
kind: "template",
|
|
159
|
+
id,
|
|
160
|
+
name,
|
|
161
|
+
renamedFrom: name === found.name ? null : found.name,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function restoreSchedule(deps, id) {
|
|
166
|
+
const found = deps.db
|
|
167
|
+
.prepare(`SELECT s.name, s.template_id,
|
|
168
|
+
(SELECT t.deleted_at IS NOT NULL FROM project_templates t WHERE t.id = s.template_id) AS trashed
|
|
169
|
+
FROM schedules s WHERE s.id = ? AND s.deleted_at IS NOT NULL AND s.purged_at IS NULL`)
|
|
170
|
+
.get(id);
|
|
171
|
+
if (typeof found?.name !== "string")
|
|
172
|
+
return { ok: false, reason: "not-found" };
|
|
173
|
+
if (found.trashed === null)
|
|
174
|
+
return { ok: false, reason: "template-gone" };
|
|
175
|
+
if (found.trashed === 1)
|
|
176
|
+
return { ok: false, reason: "template-in-trash" };
|
|
177
|
+
const at = deps.clock.now().toISOString();
|
|
178
|
+
deps.db
|
|
179
|
+
.prepare(`UPDATE schedules SET deleted_at = NULL, status = 'paused', next_run_at = NULL,
|
|
180
|
+
version = version + 1, updated_at = ? WHERE id = ?`)
|
|
181
|
+
.run(at, id);
|
|
182
|
+
return { ok: true, value: { kind: "schedule", id, name: found.name, renamedFrom: null } };
|
|
183
|
+
}
|
|
184
|
+
// "Delete now": removes one item for good. A project's folder goes first, then its rows
|
|
185
|
+
// (`slices/storage/delete-project.ts`); a folder that will not go leaves it in the trash.
|
|
186
|
+
export function deleteNow(deps, kind, id) {
|
|
187
|
+
switch (kind) {
|
|
188
|
+
case "project": {
|
|
189
|
+
if (deps.db.prepare("SELECT 1 FROM project_trash WHERE project_id = ?").get(id) === undefined)
|
|
190
|
+
return { ok: false, reason: "not-found" };
|
|
191
|
+
const removed = deleteProject(deps, id);
|
|
192
|
+
if (removed.ok)
|
|
193
|
+
return { ok: true, value: null };
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
reason: removed.reason === "no-project" ? "not-found" : removed.reason,
|
|
197
|
+
...(removed.detail === undefined ? {} : { detail: removed.detail }),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
case "prompt":
|
|
201
|
+
case "entry":
|
|
202
|
+
return transact(deps.db, () => {
|
|
203
|
+
const table = kind === "prompt" ? "prompts" : "entries";
|
|
204
|
+
const changed = deps.db
|
|
205
|
+
.prepare(`DELETE FROM ${table} WHERE id = ? AND deleted_at IS NOT NULL`)
|
|
206
|
+
.run(id).changes;
|
|
207
|
+
if (Number(changed) === 0)
|
|
208
|
+
return { ok: false, reason: "not-found" };
|
|
209
|
+
deleteVersions(deps.db, kind, id);
|
|
210
|
+
return { ok: true, value: null };
|
|
211
|
+
});
|
|
212
|
+
case "template": {
|
|
213
|
+
// Revisions cascade. A schedule in the trash may still name it: that schedule can then
|
|
214
|
+
// no longer be restored (`restoreSchedule`), only removed.
|
|
215
|
+
const changed = deps.db
|
|
216
|
+
.prepare("DELETE FROM project_templates WHERE id = ? AND deleted_at IS NOT NULL")
|
|
217
|
+
.run(id).changes;
|
|
218
|
+
return Number(changed) === 0 ? { ok: false, reason: "not-found" } : { ok: true, value: null };
|
|
219
|
+
}
|
|
220
|
+
case "schedule": {
|
|
221
|
+
// Gone from the trash; the row stays as the history Schedules lists under Deleted.
|
|
222
|
+
const changed = deps.db
|
|
223
|
+
.prepare("UPDATE schedules SET purged_at = ? WHERE id = ? AND deleted_at IS NOT NULL AND purged_at IS NULL")
|
|
224
|
+
.run(deps.clock.now().toISOString(), id).changes;
|
|
225
|
+
return Number(changed) === 0 ? { ok: false, reason: "not-found" } : { ok: true, value: null };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// The daily purge: everything deleted more than 30 days ago goes for good. A failure on one
|
|
230
|
+
// item is logged and leaves it for the next pass; the rest still go.
|
|
231
|
+
export function purgeExpired(deps) {
|
|
232
|
+
const cutoff = new Date(deps.clock.now().valueOf() - trashMs).toISOString();
|
|
233
|
+
let removed = 0;
|
|
234
|
+
let kept = 0;
|
|
235
|
+
for (const item of listTrash(deps)) {
|
|
236
|
+
if (item.deletedAt > cutoff)
|
|
237
|
+
continue;
|
|
238
|
+
const result = deleteNow(deps, item.kind, item.id);
|
|
239
|
+
if (result.ok)
|
|
240
|
+
removed++;
|
|
241
|
+
else {
|
|
242
|
+
kept++;
|
|
243
|
+
deps.log.write("warn", "trash.purge", {
|
|
244
|
+
...(item.kind === "project" ? { projectId: item.id } : {}),
|
|
245
|
+
detail: `${item.kind} ${item.id}: ${result.detail ?? result.reason}`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { removed, kept };
|
|
250
|
+
}
|
|
251
|
+
export function createTrashPurge(deps) {
|
|
252
|
+
let last;
|
|
253
|
+
return {
|
|
254
|
+
tick: () => {
|
|
255
|
+
const now = deps.clock.now().valueOf();
|
|
256
|
+
if (last !== undefined && now - last < dayMs)
|
|
257
|
+
return undefined;
|
|
258
|
+
last = now;
|
|
259
|
+
return purgeExpired(deps);
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// The name itself when free, else "Name (restored)", "Name (restored 2)"… cut to fit `max`.
|
|
264
|
+
export function freeName(name, max, taken) {
|
|
265
|
+
if (!taken(name))
|
|
266
|
+
return name;
|
|
267
|
+
for (let n = 1;; n++) {
|
|
268
|
+
const suffix = n === 1 ? " (restored)" : ` (restored ${String(n)})`;
|
|
269
|
+
const candidate = `${name.slice(0, Math.max(1, max - suffix.length)).trimEnd()}${suffix}`;
|
|
270
|
+
if (!taken(candidate))
|
|
271
|
+
return candidate;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// The tutorials are the GitHub wiki's pages (docs/wiki), so their links name sections the way
|
|
2
|
+
// GitHub does: `[text](Page-Name#section-anchor)`. These helpers work out those anchors, for
|
|
3
|
+
// the server's search, the web page that scrolls to them and the test that checks every help
|
|
4
|
+
// entry's Learn more link. Pure: the web bundle imports this file.
|
|
5
|
+
export const tutorialPageIdPattern = /^[A-Za-z0-9][A-Za-z0-9-]{0,79}$/;
|
|
6
|
+
// A heading's text as GitHub renders it: link targets, code ticks and emphasis marks dropped.
|
|
7
|
+
export function headingText(markdown) {
|
|
8
|
+
return markdown
|
|
9
|
+
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
10
|
+
.replace(/`([^`]*)`/g, "$1")
|
|
11
|
+
.replace(/(\*\*|__)(.+?)\1/g, "$2")
|
|
12
|
+
.replace(/(^|[^\p{L}\p{N}])[*_]([^*_]+)[*_](?![\p{L}\p{N}])/gu, "$1$2")
|
|
13
|
+
.replace(/<[^>]+>/g, "")
|
|
14
|
+
.trim();
|
|
15
|
+
}
|
|
16
|
+
// GitHub's anchor for a heading's text: lower case, punctuation dropped, each space a hyphen.
|
|
17
|
+
export function githubSlug(text) {
|
|
18
|
+
return text
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[^\p{L}\p{N}\p{M} _-]/gu, "")
|
|
21
|
+
.replace(/ /g, "-");
|
|
22
|
+
}
|
|
23
|
+
const headingLine = /^ {0,3}(#{1,6})\s+(.+?)\s*#*\s*$/;
|
|
24
|
+
// Every heading of a page in order, with GitHub's anchor; a repeated anchor gets `-1`, `-2`.
|
|
25
|
+
// `#` inside fenced code is not a heading.
|
|
26
|
+
export function tutorialHeadings(markdown) {
|
|
27
|
+
const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
|
|
28
|
+
const seen = new Map();
|
|
29
|
+
const headings = [];
|
|
30
|
+
let fenced = false;
|
|
31
|
+
for (const [line, raw] of lines.entries()) {
|
|
32
|
+
if (/^\s*(```|~~~)/.test(raw))
|
|
33
|
+
fenced = !fenced;
|
|
34
|
+
const match = fenced ? null : headingLine.exec(raw);
|
|
35
|
+
if (match === null)
|
|
36
|
+
continue;
|
|
37
|
+
const text = headingText(match[2] ?? "");
|
|
38
|
+
const base = githubSlug(text);
|
|
39
|
+
const count = seen.get(base) ?? 0;
|
|
40
|
+
seen.set(base, count + 1);
|
|
41
|
+
headings.push({
|
|
42
|
+
level: (match[1] ?? "#").length,
|
|
43
|
+
text,
|
|
44
|
+
anchor: count === 0 ? base : `${base}-${String(count)}`,
|
|
45
|
+
line,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return headings;
|
|
49
|
+
}
|
|
50
|
+
// The anchors a page offers, for checking a link.
|
|
51
|
+
export function tutorialAnchors(markdown) {
|
|
52
|
+
return new Set(tutorialHeadings(markdown).map((heading) => heading.anchor));
|
|
53
|
+
}
|
|
54
|
+
// A page id as a title when the sidebar gives none: "Play-Overview" → "Play Overview".
|
|
55
|
+
export function pageTitleOf(id) {
|
|
56
|
+
return id.replace(/-/g, " ");
|
|
57
|
+
}
|