@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,90 @@
|
|
|
1
|
+
import { castMentions } from "../channels/cast-match.js";
|
|
2
|
+
import { castOfChannel, resolveChannelId } from "../channels/repo.js";
|
|
3
|
+
import { normaliseTopic, topicWords } from "../schedules/similar.js";
|
|
4
|
+
import { episodeMemoryOn, memoriesOfChannel, } from "./repo.js";
|
|
5
|
+
// At most this many earlier episodes go into a new episode's prompt.
|
|
6
|
+
export const earlierEpisodesMax = 5;
|
|
7
|
+
// A shared cast member counts for more than any number of shared title words: the same
|
|
8
|
+
// character is the strongest sign two episodes must agree.
|
|
9
|
+
const castWeight = 100;
|
|
10
|
+
// The earlier episodes of the same channel related to a new one: they share a cast member the
|
|
11
|
+
// new title or keyword values mention, or words of their titles. Most related first, the
|
|
12
|
+
// newest first among equals. An episode with the same title as the new one is a remake of it,
|
|
13
|
+
// not an earlier episode, and is left out.
|
|
14
|
+
export function relatedEpisodes(memories, cast, subject, title) {
|
|
15
|
+
const mentioned = new Set(castMentions(subject, cast).map((member) => member.name.toLowerCase()));
|
|
16
|
+
const titles = memories.map((memory) => titleWords(memory.title));
|
|
17
|
+
// A word most of the channel's titles share is the template's ("D&D Lore: …"), not the
|
|
18
|
+
// topic's, and relates nothing.
|
|
19
|
+
const seen = new Map();
|
|
20
|
+
for (const set of titles)
|
|
21
|
+
for (const word of set)
|
|
22
|
+
seen.set(word, (seen.get(word) ?? 0) + 1);
|
|
23
|
+
const common = (word) => {
|
|
24
|
+
const count = seen.get(word) ?? 0;
|
|
25
|
+
return count >= 2 && count / memories.length > 0.5;
|
|
26
|
+
};
|
|
27
|
+
const words = titleWords(subject);
|
|
28
|
+
const same = normaliseTopic(title);
|
|
29
|
+
return memories
|
|
30
|
+
.flatMap((memory, order) => {
|
|
31
|
+
if (same !== "" && normaliseTopic(memory.title) === same)
|
|
32
|
+
return [];
|
|
33
|
+
const sharedCast = memory.cast.filter((name) => mentioned.has(name.toLowerCase())).length;
|
|
34
|
+
let sharedWords = 0;
|
|
35
|
+
for (const word of titles[order] ?? [])
|
|
36
|
+
if (words.has(word) && !common(word))
|
|
37
|
+
sharedWords += 1;
|
|
38
|
+
const score = sharedCast * castWeight + sharedWords;
|
|
39
|
+
return score === 0 ? [] : [{ memory, score, order }];
|
|
40
|
+
})
|
|
41
|
+
.sort((a, b) => b.score - a.score || a.order - b.order)
|
|
42
|
+
.slice(0, earlierEpisodesMax)
|
|
43
|
+
.map((row) => row.memory);
|
|
44
|
+
}
|
|
45
|
+
// A title's words as the topic checks compare them, less the one- and two-letter ones ("D&D"
|
|
46
|
+
// is "d and d"), which relate nothing.
|
|
47
|
+
function titleWords(text) {
|
|
48
|
+
return new Set([...topicWords(text)].filter((word) => word.length > 2 || /\d/.test(word)));
|
|
49
|
+
}
|
|
50
|
+
// The earlier episodes a new run carries, copied into its config when it starts so a later
|
|
51
|
+
// edit or a new memory never changes a project already made. Only for a run that writes its
|
|
52
|
+
// article (or script) on a channel with episode memory on; undefined otherwise, so the config
|
|
53
|
+
// is exactly what it was before episode memory.
|
|
54
|
+
export function earlierEpisodesFor(db, draft, title) {
|
|
55
|
+
if (draft.sources.article !== "generate")
|
|
56
|
+
return undefined;
|
|
57
|
+
const channelId = resolveChannelId(db, draft.channelId);
|
|
58
|
+
if (!episodeMemoryOn(db, channelId))
|
|
59
|
+
return undefined;
|
|
60
|
+
const memories = memoriesOfChannel(db, channelId);
|
|
61
|
+
if (memories.length === 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
const cast = castOfChannel(db, channelId).map((member) => ({
|
|
64
|
+
name: member.name,
|
|
65
|
+
aliases: member.aliases,
|
|
66
|
+
description: member.description,
|
|
67
|
+
images: [],
|
|
68
|
+
}));
|
|
69
|
+
const subject = [title, ...Object.values(draft.values)].join("\n");
|
|
70
|
+
const related = relatedEpisodes(memories, cast, subject, title);
|
|
71
|
+
return related.length === 0
|
|
72
|
+
? undefined
|
|
73
|
+
: related.map((memory) => ({ title: memory.title, summary: memory.summary }));
|
|
74
|
+
}
|
|
75
|
+
// The block appended to the article (or script) prompt. The prompt comes back unchanged when
|
|
76
|
+
// the run carries no earlier episodes, so every project made before episode memory, or with it
|
|
77
|
+
// off, sends exactly what it always sent.
|
|
78
|
+
export function withEarlierEpisodes(prompt, episodes) {
|
|
79
|
+
if (episodes === undefined || episodes.length === 0)
|
|
80
|
+
return prompt;
|
|
81
|
+
return [
|
|
82
|
+
prompt,
|
|
83
|
+
"",
|
|
84
|
+
"Earlier episodes",
|
|
85
|
+
"",
|
|
86
|
+
"This channel has already made these related episodes. Stay consistent with them and never contradict them; refer back to one where it fits naturally, but don't retell it.",
|
|
87
|
+
"",
|
|
88
|
+
...episodes.map((episode) => `- "${episode.title}": ${episode.summary.replace(/\s+/g, " ")}`),
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const memoryRow = z.object({
|
|
3
|
+
id: z.string(),
|
|
4
|
+
channel_id: z.string(),
|
|
5
|
+
project_id: z.string(),
|
|
6
|
+
title: z.string(),
|
|
7
|
+
summary: z.string(),
|
|
8
|
+
cast_json: z.string(),
|
|
9
|
+
source: z.enum(["generated", "edited"]),
|
|
10
|
+
recipe: z.string().nullable(),
|
|
11
|
+
created_at: z.string(),
|
|
12
|
+
updated_at: z.string(),
|
|
13
|
+
});
|
|
14
|
+
function memoryOf(row) {
|
|
15
|
+
const value = memoryRow.parse(row);
|
|
16
|
+
return {
|
|
17
|
+
id: value.id,
|
|
18
|
+
channelId: value.channel_id,
|
|
19
|
+
projectId: value.project_id,
|
|
20
|
+
title: value.title,
|
|
21
|
+
summary: value.summary,
|
|
22
|
+
cast: z.array(z.string()).parse(JSON.parse(value.cast_json)),
|
|
23
|
+
source: value.source,
|
|
24
|
+
recipe: value.recipe,
|
|
25
|
+
createdAt: value.created_at,
|
|
26
|
+
updatedAt: value.updated_at,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function withoutRecipe(memory) {
|
|
30
|
+
const { recipe: _recipe, ...rest } = memory;
|
|
31
|
+
return rest;
|
|
32
|
+
}
|
|
33
|
+
// Newest first.
|
|
34
|
+
export function memoriesOfChannel(db, channelId) {
|
|
35
|
+
return db
|
|
36
|
+
.prepare("SELECT * FROM episode_memories WHERE channel_id=? ORDER BY created_at DESC, rowid DESC")
|
|
37
|
+
.all(channelId)
|
|
38
|
+
.map((row) => withoutRecipe(memoryOf(row)));
|
|
39
|
+
}
|
|
40
|
+
export function memoryById(db, id) {
|
|
41
|
+
const row = db.prepare("SELECT * FROM episode_memories WHERE id=?").get(id);
|
|
42
|
+
return row === undefined ? undefined : withoutRecipe(memoryOf(row));
|
|
43
|
+
}
|
|
44
|
+
export function memoryOfProject(db, projectId) {
|
|
45
|
+
const row = db.prepare("SELECT * FROM episode_memories WHERE project_id=?").get(projectId);
|
|
46
|
+
return row === undefined ? undefined : memoryOf(row);
|
|
47
|
+
}
|
|
48
|
+
// A project finished again replaces its generated summary in place, keeping its id and when
|
|
49
|
+
// it was first made.
|
|
50
|
+
export function saveGeneratedMemory(db, memory, at) {
|
|
51
|
+
db.prepare(`INSERT INTO episode_memories(id,channel_id,project_id,title,summary,cast_json,source,recipe,created_at,updated_at)
|
|
52
|
+
VALUES (?,?,?,?,?,?,'generated',?,?,?)
|
|
53
|
+
ON CONFLICT(project_id) DO UPDATE SET channel_id=excluded.channel_id,title=excluded.title,
|
|
54
|
+
summary=excluded.summary,cast_json=excluded.cast_json,source='generated',
|
|
55
|
+
recipe=excluded.recipe,updated_at=excluded.updated_at`).run(memory.id, memory.channelId, memory.projectId, memory.title, memory.summary, JSON.stringify(memory.cast), memory.recipe, at, at);
|
|
56
|
+
}
|
|
57
|
+
export function episodeMemoryOn(db, channelId) {
|
|
58
|
+
const row = db.prepare("SELECT episode_memory FROM channels WHERE id=?").get(channelId);
|
|
59
|
+
return row !== undefined && Number(row.episode_memory) === 1;
|
|
60
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { channelById } from "../channels/repo.js";
|
|
3
|
+
import { episodeMemoryOn, memoriesOfChannel, memoryById } from "./repo.js";
|
|
4
|
+
import { summaryWordsMax } from "./summarize.js";
|
|
5
|
+
const settingSchema = z.object({ enabled: z.boolean() }).strict();
|
|
6
|
+
// Room for a hand-written summary a little longer than a generated one.
|
|
7
|
+
export const summaryCharsMax = 4000;
|
|
8
|
+
const editSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
summary: z
|
|
11
|
+
.string()
|
|
12
|
+
.trim()
|
|
13
|
+
.min(1, "Write the summary, or delete the memory instead.")
|
|
14
|
+
.max(summaryCharsMax, `Keep the summary to ${summaryCharsMax.toLocaleString("en")} characters or fewer; about ${String(summaryWordsMax)} words is plenty.`),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
export function channelEpisodes(deps, channelId) {
|
|
18
|
+
if (channelById(deps.db, channelId) === undefined)
|
|
19
|
+
return { ok: false, reason: "not-found" };
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
value: {
|
|
23
|
+
enabled: episodeMemoryOn(deps.db, channelId),
|
|
24
|
+
memories: memoriesOfChannel(deps.db, channelId),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function setEpisodeMemory(deps, channelId, input) {
|
|
29
|
+
const parsed = settingSchema.safeParse(input);
|
|
30
|
+
if (!parsed.success)
|
|
31
|
+
return { ok: false, reason: "invalid-input" };
|
|
32
|
+
const changed = deps.db
|
|
33
|
+
.prepare("UPDATE channels SET episode_memory=? WHERE id=?")
|
|
34
|
+
.run(parsed.data.enabled ? 1 : 0, channelId);
|
|
35
|
+
if (Number(changed.changes) === 0)
|
|
36
|
+
return { ok: false, reason: "not-found" };
|
|
37
|
+
return channelEpisodes(deps, channelId);
|
|
38
|
+
}
|
|
39
|
+
export function editMemory(deps, channelId, memoryId, input) {
|
|
40
|
+
const parsed = editSchema.safeParse(input);
|
|
41
|
+
if (!parsed.success)
|
|
42
|
+
return { ok: false, reason: "invalid-input", message: parsed.error.issues[0]?.message };
|
|
43
|
+
const changed = deps.db
|
|
44
|
+
.prepare("UPDATE episode_memories SET summary=?,source='edited',updated_at=? WHERE id=? AND channel_id=?")
|
|
45
|
+
.run(parsed.data.summary, deps.clock.now().toISOString(), memoryId, channelId);
|
|
46
|
+
const saved = memoryById(deps.db, memoryId);
|
|
47
|
+
if (Number(changed.changes) === 0 || saved === undefined)
|
|
48
|
+
return { ok: false, reason: "not-found" };
|
|
49
|
+
return { ok: true, value: saved };
|
|
50
|
+
}
|
|
51
|
+
export function deleteMemory(deps, channelId, memoryId) {
|
|
52
|
+
const changed = deps.db
|
|
53
|
+
.prepare("DELETE FROM episode_memories WHERE id=? AND channel_id=?")
|
|
54
|
+
.run(memoryId, channelId);
|
|
55
|
+
return Number(changed.changes) === 0
|
|
56
|
+
? { ok: false, reason: "not-found" }
|
|
57
|
+
: { ok: true, value: { deleted: true } };
|
|
58
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { projectById } from "../admission/repo.js";
|
|
4
|
+
import { castMentions } from "../channels/cast-match.js";
|
|
5
|
+
import { castOfChannel, projectChannelId } from "../channels/repo.js";
|
|
6
|
+
import { outputPath } from "../storage/layout.js";
|
|
7
|
+
import { outputsOf } from "../storage/repo.js";
|
|
8
|
+
import { episodeMemoryOn, memoryOfProject, saveGeneratedMemory } from "./repo.js";
|
|
9
|
+
// When a project finishes, a small LLM call writes what the episode covered (at most
|
|
10
|
+
// `summaryWordsMax` words) for its channel's episode memory. It never fails the project: every
|
|
11
|
+
// failure is logged and the project stays done.
|
|
12
|
+
export const summaryWordsMax = 150;
|
|
13
|
+
// Part of the cached recipe: raising it makes every finished article summarised afresh.
|
|
14
|
+
const promptVersion = 1;
|
|
15
|
+
// Only the start of a long article is sent; the summary names what it covers, not every detail.
|
|
16
|
+
const articleCharsMax = 24_000;
|
|
17
|
+
const timeoutMs = 3 * 60_000;
|
|
18
|
+
export function summaryMessages(title, article) {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
role: "system",
|
|
22
|
+
content: `You keep a YouTube channel's episode log. Answer with the summary alone: plain prose, at most ${String(summaryWordsMax)} words, no heading, no list.`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
role: "user",
|
|
26
|
+
content: [
|
|
27
|
+
`Summarise the episode "${title}" for the writers of later episodes: what it covered, the key facts and claims it made, the people, characters and places it featured and what happened to them, and how it ended. Later episodes read this so they don't contradict it.`,
|
|
28
|
+
"",
|
|
29
|
+
"The episode's text:",
|
|
30
|
+
"",
|
|
31
|
+
article.length > articleCharsMax ? `${article.slice(0, articleCharsMax)}…` : article,
|
|
32
|
+
].join("\n"),
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
// Trims a summary that ran long to its first `summaryWordsMax` words.
|
|
37
|
+
export function clampSummary(text) {
|
|
38
|
+
const words = text.replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
|
|
39
|
+
return words.length <= summaryWordsMax
|
|
40
|
+
? words.join(" ")
|
|
41
|
+
: `${words.slice(0, summaryWordsMax).join(" ")}…`;
|
|
42
|
+
}
|
|
43
|
+
export async function summarizeEpisode(deps, projectId, signal) {
|
|
44
|
+
try {
|
|
45
|
+
return await summarize(deps, projectId, signal);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
// Slopify is closing; the next finish of this project writes it.
|
|
49
|
+
if (signal?.aborted === true)
|
|
50
|
+
return "failed";
|
|
51
|
+
deps.log.write("warn", "episode.memory", {
|
|
52
|
+
projectId,
|
|
53
|
+
detail: `The episode summary wasn't saved: ${error instanceof Error ? error.message : String(error)}. The project is unaffected; only later episodes won't be reminded of this one. Check the project's text model in Settings → Providers.`,
|
|
54
|
+
});
|
|
55
|
+
return "failed";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function summarize(deps, projectId, signal) {
|
|
59
|
+
const project = projectById(deps.db, projectId);
|
|
60
|
+
if (project === undefined)
|
|
61
|
+
return "gone";
|
|
62
|
+
const channelId = projectChannelId(deps.db, projectId);
|
|
63
|
+
if (!episodeMemoryOn(deps.db, channelId))
|
|
64
|
+
return "off";
|
|
65
|
+
const existing = memoryOfProject(deps.db, projectId);
|
|
66
|
+
if (existing?.source === "edited")
|
|
67
|
+
return "edited";
|
|
68
|
+
const article = articleText(deps, projectId);
|
|
69
|
+
if (article === undefined)
|
|
70
|
+
return "no-article";
|
|
71
|
+
const recipe = createHash("sha256")
|
|
72
|
+
.update(JSON.stringify([promptVersion, project.title, article]))
|
|
73
|
+
.digest("hex");
|
|
74
|
+
if (existing?.recipe === recipe)
|
|
75
|
+
return "unchanged";
|
|
76
|
+
const choice = llmOf(project.config);
|
|
77
|
+
if (choice === undefined) {
|
|
78
|
+
deps.log.write("info", "episode.memory", {
|
|
79
|
+
projectId,
|
|
80
|
+
detail: "No episode summary: this project has no text model chosen, so later episodes won't be reminded of it.",
|
|
81
|
+
});
|
|
82
|
+
return "no-llm";
|
|
83
|
+
}
|
|
84
|
+
const answer = await deps.llm({
|
|
85
|
+
owner: { kind: "channel", id: channelId },
|
|
86
|
+
purpose: "episode-summary",
|
|
87
|
+
provider: choice.provider,
|
|
88
|
+
model: choice.model,
|
|
89
|
+
messages: summaryMessages(project.title, article),
|
|
90
|
+
signal: AbortSignal.any([
|
|
91
|
+
AbortSignal.timeout(timeoutMs),
|
|
92
|
+
...(signal === undefined ? [] : [signal]),
|
|
93
|
+
]),
|
|
94
|
+
});
|
|
95
|
+
const summary = clampSummary(answer.text);
|
|
96
|
+
if (summary === "")
|
|
97
|
+
throw new Error(`${choice.provider} (${choice.model}) answered with nothing`);
|
|
98
|
+
// Read again: the project may have been deleted, or the summary edited, while the LLM wrote.
|
|
99
|
+
if (projectById(deps.db, projectId) === undefined)
|
|
100
|
+
return "gone";
|
|
101
|
+
if (memoryOfProject(deps.db, projectId)?.source === "edited")
|
|
102
|
+
return "edited";
|
|
103
|
+
saveGeneratedMemory(deps.db, {
|
|
104
|
+
id: existing?.id ?? deps.uuid(),
|
|
105
|
+
channelId,
|
|
106
|
+
projectId,
|
|
107
|
+
title: project.title,
|
|
108
|
+
summary,
|
|
109
|
+
cast: castMentions(`${project.title}\n${article}`, castOf(deps.db, project.config, channelId)).map((member) => member.name),
|
|
110
|
+
recipe,
|
|
111
|
+
}, deps.clock.now().toISOString());
|
|
112
|
+
return "saved";
|
|
113
|
+
}
|
|
114
|
+
// The project's own text model; failing that, the one its automatic reviews use.
|
|
115
|
+
function llmOf(config) {
|
|
116
|
+
if (config.llm !== undefined)
|
|
117
|
+
return config.llm;
|
|
118
|
+
return config.reviews === undefined
|
|
119
|
+
? undefined
|
|
120
|
+
: { provider: config.reviews.provider, model: config.reviews.model };
|
|
121
|
+
}
|
|
122
|
+
// The cast the run was started with, else the channel's cast as it is now.
|
|
123
|
+
function castOf(db, config, channelId) {
|
|
124
|
+
if (config.cast !== undefined && config.cast.length > 0)
|
|
125
|
+
return config.cast;
|
|
126
|
+
return castOfChannel(db, channelId).map((member) => ({
|
|
127
|
+
name: member.name,
|
|
128
|
+
aliases: member.aliases,
|
|
129
|
+
description: member.description,
|
|
130
|
+
images: [],
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
function articleText(deps, projectId) {
|
|
134
|
+
const output = outputsOf(deps.db, projectId).find((row) => row.role === "article_txt");
|
|
135
|
+
if (output === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
const path = outputPath(deps.paths, projectId, output.path);
|
|
138
|
+
if (!existsSync(path))
|
|
139
|
+
return undefined;
|
|
140
|
+
const text = readFileSync(path, "utf8").trim();
|
|
141
|
+
return text === "" ? undefined : text;
|
|
142
|
+
}
|
|
143
|
+
export function createEpisodeMemoryWatcher(deps) {
|
|
144
|
+
const inflight = new Map();
|
|
145
|
+
const stop = new AbortController();
|
|
146
|
+
return {
|
|
147
|
+
observe: (event) => {
|
|
148
|
+
if (stop.signal.aborted || event.type !== "project.state" || event.state !== "done")
|
|
149
|
+
return;
|
|
150
|
+
const projectId = event.projectId;
|
|
151
|
+
if (inflight.has(projectId))
|
|
152
|
+
return;
|
|
153
|
+
const running = (async () => {
|
|
154
|
+
// Off the emitter's call stack: it may be inside a transaction.
|
|
155
|
+
await Promise.resolve();
|
|
156
|
+
await summarizeEpisode(deps, projectId, stop.signal);
|
|
157
|
+
})().finally(() => {
|
|
158
|
+
inflight.delete(projectId);
|
|
159
|
+
});
|
|
160
|
+
inflight.set(projectId, running);
|
|
161
|
+
},
|
|
162
|
+
settled: async () => {
|
|
163
|
+
while (inflight.size > 0)
|
|
164
|
+
await Promise.all([...inflight.values()]);
|
|
165
|
+
},
|
|
166
|
+
close: () => {
|
|
167
|
+
stop.abort();
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { sourceOf } from "../admission/model.js";
|
|
3
|
-
import { imageSecondsProblem, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
|
|
2
|
+
import { sourceOf, thumbnailCountOf } from "../admission/model.js";
|
|
3
|
+
import { imageSecondsProblem, usesDescribedNarration, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, } from "../admission/rules.js";
|
|
4
4
|
import { plainText } from "../article/plain.js";
|
|
5
5
|
import { splitEndMatter } from "../article/split.js";
|
|
6
|
+
import { narrationMinutes, plannedImageCount, scalesImages } from "../images/scale.js";
|
|
7
|
+
import { describedBlocks, narrationBlocks } from "../narration/blocks.js";
|
|
6
8
|
import { chunkNarration, defaultChunking } from "../narration/chunk.js";
|
|
9
|
+
import { describeMessages } from "../narration/describe.js";
|
|
7
10
|
import { normalizeNarrationText } from "../narration/plan.js";
|
|
8
11
|
import { preparationMessages } from "../narration/preparation.js";
|
|
12
|
+
import { defaultReviewPrompts, reviewPromptKey, reviewRetriesOf } from "../reviews/model.js";
|
|
13
|
+
import { activeReviewStages } from "../reviews/rules.js";
|
|
14
|
+
import { isLocalCliProvider } from "../settings/model.js";
|
|
9
15
|
import { defaultShortsImagePrompt, defaultShortsPrompt, shortsImageUpperBound, shortsSettingsProblems, } from "../shorts/model.js";
|
|
10
16
|
import { animatedClipSeconds, animatedImageIndexes, usesAnimation, videoEditProblems, } from "../video/edit-settings.js";
|
|
17
|
+
import { usesVoices } from "../voices/model.js";
|
|
11
18
|
import { defaultDescriptionPrompt } from "../youtube/model.js";
|
|
12
19
|
// ceiling: the most chapters "Chapter openers" is charged for before the article exists, the
|
|
13
20
|
// top of the 5-12 chapters the built-in YouTube description prompt asks for.
|
|
@@ -28,19 +35,27 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
28
35
|
const llm = draft.llm ?? { provider: "", model: "" };
|
|
29
36
|
const tts = draft.audio ?? { provider: "", model: "" };
|
|
30
37
|
const image = draft.images ?? { provider: "", model: "" };
|
|
31
|
-
const textNote =
|
|
32
|
-
|
|
33
|
-
model.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const textNote = isLocalCliProvider(llm.provider)
|
|
39
|
+
? "Runs on your CLI plan, so it adds no charge; the API figure is what the same tokens would cost through the API."
|
|
40
|
+
: (data.llm.find((model) => model.provider === llm.provider &&
|
|
41
|
+
model.id === llm.model &&
|
|
42
|
+
model.enabled &&
|
|
43
|
+
!model.deprecated)?.pricing.note ??
|
|
44
|
+
"CLI subscription or model pricing is unavailable; usage may be billed by your account.");
|
|
45
|
+
const imageNote = isLocalCliProvider(image.provider)
|
|
46
|
+
? "Runs on your Codex plan, so it adds no charge; no API price is given for agent-drawn images."
|
|
47
|
+
: (data.image.find((model) => model.provider === image.provider &&
|
|
48
|
+
model.id === image.model &&
|
|
49
|
+
model.enabled &&
|
|
50
|
+
!model.deprecated)?.pricing.note ?? "Model or account pricing is unknown.");
|
|
40
51
|
const generatedArticle = draft.sources.article === "generate";
|
|
41
52
|
const articleChars = generatedArticle ? expectedWords * 6 : (draft.provided.article?.length ?? 0);
|
|
42
53
|
const promptChars = Object.entries(rendered).reduce((sum, [key, value]) => sum +
|
|
43
|
-
(key === "narration" ||
|
|
54
|
+
(key === "narration" ||
|
|
55
|
+
key === "description" ||
|
|
56
|
+
key === "shorts" ||
|
|
57
|
+
key === "shortsImage" ||
|
|
58
|
+
key.startsWith("review.")
|
|
44
59
|
? 0
|
|
45
60
|
: value.length), 0);
|
|
46
61
|
const local = (stage, detail) => {
|
|
@@ -67,6 +82,25 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
67
82
|
local("Article", "Provided article; no generation charge.");
|
|
68
83
|
if (draft.intro?.mode === "llm" || draft.outro?.mode === "llm")
|
|
69
84
|
text("Intro / outro text", promptChars + articleChars, 2400);
|
|
85
|
+
if (usesDescribedNarration(draft)) {
|
|
86
|
+
// One call per table, figure, equation or code block: counted in a provided article,
|
|
87
|
+
// unknown until a generated one is written.
|
|
88
|
+
const blocks = generatedArticle
|
|
89
|
+
? undefined
|
|
90
|
+
: describedBlocks(narrationBlocks(splitEndMatter(draft.provided.article ?? "").body, {
|
|
91
|
+
code: draft.audio?.skipCode === true ? "skip" : "describe",
|
|
92
|
+
language: draft.language,
|
|
93
|
+
}));
|
|
94
|
+
if (blocks === undefined)
|
|
95
|
+
requests.push({
|
|
96
|
+
kind: "unknown",
|
|
97
|
+
stage: "Narration descriptions",
|
|
98
|
+
detail: "One LLM call per table, figure, equation or code block in the article; how many is known once the article is written.",
|
|
99
|
+
});
|
|
100
|
+
else
|
|
101
|
+
for (const block of blocks)
|
|
102
|
+
text("Narration descriptions", describeMessages(block, rendered.narration ?? "", draft.language).reduce((n, message) => n + message.content.length, 0), 600, `${String(blocks.length)} LLM ${blocks.length === 1 ? "call" : "calls"}: one per table, figure, equation or code block.`);
|
|
103
|
+
}
|
|
70
104
|
if (draft.sources.audio === "generate") {
|
|
71
105
|
if (usesNarrationPreparation(draft)) {
|
|
72
106
|
const known = generatedArticle
|
|
@@ -86,7 +120,7 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
86
120
|
});
|
|
87
121
|
else
|
|
88
122
|
for (const source of known)
|
|
89
|
-
text("Narration Preparation", preparationMessages(rendered.narration ?? "", source).reduce((n, message) => n + message.content.length, 0), 2400, `${known.length} LLM calls: one per logical narration chunk or entry.`);
|
|
123
|
+
text("Narration Preparation", preparationMessages(rendered.narration ?? "", source, [], draft.language).reduce((n, message) => n + message.content.length, 0), 2400, `${known.length} LLM calls: one per logical narration chunk or entry.`);
|
|
90
124
|
requests.push({
|
|
91
125
|
kind: "unknown",
|
|
92
126
|
stage: "Delivery cue overhead",
|
|
@@ -96,18 +130,38 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
96
130
|
const extras = ["intro", "outro"].reduce((n, key) => n + (rendered[key]?.length ?? 0), 0);
|
|
97
131
|
const llmExtras = (draft.intro?.mode === "llm" ? 1200 : 0) + (draft.outro?.mode === "llm" ? 1200 : 0);
|
|
98
132
|
const choice = { stage: "Narration", provider: tts.provider, model: tts.model };
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
133
|
+
const speakers = usesVoices(draft) ? (draft.voices?.speakers ?? []) : [];
|
|
134
|
+
if (speakers.length > 0) {
|
|
135
|
+
// Each speaker's voice is priced on an equal share of the script; the intro and outro
|
|
136
|
+
// keep the narration voice.
|
|
137
|
+
for (const speaker of speakers)
|
|
138
|
+
requests.push({
|
|
139
|
+
kind: "tts-estimate",
|
|
140
|
+
stage: "Narration",
|
|
141
|
+
provider: speaker.voice.provider,
|
|
142
|
+
model: speaker.voice.model,
|
|
143
|
+
characters: Math.round(articleChars / speakers.length),
|
|
144
|
+
detail: `${speaker.name.trim() || "A speaker"}: assumes every speaker says about the same.`,
|
|
145
|
+
});
|
|
146
|
+
if (extras + llmExtras > 0)
|
|
147
|
+
requests.push({ ...choice, kind: "tts-estimate", characters: extras + llmExtras });
|
|
148
|
+
if (draft.voices?.source === "attribute")
|
|
149
|
+
text("Speaker split", articleChars + 1500, Math.round(articleChars * 1.1), "One LLM call hands the text's passages to the speakers.");
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
requests.push(generatedArticle || llmExtras > 0
|
|
153
|
+
? { ...choice, kind: "tts-estimate", characters: articleChars + extras + llmExtras }
|
|
154
|
+
: {
|
|
155
|
+
...choice,
|
|
156
|
+
kind: "tts",
|
|
157
|
+
text: (draft.provided.article ?? "") + (rendered.intro ?? "") + (rendered.outro ?? ""),
|
|
158
|
+
});
|
|
106
159
|
}
|
|
107
160
|
else
|
|
108
161
|
local("Narration", "Provided or off; no generation charge.");
|
|
109
162
|
const images = draft.sources.images === "generate"
|
|
110
|
-
?
|
|
163
|
+
? // Each prompt's Number, or more for a long video (`images/scale.ts`).
|
|
164
|
+
plannedImageCount(draft)
|
|
111
165
|
: 0;
|
|
112
166
|
z.number().int().nonnegative().parse(images);
|
|
113
167
|
for (let index = 0; index < images; index++)
|
|
@@ -116,7 +170,9 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
116
170
|
stage: "Images",
|
|
117
171
|
provider: image.provider,
|
|
118
172
|
model: image.model,
|
|
119
|
-
detail:
|
|
173
|
+
detail: scalesImages(draft)
|
|
174
|
+
? `${images} images for about ${String(Math.round(narrationMinutes(draft.imageScale.words)))} minutes of narration (${draft.imageScale.words.toLocaleString()} words). ${imageNote}`
|
|
175
|
+
: `${images} images. ${imageNote}`,
|
|
120
176
|
});
|
|
121
177
|
if (images === 0)
|
|
122
178
|
local("Images", "Provided or off.");
|
|
@@ -129,12 +185,13 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
129
185
|
model: image.model,
|
|
130
186
|
});
|
|
131
187
|
if (["from_prompt", "prompt_by_llm"].includes(draft.sources.thumbnail))
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
188
|
+
for (let variant = 1; variant <= thumbnailCountOf(draft); variant++)
|
|
189
|
+
requests.push({
|
|
190
|
+
kind: "image",
|
|
191
|
+
stage: "Thumbnail",
|
|
192
|
+
provider: image.provider,
|
|
193
|
+
model: image.model,
|
|
194
|
+
});
|
|
138
195
|
else
|
|
139
196
|
local("Thumbnail", "Provided or off.");
|
|
140
197
|
if (draft.sources.thumbnail === "prompt_by_llm")
|
|
@@ -195,6 +252,30 @@ export function estimateRun(draft, rendered, expectedWords, catalogue) {
|
|
|
195
252
|
: `Up to ${String(clips)} clips of ${String(animatedClipSeconds)} seconds, one per chapter opening. ${note}`,
|
|
196
253
|
});
|
|
197
254
|
}
|
|
255
|
+
// Automatic reviews: one call per reviewed item, on the reviewer's model. A failed item
|
|
256
|
+
// made again is reviewed again, at most the retries allowed, which is not priced here.
|
|
257
|
+
const reviews = draft.reviews;
|
|
258
|
+
if (reviews !== undefined)
|
|
259
|
+
for (const stage of activeReviewStages(draft)) {
|
|
260
|
+
const picked = rendered[reviewPromptKey(stage)]?.trim() ?? "";
|
|
261
|
+
const prompt = picked === "" ? defaultReviewPrompts[stage].length : picked.length;
|
|
262
|
+
const items = stage === "images" ? images : stage === "shorts" ? (draft.shorts?.count ?? 0) : 1;
|
|
263
|
+
const material = stage === "article"
|
|
264
|
+
? articleChars + promptChars
|
|
265
|
+
: stage === "narration"
|
|
266
|
+
? Math.round(articleChars * 2.1)
|
|
267
|
+
: 1500;
|
|
268
|
+
for (let index = 0; index < items; index++)
|
|
269
|
+
requests.push({
|
|
270
|
+
kind: "llm",
|
|
271
|
+
stage: "Reviews",
|
|
272
|
+
provider: reviews.provider,
|
|
273
|
+
model: reviews.model,
|
|
274
|
+
inputCharacters: prompt + material + 800,
|
|
275
|
+
outputCharacters: 400,
|
|
276
|
+
detail: `One review per item. A failed item made again is reviewed again, up to ${String(reviewRetriesOf(reviews))} times; those remakes are not in this estimate. ${textNote}`,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
198
279
|
if (sourceOf(draft.sources, "document") === "generate")
|
|
199
280
|
local("Document", "Laid out locally from the article; no API fee.");
|
|
200
281
|
return {
|