@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,100 @@
|
|
|
1
|
+
// Prompt history: every save of a Library prompt or intro/outro keeps a version, and History
|
|
2
|
+
// lists, compares and restores them. A version is the whole saved row - name, body, kind and
|
|
3
|
+
// mode - so restoring one puts back exactly what was saved then.
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { entryModes } from "./model.js";
|
|
6
|
+
export const libraryItemKinds = ["prompt", "entry"];
|
|
7
|
+
// Saves come from this one-user app, so every version is the user's. The column exists so a
|
|
8
|
+
// version written by something else (an import, a future automatic update) can say so.
|
|
9
|
+
export const versionAuthor = "you";
|
|
10
|
+
const versionRow = z.object({
|
|
11
|
+
version: z.number().int().positive(),
|
|
12
|
+
kind: z.string(),
|
|
13
|
+
mode: z.enum(entryModes).nullable(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
body: z.string(),
|
|
16
|
+
author: z.string(),
|
|
17
|
+
restored_from: z.number().int().nullable(),
|
|
18
|
+
created_at: z.string(),
|
|
19
|
+
});
|
|
20
|
+
function kindOf(item) {
|
|
21
|
+
return "kind" in item ? item.kind : item.category;
|
|
22
|
+
}
|
|
23
|
+
function modeOf(item) {
|
|
24
|
+
return "mode" in item ? item.mode : null;
|
|
25
|
+
}
|
|
26
|
+
// Newest first: History opens on what is saved now.
|
|
27
|
+
export function listVersions(db, itemKind, itemId) {
|
|
28
|
+
return db
|
|
29
|
+
.prepare("SELECT version,kind,mode,name,body,author,restored_from,created_at FROM library_versions WHERE item_kind=? AND item_id=? ORDER BY version DESC")
|
|
30
|
+
.all(itemKind, itemId)
|
|
31
|
+
.map((row) => toVersion(versionRow.parse(row)));
|
|
32
|
+
}
|
|
33
|
+
export function versionOf(db, itemKind, itemId, version) {
|
|
34
|
+
const row = db
|
|
35
|
+
.prepare("SELECT version,kind,mode,name,body,author,restored_from,created_at FROM library_versions WHERE item_kind=? AND item_id=? AND version=?")
|
|
36
|
+
.get(itemKind, itemId, version);
|
|
37
|
+
return row === undefined ? undefined : toVersion(versionRow.parse(row));
|
|
38
|
+
}
|
|
39
|
+
// Records the row as saved. A save that changed nothing (the editor's Save pressed twice, a
|
|
40
|
+
// restore of the version already current) adds no version, so History lists real changes only.
|
|
41
|
+
// A row with no versions at all - one a backup or a portable import wrote straight into the
|
|
42
|
+
// table - gets its saved text as version 1 before the new one, so History never starts from
|
|
43
|
+
// a version the user never saw.
|
|
44
|
+
export function recordVersion(db, itemKind, item, options = {}) {
|
|
45
|
+
const latest = listVersions(db, itemKind, item.id)[0];
|
|
46
|
+
let next = (latest?.version ?? 0) + 1;
|
|
47
|
+
if (latest === undefined && options.previous !== undefined && !same(options.previous, item)) {
|
|
48
|
+
insert(db, itemKind, options.previous, 1, null, options.previous.updatedAt);
|
|
49
|
+
next = 2;
|
|
50
|
+
}
|
|
51
|
+
if (latest !== undefined && sameAsVersion(latest, item))
|
|
52
|
+
return;
|
|
53
|
+
insert(db, itemKind, item, next, options.restoredFrom ?? null, item.updatedAt);
|
|
54
|
+
}
|
|
55
|
+
export function deleteVersions(db, itemKind, itemId) {
|
|
56
|
+
db.prepare("DELETE FROM library_versions WHERE item_kind=? AND item_id=?").run(itemKind, itemId);
|
|
57
|
+
}
|
|
58
|
+
// History for a row with no versions yet (written by an import): its saved text as version 1,
|
|
59
|
+
// said rather than stored, since reading must not write.
|
|
60
|
+
export function versionsOrCurrent(db, itemKind, item) {
|
|
61
|
+
const versions = listVersions(db, itemKind, item.id);
|
|
62
|
+
if (versions.length > 0)
|
|
63
|
+
return versions;
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
version: 1,
|
|
67
|
+
kind: kindOf(item),
|
|
68
|
+
mode: modeOf(item),
|
|
69
|
+
name: item.name,
|
|
70
|
+
body: item.body,
|
|
71
|
+
author: versionAuthor,
|
|
72
|
+
restoredFrom: null,
|
|
73
|
+
createdAt: item.updatedAt,
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
function insert(db, itemKind, item, version, restoredFrom, createdAt) {
|
|
78
|
+
db.prepare("INSERT INTO library_versions (item_kind,item_id,version,kind,mode,name,body,author,restored_from,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)").run(itemKind, item.id, version, kindOf(item), modeOf(item), item.name, item.body, versionAuthor, restoredFrom, createdAt);
|
|
79
|
+
}
|
|
80
|
+
function same(a, b) {
|
|
81
|
+
return (a.name === b.name && a.body === b.body && kindOf(a) === kindOf(b) && modeOf(a) === modeOf(b));
|
|
82
|
+
}
|
|
83
|
+
function sameAsVersion(version, item) {
|
|
84
|
+
return (version.name === item.name &&
|
|
85
|
+
version.body === item.body &&
|
|
86
|
+
version.kind === kindOf(item) &&
|
|
87
|
+
version.mode === modeOf(item));
|
|
88
|
+
}
|
|
89
|
+
function toVersion(row) {
|
|
90
|
+
return {
|
|
91
|
+
version: row.version,
|
|
92
|
+
kind: row.kind,
|
|
93
|
+
mode: row.mode,
|
|
94
|
+
name: row.name,
|
|
95
|
+
body: row.body,
|
|
96
|
+
author: row.author,
|
|
97
|
+
restoredFrom: row.restored_from,
|
|
98
|
+
createdAt: row.created_at,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -9,6 +9,9 @@ export const promptKinds = [
|
|
|
9
9
|
"narration",
|
|
10
10
|
"description",
|
|
11
11
|
"shorts",
|
|
12
|
+
"review",
|
|
13
|
+
// Speaker turns for a multi-voice run (`slices/voices/script.ts`), in place of an article.
|
|
14
|
+
"script",
|
|
12
15
|
];
|
|
13
16
|
export const entryCategories = ["intro", "outro"];
|
|
14
17
|
// The name is what the Play pickers show, so it shares admission's title bound.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readSetting, writeSetting } from "../settings/repo.js";
|
|
2
|
+
import { promptById, promptByName } from "./repo.js";
|
|
3
|
+
// An Image prompt's "Draws photorealistic pictures" tick (Library → Prompts → the prompt).
|
|
4
|
+
// Pictures an image model draws in such a style can pass for real scenes, which is YouTube's
|
|
5
|
+
// third AI use case (`slices/studio/disclosure.ts`); a stylised, painterly or illustrated
|
|
6
|
+
// style can't, so the tick is off unless the person sets it. One row of the key/value
|
|
7
|
+
// `settings` table holds the ticked prompts' ids, so the flag needs no column of its own, keeps
|
|
8
|
+
// through edits and renames, and travels with a backup. A prompt in the trash keeps its tick
|
|
9
|
+
// for when it is restored.
|
|
10
|
+
export const photorealisticPromptsKey = "library.photorealisticPrompts";
|
|
11
|
+
export function photorealisticPromptIds(db) {
|
|
12
|
+
const stored = readSetting(db, photorealisticPromptsKey);
|
|
13
|
+
if (stored === undefined)
|
|
14
|
+
return new Set();
|
|
15
|
+
try {
|
|
16
|
+
const value = JSON.parse(stored);
|
|
17
|
+
return new Set(Array.isArray(value) ? value.filter((one) => typeof one === "string") : []);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return new Set();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// The prompts as the Library lists them, the ticked ones marked.
|
|
24
|
+
export function withPhotorealistic(db, prompts) {
|
|
25
|
+
const ticked = photorealisticPromptIds(db);
|
|
26
|
+
return prompts.map((prompt) => ticked.has(prompt.id) ? { ...prompt, photorealistic: true } : prompt);
|
|
27
|
+
}
|
|
28
|
+
export function setPromptPhotorealistic(db, id, on) {
|
|
29
|
+
const prompt = promptById(db, id);
|
|
30
|
+
if (prompt === undefined)
|
|
31
|
+
return { ok: false, reason: "not-found" };
|
|
32
|
+
if (prompt.kind !== "image")
|
|
33
|
+
return { ok: false, reason: "not-an-image-prompt" };
|
|
34
|
+
const others = [...photorealisticPromptIds(db)].filter((one) => one !== id);
|
|
35
|
+
const next = on ? [...others, id].sort() : others;
|
|
36
|
+
if (next.length === 0)
|
|
37
|
+
db.prepare("DELETE FROM settings WHERE key = ?").run(photorealisticPromptsKey);
|
|
38
|
+
else
|
|
39
|
+
writeSetting(db, photorealisticPromptsKey, JSON.stringify(next));
|
|
40
|
+
return { ok: true };
|
|
41
|
+
}
|
|
42
|
+
// Whether the Image prompt a project names draws photorealistic pictures. A name the Library
|
|
43
|
+
// no longer has (deleted or renamed since) is not ticked.
|
|
44
|
+
export function namesPhotorealisticPrompt(db, name) {
|
|
45
|
+
if (name === undefined || name.trim() === "")
|
|
46
|
+
return false;
|
|
47
|
+
const prompt = promptByName(db, "image", name.trim());
|
|
48
|
+
return prompt !== undefined && photorealisticPromptIds(db).has(prompt.id);
|
|
49
|
+
}
|
|
@@ -23,6 +23,8 @@ const slotsColumn = z.array(z.string());
|
|
|
23
23
|
// `lower(name)` and not `name COLLATE NOCASE`: it is the expression the unique indexes
|
|
24
24
|
// are built on, so a lookup by name reads the index instead of the table.
|
|
25
25
|
const byName = "lower(name) = lower(?)";
|
|
26
|
+
// A row in the trash is invisible to every read and write here; `slices/trash` owns it.
|
|
27
|
+
const live = "deleted_at IS NULL";
|
|
26
28
|
// The lists on 04 Prompts and the pickers on Play sort by name.
|
|
27
29
|
const byNameOrder = "ORDER BY lower(name)";
|
|
28
30
|
export function insertPrompt(db, prompt) {
|
|
@@ -32,27 +34,30 @@ export function insertPrompt(db, prompt) {
|
|
|
32
34
|
// column. Returns false when no row has that id.
|
|
33
35
|
export function replacePrompt(db, prompt) {
|
|
34
36
|
const result = db
|
|
35
|
-
.prepare(
|
|
37
|
+
.prepare(`UPDATE prompts SET kind = ?, name = ?, body = ?, slots = ?, updated_at = ? WHERE id = ? AND ${live}`)
|
|
36
38
|
.run(prompt.kind, prompt.name, prompt.body, JSON.stringify(prompt.slots), prompt.updatedAt, prompt.id);
|
|
37
39
|
return Number(result.changes) > 0;
|
|
38
40
|
}
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// Delete moves the row to the trash (Settings → Trash): it keeps its versions and leaves
|
|
42
|
+
// every list and lookup here. No foreign key points at it, so a project that used the
|
|
43
|
+
// template keeps its own rendered text either way. `slices/trash` removes it for good.
|
|
44
|
+
export function trashPrompt(db, id, at) {
|
|
45
|
+
return (Number(db.prepare(`UPDATE prompts SET deleted_at = ? WHERE id = ? AND ${live}`).run(at, id).changes) > 0);
|
|
43
46
|
}
|
|
44
47
|
export function listPrompts(db) {
|
|
45
48
|
return db
|
|
46
|
-
.prepare(`SELECT * FROM prompts ${byNameOrder}`)
|
|
49
|
+
.prepare(`SELECT * FROM prompts WHERE ${live} ${byNameOrder}`)
|
|
47
50
|
.all()
|
|
48
51
|
.map((row) => toPrompt(promptRow.parse(row)));
|
|
49
52
|
}
|
|
50
53
|
export function promptById(db, id) {
|
|
51
|
-
const row = db.prepare(
|
|
54
|
+
const row = db.prepare(`SELECT * FROM prompts WHERE id = ? AND ${live}`).get(id);
|
|
52
55
|
return row === undefined ? undefined : toPrompt(promptRow.parse(row));
|
|
53
56
|
}
|
|
54
57
|
export function promptByName(db, kind, name) {
|
|
55
|
-
const row = db
|
|
58
|
+
const row = db
|
|
59
|
+
.prepare(`SELECT * FROM prompts WHERE kind = ? AND ${byName} AND ${live}`)
|
|
60
|
+
.get(kind, name);
|
|
56
61
|
return row === undefined ? undefined : toPrompt(promptRow.parse(row));
|
|
57
62
|
}
|
|
58
63
|
export function insertEntry(db, entry) {
|
|
@@ -60,26 +65,26 @@ export function insertEntry(db, entry) {
|
|
|
60
65
|
}
|
|
61
66
|
export function replaceEntry(db, entry) {
|
|
62
67
|
const result = db
|
|
63
|
-
.prepare(
|
|
68
|
+
.prepare(`UPDATE entries SET category = ?, mode = ?, name = ?, body = ?, slots = ?, updated_at = ? WHERE id = ? AND ${live}`)
|
|
64
69
|
.run(entry.category, entry.mode, entry.name, entry.body, JSON.stringify(entry.slots), entry.updatedAt, entry.id);
|
|
65
70
|
return Number(result.changes) > 0;
|
|
66
71
|
}
|
|
67
|
-
export function
|
|
68
|
-
return Number(db.prepare(
|
|
72
|
+
export function trashEntry(db, id, at) {
|
|
73
|
+
return (Number(db.prepare(`UPDATE entries SET deleted_at = ? WHERE id = ? AND ${live}`).run(at, id).changes) > 0);
|
|
69
74
|
}
|
|
70
75
|
export function listEntries(db) {
|
|
71
76
|
return db
|
|
72
|
-
.prepare(`SELECT * FROM entries ${byNameOrder}`)
|
|
77
|
+
.prepare(`SELECT * FROM entries WHERE ${live} ${byNameOrder}`)
|
|
73
78
|
.all()
|
|
74
79
|
.map((row) => toEntry(entryRow.parse(row)));
|
|
75
80
|
}
|
|
76
81
|
export function entryById(db, id) {
|
|
77
|
-
const row = db.prepare(
|
|
82
|
+
const row = db.prepare(`SELECT * FROM entries WHERE id = ? AND ${live}`).get(id);
|
|
78
83
|
return row === undefined ? undefined : toEntry(entryRow.parse(row));
|
|
79
84
|
}
|
|
80
85
|
export function entryByName(db, category, name) {
|
|
81
86
|
const row = db
|
|
82
|
-
.prepare(`SELECT * FROM entries WHERE category = ? AND ${byName}`)
|
|
87
|
+
.prepare(`SELECT * FROM entries WHERE category = ? AND ${byName} AND ${live}`)
|
|
83
88
|
.get(category, name);
|
|
84
89
|
return row === undefined ? undefined : toEntry(entryRow.parse(row));
|
|
85
90
|
}
|
|
@@ -1,31 +1,57 @@
|
|
|
1
1
|
// Creating, editing and deleting a template. One rule set covers prompts
|
|
2
2
|
// and entries, so both go through the same three outcomes.
|
|
3
3
|
import { isUniqueConstraint } from "../../kernel/db/index.js";
|
|
4
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
4
5
|
import { detectSlots } from "../admission/substitute.js";
|
|
6
|
+
import { recordVersion, versionOf } from "./history.js";
|
|
5
7
|
import { lintEntry, lintPrompt } from "./lint.js";
|
|
6
|
-
import {
|
|
8
|
+
import { entryCategories, promptKinds } from "./model.js";
|
|
9
|
+
import { entryById, insertEntry, insertPrompt, promptById, replaceEntry, replacePrompt, trashEntry, trashPrompt, } from "./repo.js";
|
|
7
10
|
export function createPrompt(deps, draft) {
|
|
8
11
|
const fields = lintPrompt(draft);
|
|
9
12
|
if (fields.length > 0) {
|
|
10
13
|
return { ok: false, reason: "invalid", fields };
|
|
11
14
|
}
|
|
12
15
|
const prompt = { id: deps.ids.next(), ...promptOf(deps, draft) };
|
|
13
|
-
return written(() => {
|
|
16
|
+
return written(deps.db, () => {
|
|
14
17
|
insertPrompt(deps.db, prompt);
|
|
18
|
+
recordVersion(deps.db, "prompt", prompt);
|
|
15
19
|
return true;
|
|
16
20
|
}, prompt);
|
|
17
21
|
}
|
|
18
|
-
// A save
|
|
19
|
-
export function updatePrompt(deps, id, draft) {
|
|
22
|
+
// A save replaces the row whole and keeps what was saved as a new version (`history.ts`).
|
|
23
|
+
export function updatePrompt(deps, id, draft, restoredFrom) {
|
|
20
24
|
const fields = lintPrompt(draft);
|
|
21
25
|
if (fields.length > 0) {
|
|
22
26
|
return { ok: false, reason: "invalid", fields };
|
|
23
27
|
}
|
|
24
28
|
const prompt = { id, ...promptOf(deps, draft) };
|
|
25
|
-
return written(
|
|
29
|
+
return written(deps.db, () => {
|
|
30
|
+
const previous = promptById(deps.db, id);
|
|
31
|
+
if (!replacePrompt(deps.db, prompt))
|
|
32
|
+
return false;
|
|
33
|
+
recordVersion(deps.db, "prompt", prompt, {
|
|
34
|
+
previous,
|
|
35
|
+
...(restoredFrom === undefined ? {} : { restoredFrom }),
|
|
36
|
+
});
|
|
37
|
+
return true;
|
|
38
|
+
}, prompt);
|
|
39
|
+
}
|
|
40
|
+
// Restore saves an old version again, as a new version that says where it came from, so the
|
|
41
|
+
// versions after it stay in History.
|
|
42
|
+
export function restorePrompt(deps, id, version) {
|
|
43
|
+
const saved = versionOf(deps.db, "prompt", id, version);
|
|
44
|
+
const kind = promptKinds.find((one) => one === saved?.kind);
|
|
45
|
+
if (saved === undefined || kind === undefined || promptById(deps.db, id) === undefined)
|
|
46
|
+
return { ok: false, reason: "not-found" };
|
|
47
|
+
return updatePrompt(deps, id, { kind, name: saved.name, body: saved.body }, version);
|
|
26
48
|
}
|
|
49
|
+
// Delete moves the prompt to the trash with its history; Settings → Trash puts it back or
|
|
50
|
+
// removes it for good (`slices/trash`).
|
|
27
51
|
export function removePrompt(deps, id) {
|
|
28
|
-
return
|
|
52
|
+
return trashPrompt(deps.db, id, deps.clock.now().toISOString())
|
|
53
|
+
? { ok: true, value: null }
|
|
54
|
+
: { ok: false, reason: "not-found" };
|
|
29
55
|
}
|
|
30
56
|
export function createEntry(deps, draft) {
|
|
31
57
|
const fields = lintEntry(draft);
|
|
@@ -33,21 +59,43 @@ export function createEntry(deps, draft) {
|
|
|
33
59
|
return { ok: false, reason: "invalid", fields };
|
|
34
60
|
}
|
|
35
61
|
const entry = { id: deps.ids.next(), ...entryOf(deps, draft) };
|
|
36
|
-
return written(() => {
|
|
62
|
+
return written(deps.db, () => {
|
|
37
63
|
insertEntry(deps.db, entry);
|
|
64
|
+
recordVersion(deps.db, "entry", entry);
|
|
38
65
|
return true;
|
|
39
66
|
}, entry);
|
|
40
67
|
}
|
|
41
|
-
export function updateEntry(deps, id, draft) {
|
|
68
|
+
export function updateEntry(deps, id, draft, restoredFrom) {
|
|
42
69
|
const fields = lintEntry(draft);
|
|
43
70
|
if (fields.length > 0) {
|
|
44
71
|
return { ok: false, reason: "invalid", fields };
|
|
45
72
|
}
|
|
46
73
|
const entry = { id, ...entryOf(deps, draft) };
|
|
47
|
-
return written(
|
|
74
|
+
return written(deps.db, () => {
|
|
75
|
+
const previous = entryById(deps.db, id);
|
|
76
|
+
if (!replaceEntry(deps.db, entry))
|
|
77
|
+
return false;
|
|
78
|
+
recordVersion(deps.db, "entry", entry, {
|
|
79
|
+
previous,
|
|
80
|
+
...(restoredFrom === undefined ? {} : { restoredFrom }),
|
|
81
|
+
});
|
|
82
|
+
return true;
|
|
83
|
+
}, entry);
|
|
84
|
+
}
|
|
85
|
+
export function restoreEntry(deps, id, version) {
|
|
86
|
+
const saved = versionOf(deps.db, "entry", id, version);
|
|
87
|
+
const category = entryCategories.find((one) => one === saved?.kind);
|
|
88
|
+
if (saved === undefined ||
|
|
89
|
+
category === undefined ||
|
|
90
|
+
saved.mode === null ||
|
|
91
|
+
entryById(deps.db, id) === undefined)
|
|
92
|
+
return { ok: false, reason: "not-found" };
|
|
93
|
+
return updateEntry(deps, id, { category, mode: saved.mode, name: saved.name, body: saved.body }, version);
|
|
48
94
|
}
|
|
49
95
|
export function removeEntry(deps, id) {
|
|
50
|
-
return
|
|
96
|
+
return trashEntry(deps.db, id, deps.clock.now().toISOString())
|
|
97
|
+
? { ok: true, value: null }
|
|
98
|
+
: { ok: false, reason: "not-found" };
|
|
51
99
|
}
|
|
52
100
|
// The stored `slots` is recomputed from the body on every save, so the column can never
|
|
53
101
|
// describe a body that is no longer there.
|
|
@@ -74,9 +122,11 @@ function entryOf(deps, draft) {
|
|
|
74
122
|
// lower(name))`. A read-then-write check would answer from a row a second writer could
|
|
75
123
|
// delete between the two statements, so the index decides and the raw SQLite error never
|
|
76
124
|
// leaves this module.
|
|
77
|
-
|
|
125
|
+
// The row and its version are written together: a save either lands with its version or not
|
|
126
|
+
// at all.
|
|
127
|
+
function written(db, write, value) {
|
|
78
128
|
try {
|
|
79
|
-
return write
|
|
129
|
+
return transact(db, write) ? { ok: true, value } : { ok: false, reason: "not-found" };
|
|
80
130
|
}
|
|
81
131
|
catch (error) {
|
|
82
132
|
if (isUniqueConstraint(error)) {
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
// is the one that runs.
|
|
5
5
|
import { normaliseDraft, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
|
|
6
6
|
import { collectFields, render } from "../admission/substitute.js";
|
|
7
|
+
import { reviewPromptKey } from "../reviews/model.js";
|
|
8
|
+
import { activeReviewStages } from "../reviews/rules.js";
|
|
9
|
+
import { usesScriptPrompt } from "../voices/model.js";
|
|
7
10
|
import { snapshotEntry, snapshotPrompt } from "./snapshot.js";
|
|
8
11
|
export function pickTemplates(db, input, snapshot) {
|
|
9
12
|
const draft = normaliseDraft(input);
|
|
@@ -14,7 +17,7 @@ export function pickTemplates(db, input, snapshot) {
|
|
|
14
17
|
const text = [];
|
|
15
18
|
const image = [];
|
|
16
19
|
if (sources.article === "generate") {
|
|
17
|
-
body(db, "article", draft.articlePrompt, "articlePrompt", missing, text, "article", snapshot);
|
|
20
|
+
body(db, usesScriptPrompt(draft) ? "script" : "article", draft.articlePrompt, "articlePrompt", missing, text, "article", snapshot);
|
|
18
21
|
}
|
|
19
22
|
const intro = pickEntry(db, "intro", draft.intro, missing, snapshot);
|
|
20
23
|
if (usesNarrationPreparation(draft))
|
|
@@ -30,6 +33,9 @@ export function pickTemplates(db, input, snapshot) {
|
|
|
30
33
|
body(db, "shorts", draft.shorts?.prompt, "shorts.prompt", missing, text, "shorts", snapshot);
|
|
31
34
|
body(db, "image", draft.shorts?.imagePrompt, "shorts.imagePrompt", missing, image, "shortsImage", snapshot);
|
|
32
35
|
}
|
|
36
|
+
// Each reviewed stage's Review prompt; none picked is the stage's built-in one.
|
|
37
|
+
for (const stage of activeReviewStages(draft))
|
|
38
|
+
body(db, "review", draft.reviews?.stages[stage]?.prompt, `reviews.stages.${stage}.prompt`, missing, text, reviewPromptKey(stage), snapshot);
|
|
33
39
|
// The establishing image's prompt: an image prompt like the others, its keywords filled the
|
|
34
40
|
// same way.
|
|
35
41
|
if (usesReference(draft) && draft.reference?.source === "prompt")
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// "Used by": the templates, schedules and projects that name a Library prompt or intro/outro.
|
|
2
|
+
// Everything refers to the Library by name (a template's form, a schedule's template, a
|
|
3
|
+
// project revision's config), so the match is the name, without regard to case, within the
|
|
4
|
+
// prompt's kind or the entry's category - the same rule the unique indexes and
|
|
5
|
+
// `snapshot.ts` use.
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { liveProject } from "../admission/repo.js";
|
|
8
|
+
// The fields of a Play form or a run config that name Library items. Both shapes carry the
|
|
9
|
+
// same names; a run config holds the intro and outro as `{ name }` and a form as the name.
|
|
10
|
+
const named = z.string().optional().catch(undefined);
|
|
11
|
+
const refsShape = z
|
|
12
|
+
.object({
|
|
13
|
+
articlePrompt: named,
|
|
14
|
+
narrationPrompt: named,
|
|
15
|
+
thumbnailPrompt: named,
|
|
16
|
+
descriptionPrompt: named,
|
|
17
|
+
imagePrompts: z
|
|
18
|
+
.array(z.object({ name: z.string() }).loose())
|
|
19
|
+
.optional()
|
|
20
|
+
.catch(undefined),
|
|
21
|
+
reference: z.object({ prompt: named }).loose().optional().catch(undefined),
|
|
22
|
+
shorts: z.object({ prompt: named, imagePrompt: named }).loose().optional().catch(undefined),
|
|
23
|
+
intro: z
|
|
24
|
+
.union([z.string(), z.object({ name: z.string() }).loose()])
|
|
25
|
+
.optional()
|
|
26
|
+
.catch(undefined),
|
|
27
|
+
outro: z
|
|
28
|
+
.union([z.string(), z.object({ name: z.string() }).loose()])
|
|
29
|
+
.optional()
|
|
30
|
+
.catch(undefined),
|
|
31
|
+
})
|
|
32
|
+
.loose();
|
|
33
|
+
// The names a form or config holds for one kind of prompt or category of entry.
|
|
34
|
+
export function namesIn(value, ref) {
|
|
35
|
+
const parsed = refsShape.safeParse(value);
|
|
36
|
+
if (!parsed.success)
|
|
37
|
+
return [];
|
|
38
|
+
const form = parsed.data;
|
|
39
|
+
const entryName = (one) => one === undefined ? undefined : typeof one === "string" ? one : one.name;
|
|
40
|
+
const names = ref.item === "entry"
|
|
41
|
+
? [ref.category === "intro" ? entryName(form.intro) : entryName(form.outro)]
|
|
42
|
+
: ref.kind === "article"
|
|
43
|
+
? [form.articlePrompt]
|
|
44
|
+
: ref.kind === "narration"
|
|
45
|
+
? [form.narrationPrompt]
|
|
46
|
+
: ref.kind === "thumbnail"
|
|
47
|
+
? [form.thumbnailPrompt]
|
|
48
|
+
: ref.kind === "description"
|
|
49
|
+
? [form.descriptionPrompt]
|
|
50
|
+
: ref.kind === "shorts"
|
|
51
|
+
? [form.shorts?.prompt]
|
|
52
|
+
: [
|
|
53
|
+
...(form.imagePrompts ?? []).map((one) => one.name),
|
|
54
|
+
form.reference?.prompt,
|
|
55
|
+
form.shorts?.imagePrompt,
|
|
56
|
+
];
|
|
57
|
+
return names.filter((name) => name !== undefined && name.trim() !== "");
|
|
58
|
+
}
|
|
59
|
+
export function uses(value, ref) {
|
|
60
|
+
const wanted = ref.name.trim().toLowerCase();
|
|
61
|
+
return namesIn(value, ref).some((name) => name.trim().toLowerCase() === wanted);
|
|
62
|
+
}
|
|
63
|
+
const documentShape = z.object({ form: z.unknown() }).loose();
|
|
64
|
+
function formOf(json) {
|
|
65
|
+
const parsed = documentShape.safeParse(JSON.parse(json));
|
|
66
|
+
return parsed.success ? parsed.data.form : undefined;
|
|
67
|
+
}
|
|
68
|
+
export function usedBy(db, ref) {
|
|
69
|
+
// A name that appears nowhere in the JSON cannot match, so SQL skips those rows before any
|
|
70
|
+
// is parsed. JSON escapes quotes and backslashes, so only a plain name is pre-filtered.
|
|
71
|
+
// SQLite's lower() folds ASCII only, so a name with anything else is not pre-filtered.
|
|
72
|
+
const plain = /^[\x20-\x7e]*$/u.test(ref.name) && !/["\\]/u.test(ref.name)
|
|
73
|
+
? ref.name.trim().toLowerCase()
|
|
74
|
+
: "";
|
|
75
|
+
const templates = db
|
|
76
|
+
.prepare(`SELECT t.id,r.name,r.document_json FROM project_templates t
|
|
77
|
+
JOIN project_template_revisions r ON r.template_id=t.id AND r.version=t.head_version
|
|
78
|
+
WHERE t.deleted_at IS NULL AND instr(lower(r.document_json),?)>0 ORDER BY lower(r.name),t.id`)
|
|
79
|
+
.all(plain)
|
|
80
|
+
.flatMap((row) => {
|
|
81
|
+
const parsed = templateRow.parse(row);
|
|
82
|
+
return uses(formOf(parsed.document_json), ref) ? [{ id: parsed.id, name: parsed.name }] : [];
|
|
83
|
+
});
|
|
84
|
+
// A schedule runs the template version it was saved with, so that version is what it uses.
|
|
85
|
+
const schedules = db
|
|
86
|
+
.prepare(`SELECT s.id,s.name,s.status,r.document_json FROM schedules s
|
|
87
|
+
JOIN project_template_revisions r ON r.template_id=s.template_id AND r.version=s.template_version
|
|
88
|
+
WHERE s.deleted_at IS NULL AND s.status IN ('active','paused') AND instr(lower(r.document_json),?)>0
|
|
89
|
+
ORDER BY lower(s.name),s.id`)
|
|
90
|
+
.all(plain)
|
|
91
|
+
.flatMap((row) => {
|
|
92
|
+
const parsed = scheduleRow.parse(row);
|
|
93
|
+
return uses(formOf(parsed.document_json), ref)
|
|
94
|
+
? [{ id: parsed.id, name: parsed.name, status: parsed.status }]
|
|
95
|
+
: [];
|
|
96
|
+
});
|
|
97
|
+
const revisionRows = db
|
|
98
|
+
.prepare(`SELECT r.project_id,p.title,r.id AS revision_id,r.config,h.revision_id AS head,
|
|
99
|
+
(SELECT count(*) FROM project_revisions x WHERE x.project_id=r.project_id) AS total
|
|
100
|
+
FROM project_revisions r JOIN projects p ON p.id=r.project_id
|
|
101
|
+
LEFT JOIN project_heads h ON h.project_id=r.project_id
|
|
102
|
+
WHERE ${liveProject("p")} AND instr(lower(r.config),?)>0
|
|
103
|
+
ORDER BY p.created_at DESC,p.id,r.created_at`)
|
|
104
|
+
.all(plain)
|
|
105
|
+
.map((row) => revisionRow.parse(row));
|
|
106
|
+
const projects = new Map();
|
|
107
|
+
for (const row of revisionRows) {
|
|
108
|
+
if (!uses(JSON.parse(row.config), ref))
|
|
109
|
+
continue;
|
|
110
|
+
const seen = projects.get(row.project_id);
|
|
111
|
+
projects.set(row.project_id, {
|
|
112
|
+
id: row.project_id,
|
|
113
|
+
title: row.title,
|
|
114
|
+
revisions: (seen?.revisions ?? 0) + 1,
|
|
115
|
+
totalRevisions: row.total,
|
|
116
|
+
current: (seen?.current ?? false) || row.head === row.revision_id,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// A project from before revisions existed has none yet; its own config is what it ran with.
|
|
120
|
+
for (const row of db
|
|
121
|
+
.prepare(`SELECT p.id,p.title,p.config FROM projects p
|
|
122
|
+
WHERE ${liveProject("p")}
|
|
123
|
+
AND NOT EXISTS (SELECT 1 FROM project_revisions r WHERE r.project_id=p.id)
|
|
124
|
+
AND instr(lower(p.config),?)>0 ORDER BY p.created_at DESC,p.id`)
|
|
125
|
+
.all(plain)
|
|
126
|
+
.map((row) => legacyRow.parse(row))) {
|
|
127
|
+
if (uses(JSON.parse(row.config), ref))
|
|
128
|
+
projects.set(row.id, {
|
|
129
|
+
id: row.id,
|
|
130
|
+
title: row.title,
|
|
131
|
+
revisions: 0,
|
|
132
|
+
totalRevisions: 0,
|
|
133
|
+
current: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return { templates, schedules, projects: [...projects.values()] };
|
|
137
|
+
}
|
|
138
|
+
const templateRow = z.object({ id: z.string(), name: z.string(), document_json: z.string() });
|
|
139
|
+
const scheduleRow = z.object({
|
|
140
|
+
id: z.string(),
|
|
141
|
+
name: z.string(),
|
|
142
|
+
status: z.string(),
|
|
143
|
+
document_json: z.string(),
|
|
144
|
+
});
|
|
145
|
+
const revisionRow = z.object({
|
|
146
|
+
project_id: z.string(),
|
|
147
|
+
title: z.string(),
|
|
148
|
+
revision_id: z.string(),
|
|
149
|
+
config: z.string(),
|
|
150
|
+
head: z.string().nullable(),
|
|
151
|
+
total: z.number(),
|
|
152
|
+
});
|
|
153
|
+
const legacyRow = z.object({ id: z.string(), title: z.string(), config: z.string() });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { levelFile, measurable } from "./loudnorm.js";
|
|
4
|
+
import { pieceLufs, pieceTruePeak, spreadOf } from "./model.js";
|
|
5
|
+
// Every narration piece brought to the one common loudness before the join (`levelFile`), each
|
|
6
|
+
// written again as a WAV in `directory` for the join to read in their place. They come out in one format (44.1 kHz mono, which is what the
|
|
7
|
+
// multi-voice join already brings every turn to), so the plain concat can join them too. A piece
|
|
8
|
+
// too short or quiet to measure keeps its own level. Each levelled piece is measured again, so
|
|
9
|
+
// the report says what the levelling did, not what it meant to do.
|
|
10
|
+
const format = { sampleRate: 44100, channels: 1 };
|
|
11
|
+
const goal = { lufs: pieceLufs, truePeak: pieceTruePeak };
|
|
12
|
+
export async function levelPieces(run, files, directory) {
|
|
13
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
14
|
+
const levelled = [];
|
|
15
|
+
const before = [];
|
|
16
|
+
const after = [];
|
|
17
|
+
let skipped = 0;
|
|
18
|
+
for (const [index, file] of files.entries()) {
|
|
19
|
+
run.signal.throwIfAborted();
|
|
20
|
+
const output = join(directory, `piece-${String(index + 1).padStart(4, "0")}.wav`);
|
|
21
|
+
const measured = await levelFile(run, file, output, goal, format);
|
|
22
|
+
levelled.push(output);
|
|
23
|
+
if (!measurable(measured.before)) {
|
|
24
|
+
skipped += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
before.push(measured.before.integrated);
|
|
28
|
+
if (measurable(measured.after))
|
|
29
|
+
after.push(measured.after.integrated);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
files: levelled,
|
|
33
|
+
report: {
|
|
34
|
+
pieces: files.length,
|
|
35
|
+
skipped,
|
|
36
|
+
spreadBefore: spreadOf(before),
|
|
37
|
+
spreadAfter: spreadOf(after),
|
|
38
|
+
target: pieceLufs,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|