@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
|
@@ -4,6 +4,10 @@ import remarkGfm from "remark-gfm";
|
|
|
4
4
|
// https://docs.inworld.ai/tts/capabilities/custom-pronunciation
|
|
5
5
|
const ipaAtom = "[ˈˌ]?[abdefghijklmnoprstuvwxzæðŋθɑɒɔəɚɛɜɝɡɪɹʃʊʌʒʔɫɾ][\\u0303\\u031a\\u0325\\u0329\\u032a\\u032c\\u032f\\u035c\\u0361ʰʲʷ]*[ːˑ]?";
|
|
6
6
|
const ipaSymbols = new RegExp(`^(?:${ipaAtom})+(?:\\.(?:${ipaAtom})+)*$`, "u");
|
|
7
|
+
// Another language's glossary needs that language's sounds: the full IPA consonant and vowel
|
|
8
|
+
// charts (ç, ʁ, ø, y, ɲ, ʎ, x, β, …) and its diacritics, still never ARPAbet or tags.
|
|
9
|
+
const worldAtom = "[ˈˌ]?[a-zæçðøħŋœɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟɠɡɢɣɤɥɦɧɨɪɫɬɭɮɯɰɱɲɳɴɵɶɸɹɺɻɽɾʀʁʂʃʄʈʉʊʋʌʍʎʏʐʑʒʔʕʙʛʜʝʟʡʢβθχ][\\u0300-\\u036fʰʱʲʷʼˠˤ˞]*[ːˑ]?";
|
|
10
|
+
const worldSymbols = new RegExp(`^(?:${worldAtom})+(?:\\.(?:${worldAtom})+)*$`, "u");
|
|
7
11
|
function textOf(node) {
|
|
8
12
|
if (node.type === "break")
|
|
9
13
|
return "\n";
|
|
@@ -18,7 +22,12 @@ function rowsOf(node) {
|
|
|
18
22
|
const rows = node.children ?? [];
|
|
19
23
|
return rows.slice(1).map((row) => {
|
|
20
24
|
const cells = row.children ?? [];
|
|
21
|
-
|
|
25
|
+
if (cells.length < 2)
|
|
26
|
+
return textOf(row);
|
|
27
|
+
const [term, ipa] = cells.slice(0, 2).map((cell) => textOf(cell).trim());
|
|
28
|
+
// A table's IPA column is often written without the slashes ("| Tiamat | ˈtiːəmɑːt |");
|
|
29
|
+
// the column already says what the cell is, so a bare cell reads as one /…/ pronunciation.
|
|
30
|
+
return `${term}: ${ipa !== undefined && ipa !== "" && !ipa.includes("/") ? `/${ipa}/` : ipa}`;
|
|
22
31
|
});
|
|
23
32
|
}
|
|
24
33
|
if (node.type === "paragraph")
|
|
@@ -27,16 +36,6 @@ function rowsOf(node) {
|
|
|
27
36
|
return (node.children ?? []).flatMap(rowsOf);
|
|
28
37
|
return ["(unsupported glossary block)"];
|
|
29
38
|
}
|
|
30
|
-
function refused(row, reason) {
|
|
31
|
-
return {
|
|
32
|
-
ok: false,
|
|
33
|
-
reason: "Pronunciation Glossary entry " +
|
|
34
|
-
row +
|
|
35
|
-
": " +
|
|
36
|
-
reason +
|
|
37
|
-
". Edit the glossary or turn off Use Pronunciation Glossary.",
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
39
|
function termIdentity(term) {
|
|
41
40
|
return Array.from(term, (character) => {
|
|
42
41
|
const lower = character.toLowerCase();
|
|
@@ -48,10 +47,19 @@ function termIdentity(term) {
|
|
|
48
47
|
return match.test(folded) ? folded : match.test(lower) ? lower : character;
|
|
49
48
|
}).join("");
|
|
50
49
|
}
|
|
51
|
-
|
|
50
|
+
// `language` is the project's (`kernel/ports/languages.ts`): absent or English accepts the
|
|
51
|
+
// standard-English IPA Inworld asks for, as it always did; any other accepts full IPA.
|
|
52
|
+
export function parsePronunciationGlossary(markdown, language) {
|
|
53
|
+
const english = language === undefined || language === "en";
|
|
54
|
+
const symbols = english ? ipaSymbols : worldSymbols;
|
|
55
|
+
const kind = english ? "standard-English IPA" : "IPA";
|
|
52
56
|
const rows = rowsOf(remark().use(remarkGfm).parse(markdown));
|
|
53
57
|
const entries = new Map();
|
|
58
|
+
const skipped = [];
|
|
54
59
|
for (const [index, row] of rows.entries()) {
|
|
60
|
+
const skip = (reason) => {
|
|
61
|
+
skipped.push({ row: index + 1, reason });
|
|
62
|
+
};
|
|
55
63
|
const trimmed = row.trim();
|
|
56
64
|
if (trimmed === "" || /^pronunciation glossary:?\s*$/iu.test(trimmed))
|
|
57
65
|
continue;
|
|
@@ -61,24 +69,63 @@ export function parsePronunciationGlossary(markdown) {
|
|
|
61
69
|
if (term === undefined ||
|
|
62
70
|
pronunciation === undefined ||
|
|
63
71
|
!/[\p{L}\p{N}]/u.test(term) ||
|
|
64
|
-
/[/[\]<>\p{Cc}]/u.test(term))
|
|
65
|
-
|
|
72
|
+
/[/[\]<>\p{Cc}]/u.test(term)) {
|
|
73
|
+
skip("use Term: /IPA/ or a Term | IPA table");
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
66
76
|
const notation = /^(\/[^/]+\/(?:\s+\/[^/]+\/)*)(?:\s+[^/]+)?$/u.exec(pronunciation);
|
|
67
|
-
if (notation?.[1] === undefined)
|
|
68
|
-
|
|
77
|
+
if (notation?.[1] === undefined) {
|
|
78
|
+
skip(`use slash-delimited ${kind}`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
69
81
|
const ipa = Array.from(notation[1].matchAll(/\/([^/]+)\//gu)).flatMap((match) => (match[1] ?? "").trim().split(/\s+/u));
|
|
70
|
-
if (ipa.length === 0 || ipa.some((word) => !
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
if (ipa.length === 0 || ipa.some((word) => !symbols.test(word))) {
|
|
83
|
+
skip(english
|
|
84
|
+
? "use standard-English IPA only, not ARPAbet, delivery tags or non-English sounds; give a foreign name an English approximation"
|
|
85
|
+
: "use IPA, not ARPAbet or delivery tags");
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (term.split(" ").length !== ipa.length) {
|
|
89
|
+
skip("supply one IPA word for each written word");
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
74
92
|
const key = termIdentity(term);
|
|
75
93
|
const previous = entries.get(key);
|
|
76
|
-
if (previous !== undefined && previous.ipa.join(" ") !== ipa.join(" "))
|
|
77
|
-
|
|
94
|
+
if (previous !== undefined && previous.ipa.join(" ") !== ipa.join(" ")) {
|
|
95
|
+
skip("an earlier entry already gives this term a different pronunciation");
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
78
98
|
if (previous === undefined)
|
|
79
99
|
entries.set(key, { term, ipa });
|
|
80
100
|
}
|
|
81
|
-
return
|
|
101
|
+
return skipped.length === 0
|
|
102
|
+
? { ok: true, entries: [...entries.values()] }
|
|
103
|
+
: { ok: true, entries: [...entries.values()], skipped };
|
|
104
|
+
}
|
|
105
|
+
// What a person reads about the skipped rows: where they are and why, never their text.
|
|
106
|
+
export function skippedGlossaryNotice(skipped) {
|
|
107
|
+
if (skipped.length === 0)
|
|
108
|
+
return null;
|
|
109
|
+
const rows = skipped.map((one) => `entry ${one.row}: ${one.reason}`).join("; ");
|
|
110
|
+
return (`Pronunciation Glossary: ${skipped.length === 1 ? "1 entry is" : `${skipped.length} entries are`} ` +
|
|
111
|
+
`skipped and read as ordinary text (${rows}). The rest of the glossary is used. ` +
|
|
112
|
+
"To use them, fix those entries in the Pronunciation Glossary at the end of the article in Edit project → Article.");
|
|
113
|
+
}
|
|
114
|
+
export function skippedSpeakerPronunciations(speakers, language) {
|
|
115
|
+
return speakers.flatMap((speaker) => {
|
|
116
|
+
if (!speaker.pronunciations?.trim())
|
|
117
|
+
return [];
|
|
118
|
+
const parsed = parsePronunciationGlossary(speaker.pronunciations, language);
|
|
119
|
+
const skipped = parsed.ok ? (parsed.skipped ?? []) : [];
|
|
120
|
+
return skipped.length === 0 ? [] : [{ speaker: speaker.name.trim() || "A speaker", skipped }];
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export function skippedSpeakerPronunciationsNotice(rows) {
|
|
124
|
+
if (rows.length === 0)
|
|
125
|
+
return null;
|
|
126
|
+
const parts = rows.map((row) => `${row.speaker}: ${row.skipped.map((one) => `entry ${one.row}: ${one.reason}`).join("; ")}`);
|
|
127
|
+
return (`Speaker pronunciations: some entries are skipped and read as ordinary text (${parts.join(". ")}). ` +
|
|
128
|
+
"The rest are used. To use them, fix those entries under Speakers → Pronunciations for that speaker (Play → Audio, or Edit project → Providers).");
|
|
82
129
|
}
|
|
83
130
|
export function pronunciationMatches(source, entries) {
|
|
84
131
|
if (entries.length === 0)
|
|
@@ -105,7 +152,7 @@ export function pronunciationMatches(source, entries) {
|
|
|
105
152
|
continue;
|
|
106
153
|
const entry = ordered.find((_, index) => match[index + 1] !== undefined);
|
|
107
154
|
if (entry === undefined)
|
|
108
|
-
throw new Error("Slopify hit an internal error (a pronunciation glossary term has no pronunciation).
|
|
155
|
+
throw new Error("Slopify hit an internal error (a pronunciation glossary term has no pronunciation). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
109
156
|
matches.push({ start: match.index, end, entry });
|
|
110
157
|
}
|
|
111
158
|
return matches;
|
|
@@ -117,7 +164,7 @@ export function pronunciationSpans(source, entries) {
|
|
|
117
164
|
for (const [index, word] of Array.from(words).entries()) {
|
|
118
165
|
const ipa = match.entry.ipa[index];
|
|
119
166
|
if (ipa === undefined)
|
|
120
|
-
throw new Error("Slopify hit an internal error (a pronunciation glossary term is missing a word).
|
|
167
|
+
throw new Error("Slopify hit an internal error (a pronunciation glossary term is missing a word). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
121
168
|
const start = match.start + word.index;
|
|
122
169
|
spans.push({ start, end: start + word[0].length, text: `/${ipa}/` });
|
|
123
170
|
}
|
|
@@ -14,7 +14,7 @@ import { planLegacyNarration } from "./legacy-plan.js";
|
|
|
14
14
|
import { observeNarration } from "./live.js";
|
|
15
15
|
// An empty narration source fails the stage immediately with no retries. Thrown from the
|
|
16
16
|
// slice rather than from a provider call, so the attempt wrapper never sees it.
|
|
17
|
-
export const nothingToNarrate = "There is nothing to narrate because the article is empty. Add article text in Edit project → Article, then
|
|
17
|
+
export const nothingToNarrate = "There is nothing to narrate because the article is empty. Add article text in Edit project → Article, then Try again.";
|
|
18
18
|
// What a chunk piece carries between runs: the text that was sent, and the file it came
|
|
19
19
|
// back as once the provider answered. That is the whole of the resume.
|
|
20
20
|
const chunkPayload = z.object({ text: z.string(), file: z.string().optional() });
|
|
@@ -41,7 +41,7 @@ export async function runNarration(deps, context, providers) {
|
|
|
41
41
|
if (choice === undefined) {
|
|
42
42
|
// Admission refuses a run whose audio is Generate without a provider and a voice, so
|
|
43
43
|
// reaching here is a bug in admission rather than something the user did.
|
|
44
|
-
throw new Error("No voice is set for the narration. Choose a voice in Edit project → Providers, then
|
|
44
|
+
throw new Error("No voice is set for the narration. Choose a voice in Edit project → Providers, then Try again.");
|
|
45
45
|
}
|
|
46
46
|
// Read once, before anything is written: the segment steps below ask it what a previous
|
|
47
47
|
// run already stored, and the body step is the only thing that adds to it.
|
|
@@ -168,7 +168,7 @@ async function speakSegments(deps, context, providers, choice, outputs) {
|
|
|
168
168
|
if (text === "") {
|
|
169
169
|
// The same rule, applied to a segment: there is nothing to say, and silently
|
|
170
170
|
// dropping an intro the user picked would lose it without telling anyone.
|
|
171
|
-
throw new Error(`The ${segment.category} is empty, so there is nothing to narrate for it. Write the ${segment.category} or remove it in Edit project, then
|
|
171
|
+
throw new Error(`The ${segment.category} is empty, so there is nothing to narrate for it. Write the ${segment.category} or remove it in Edit project, then Try again.`);
|
|
172
172
|
}
|
|
173
173
|
const spoken = await providers.forPiece(piece.id).tts({
|
|
174
174
|
provider: choice.provider,
|
|
@@ -200,7 +200,7 @@ function segmentPieces(deps, projectId) {
|
|
|
200
200
|
if (article === undefined) {
|
|
201
201
|
// Admission writes all six stage rows with the project, so a project without an article
|
|
202
202
|
// stage is a bug rather than a run the user configured.
|
|
203
|
-
throw new Error("Slopify hit an internal error (the project has no article stage).
|
|
203
|
+
throw new Error("Slopify hit an internal error (the project has no article stage). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
204
204
|
}
|
|
205
205
|
return piecesOf(deps.db, article.id, "segment");
|
|
206
206
|
}
|
|
@@ -257,13 +257,13 @@ function store(deps, projectId, role, path, durationMs, choice) {
|
|
|
257
257
|
}
|
|
258
258
|
function chunkOf(piece) {
|
|
259
259
|
if (piece.payload === null) {
|
|
260
|
-
throw new Error("Slopify hit an internal error (a saved narration chunk is empty).
|
|
260
|
+
throw new Error("Slopify hit an internal error (a saved narration chunk is empty). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
261
261
|
}
|
|
262
262
|
return chunkPayload.parse(JSON.parse(piece.payload));
|
|
263
263
|
}
|
|
264
264
|
function segmentOf(piece) {
|
|
265
265
|
if (piece.payload === null) {
|
|
266
|
-
throw new Error("Slopify hit an internal error (a saved intro or outro is empty).
|
|
266
|
+
throw new Error("Slopify hit an internal error (a saved intro or outro is empty). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
267
267
|
}
|
|
268
268
|
return segmentPayload.parse(JSON.parse(piece.payload));
|
|
269
269
|
}
|
|
@@ -22,7 +22,7 @@ export function collectSharedGlossary(deps, exceptProjectId) {
|
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
24
|
const parsed = parsePronunciationGlossary(text);
|
|
25
|
-
//
|
|
25
|
+
// Rows it had to skip are left out; a glossary that does not parse at all shares nothing.
|
|
26
26
|
if (!parsed.ok)
|
|
27
27
|
continue;
|
|
28
28
|
for (const entry of parsed.entries) {
|
|
@@ -9,21 +9,32 @@ function atoms(source, from, to, spans) {
|
|
|
9
9
|
const span = spans.get(at);
|
|
10
10
|
if (span !== undefined) {
|
|
11
11
|
if (span.end > to || span.end <= at)
|
|
12
|
-
throw new Error("Slopify hit an internal error (a pronunciation fix spans more than one word).
|
|
12
|
+
throw new Error("Slopify hit an internal error (a pronunciation fix spans more than one word). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
13
13
|
result.push({ text: span.text, spokenText: source.slice(at, span.end) });
|
|
14
14
|
at = span.end;
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
17
|
const point = source.codePointAt(at);
|
|
18
18
|
if (point === undefined)
|
|
19
|
-
throw new Error("Slopify hit an internal error (a narration position is outside the article text).
|
|
19
|
+
throw new Error("Slopify hit an internal error (a narration position is outside the article text). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
20
20
|
const text = String.fromCodePoint(point);
|
|
21
|
-
|
|
21
|
+
// An empty span is a later word of a multi-word alias (its spoken form sits on the first
|
|
22
|
+
// word), so the whitespace in front of it would leave two spaces where one word was.
|
|
23
|
+
// It stays in the spoken (transcript) text, which the captions are aligned against.
|
|
24
|
+
result.push({ text: silencedBefore(source, at, spans) ? "" : text, spokenText: text });
|
|
22
25
|
at += text.length;
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
return result;
|
|
26
29
|
}
|
|
30
|
+
function silencedBefore(source, at, spans) {
|
|
31
|
+
if (!/\s/u.test(source[at] ?? ""))
|
|
32
|
+
return false;
|
|
33
|
+
let next = at;
|
|
34
|
+
while (next < source.length && /\s/u.test(source[next] ?? ""))
|
|
35
|
+
next += 1;
|
|
36
|
+
return spans.get(next)?.text === "";
|
|
37
|
+
}
|
|
27
38
|
function checkedSpans(source, pronunciation) {
|
|
28
39
|
const spans = new Map();
|
|
29
40
|
let end = 0;
|
|
@@ -34,7 +45,7 @@ function checkedSpans(source, pronunciation) {
|
|
|
34
45
|
span.end <= span.start ||
|
|
35
46
|
span.end > source.length ||
|
|
36
47
|
!/^[^\s\uD800-\uDFFF]+$/u.test(source.slice(span.start, span.end)))
|
|
37
|
-
throw new Error("Slopify hit an internal error (pronunciation fixes overlap or split a word).
|
|
48
|
+
throw new Error("Slopify hit an internal error (pronunciation fixes overlap or split a word). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
38
49
|
spans.set(span.start, span);
|
|
39
50
|
end = span.end;
|
|
40
51
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { redact } from "../../kernel/log.js";
|
|
2
|
-
import { noticeOf, noticeText, webhookBody } from "./rules.js";
|
|
2
|
+
import { noticeOf, noticeText, topicsNoticeText, webhookBody } from "./rules.js";
|
|
3
3
|
import { sendFailureText } from "./send.js";
|
|
4
4
|
export function createRunNotifier(deps) {
|
|
5
5
|
// ceiling: one entry per project seen since boot, a few dozen bytes each.
|
|
@@ -15,19 +15,22 @@ export function createRunNotifier(deps) {
|
|
|
15
15
|
if (subject === undefined)
|
|
16
16
|
return;
|
|
17
17
|
const text = noticeText(kind, { ...subject, reason: reasons.get(projectId) });
|
|
18
|
+
post(url, text, kind, { projectId });
|
|
19
|
+
};
|
|
20
|
+
const post = (url, text, kind, about) => {
|
|
18
21
|
const body = redact(webhookBody(text));
|
|
19
22
|
const sending = deps
|
|
20
23
|
.send(url, body)
|
|
21
24
|
.then((result) => {
|
|
22
25
|
if (!result.ok)
|
|
23
26
|
deps.log.write("warn", "notification.failed", {
|
|
24
|
-
|
|
27
|
+
...about,
|
|
25
28
|
detail: `${kind} notification not delivered: ${sendFailureText(result)}`,
|
|
26
29
|
});
|
|
27
30
|
})
|
|
28
31
|
.catch((error) => {
|
|
29
32
|
deps.log.write("warn", "notification.failed", {
|
|
30
|
-
|
|
33
|
+
...about,
|
|
31
34
|
detail: `${kind} notification not delivered: ${error instanceof Error ? error.message : String(error)}`,
|
|
32
35
|
});
|
|
33
36
|
})
|
|
@@ -65,6 +68,21 @@ export function createRunNotifier(deps) {
|
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
},
|
|
71
|
+
observeTopics: (event) => {
|
|
72
|
+
if (closed || event.added === 0)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
const url = deps.url();
|
|
76
|
+
if (url === null)
|
|
77
|
+
return;
|
|
78
|
+
post(url, topicsNoticeText(event), "topics", {});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
deps.log.write("warn", "notification.failed", {
|
|
82
|
+
detail: `topics notification skipped: ${error instanceof Error ? error.message : String(error)}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
68
86
|
close: () => {
|
|
69
87
|
closed = true;
|
|
70
88
|
},
|
|
@@ -8,6 +8,8 @@ export function noticeOf(previous, next) {
|
|
|
8
8
|
switch (next) {
|
|
9
9
|
case "done":
|
|
10
10
|
return "ready";
|
|
11
|
+
case "partial":
|
|
12
|
+
return "partial";
|
|
11
13
|
case "failed":
|
|
12
14
|
return "failed";
|
|
13
15
|
case "pending":
|
|
@@ -34,6 +36,16 @@ export function noticeText(kind, subject) {
|
|
|
34
36
|
return subject.makesVideo
|
|
35
37
|
? { headline: `Video ready: ${title}`, detail: "Open the project to watch it." }
|
|
36
38
|
: { headline: `Run finished: ${title}`, detail: "Open the project to see its files." };
|
|
39
|
+
case "partial": {
|
|
40
|
+
const reason = shortReason(subject.reason);
|
|
41
|
+
const made = subject.makesVideo
|
|
42
|
+
? `Video ready with problems: ${title}`
|
|
43
|
+
: `Run finished with problems: ${title}`;
|
|
44
|
+
return {
|
|
45
|
+
headline: reason === undefined ? made : `${made} — ${reason}`,
|
|
46
|
+
detail: "Open the project to see which step failed and the button that fixes it.",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
37
49
|
case "failed": {
|
|
38
50
|
const reason = shortReason(subject.reason);
|
|
39
51
|
return {
|
|
@@ -44,10 +56,19 @@ export function noticeText(kind, subject) {
|
|
|
44
56
|
case "waiting":
|
|
45
57
|
return {
|
|
46
58
|
headline: `Waiting for you: ${title}`,
|
|
47
|
-
detail: "Open the project to review the held step or press
|
|
59
|
+
detail: "Open the project to review the held step or press Continue the run.",
|
|
48
60
|
};
|
|
49
61
|
}
|
|
50
62
|
}
|
|
63
|
+
export function topicsNoticeText(subject) {
|
|
64
|
+
const name = subject.scheduleName.trim() === "" ? "Untitled schedule" : subject.scheduleName.trim();
|
|
65
|
+
const count = subject.added;
|
|
66
|
+
const total = subject.waiting > count ? ` ${String(subject.waiting)} are waiting in all.` : "";
|
|
67
|
+
return {
|
|
68
|
+
headline: `${String(count)} new ${count === 1 ? "topic is" : "topics are"} waiting for you`,
|
|
69
|
+
detail: `Open Calendar → Suggested topics → ${name} to queue or reject ${count === 1 ? "it" : "them"}.${total}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
51
72
|
export const testNotice = {
|
|
52
73
|
headline: "Slopify test notification",
|
|
53
74
|
detail: "Notifications work. You'll get one when a run finishes, fails or waits for you.",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { copyFileSync, lstatSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
3
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
4
|
+
import { executionPlan } from "../rebuild/runtime-plan.js";
|
|
5
|
+
import { currentRevisionId } from "../revisions/repo.js";
|
|
6
|
+
import { getRevisionView } from "../revisions/view.js";
|
|
7
|
+
import { projectRows } from "../storage/backup-export.js";
|
|
8
|
+
import { projectTables } from "../storage/backup-format.js";
|
|
9
|
+
import { projectDir } from "../storage/layout.js";
|
|
10
|
+
// ceiling: one pass per layer of the step graph (text, narration, timing, picks, prompts,
|
|
11
|
+
// images, renders); far more than any plan has.
|
|
12
|
+
const rebindPasses = 40;
|
|
13
|
+
export function copyProject(deps, sourceId, title) {
|
|
14
|
+
const source = projectDir(deps.paths, sourceId);
|
|
15
|
+
const tables = projectTables.map((table) => [table, projectRows(deps.db, table, sourceId)]);
|
|
16
|
+
const ids = new Map([[sourceId, deps.ids.next()]]);
|
|
17
|
+
for (const [, rows] of tables)
|
|
18
|
+
for (const row of rows)
|
|
19
|
+
for (const [column, value] of Object.entries(row))
|
|
20
|
+
if ((column === "id" || column === "publication_id" || column === "admission_id") &&
|
|
21
|
+
typeof value === "string" &&
|
|
22
|
+
// ceiling: every id Slopify makes is a 26-character ULID or a 36-character UUID; a
|
|
23
|
+
// shorter value could match inside unrelated text.
|
|
24
|
+
value.length >= 16 &&
|
|
25
|
+
!ids.has(value))
|
|
26
|
+
ids.set(value, deps.ids.next());
|
|
27
|
+
const pattern = new RegExp([...ids.keys()]
|
|
28
|
+
.toSorted((left, right) => right.length - left.length)
|
|
29
|
+
.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
30
|
+
.join("|"), "g");
|
|
31
|
+
const remap = (text) => text.replace(pattern, (id) => ids.get(id) ?? id);
|
|
32
|
+
const projectId = ids.get(sourceId) ?? sourceId;
|
|
33
|
+
const target = projectDir(deps.paths, projectId);
|
|
34
|
+
const at = deps.clock.now().toISOString();
|
|
35
|
+
try {
|
|
36
|
+
for (const file of listFiles(source)) {
|
|
37
|
+
const to = join(target, remap(file));
|
|
38
|
+
mkdirSync(dirname(to), { recursive: true, mode: 0o700 });
|
|
39
|
+
copyFileSync(join(source, file), to);
|
|
40
|
+
}
|
|
41
|
+
transact(deps.db, () => {
|
|
42
|
+
deps.db.exec("PRAGMA defer_foreign_keys = ON");
|
|
43
|
+
for (const [table, rows] of tables)
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
const copied = Object.fromEntries(Object.entries(row).map(([column, value]) => [
|
|
46
|
+
column,
|
|
47
|
+
typeof value === "string" ? remap(value) : value,
|
|
48
|
+
]));
|
|
49
|
+
insert(deps.db, table, table === "projects" ? { ...copied, title, created_at: at, updated_at: at } : copied);
|
|
50
|
+
}
|
|
51
|
+
rebind(deps, projectId);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
rmSync(target, { recursive: true, force: true });
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
return { projectId };
|
|
59
|
+
}
|
|
60
|
+
// Re-stamps the copy's finished steps with the fingerprints its own ids give them.
|
|
61
|
+
function rebind(deps, projectId) {
|
|
62
|
+
const revisionId = currentRevisionId(deps.db, projectId);
|
|
63
|
+
if (revisionId === undefined)
|
|
64
|
+
return;
|
|
65
|
+
for (let pass = 0; pass < rebindPasses; pass++) {
|
|
66
|
+
const view = getRevisionView(deps, projectId, revisionId);
|
|
67
|
+
if (view === undefined)
|
|
68
|
+
return;
|
|
69
|
+
const plan = executionPlan(deps, view, deps.catalogue);
|
|
70
|
+
const changes = new Map();
|
|
71
|
+
for (const change of plan.changedInputs)
|
|
72
|
+
if (change.before !== null && change.after !== null && change.before !== change.after)
|
|
73
|
+
changes.set(change.before, change.after);
|
|
74
|
+
// A finished piece also records the request it answered, which covers the same ids.
|
|
75
|
+
for (const recipe of plan.recipes)
|
|
76
|
+
for (const piece of view.pieces)
|
|
77
|
+
if (piece.selected && piece.key === recipe.key && piece.piece.payload !== null) {
|
|
78
|
+
const request = requestOf(piece.piece.payload);
|
|
79
|
+
if (request !== undefined &&
|
|
80
|
+
recipe.requestFingerprint !== null &&
|
|
81
|
+
request !== recipe.requestFingerprint)
|
|
82
|
+
changes.set(request, recipe.requestFingerprint);
|
|
83
|
+
}
|
|
84
|
+
if (changes.size === 0) {
|
|
85
|
+
deps.db
|
|
86
|
+
.prepare("UPDATE project_revisions SET fingerprints=? WHERE id=?")
|
|
87
|
+
.run(JSON.stringify(Object.fromEntries(plan.recipes.map((row) => [row.key, row.fingerprint]))), revisionId);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const [before, after] of changes)
|
|
91
|
+
for (const [table, column] of [
|
|
92
|
+
["revision_outputs", "fingerprint"],
|
|
93
|
+
["revision_pieces", "fingerprint"],
|
|
94
|
+
["revision_outputs", "descriptor"],
|
|
95
|
+
["revision_pieces", "descriptor"],
|
|
96
|
+
["project_revisions", "content"],
|
|
97
|
+
])
|
|
98
|
+
deps.db
|
|
99
|
+
.prepare(`UPDATE ${table} SET ${column}=replace(${column},?,?) WHERE project_id=? AND instr(${column},?)>0`)
|
|
100
|
+
.run(before, after, projectId, before);
|
|
101
|
+
}
|
|
102
|
+
throw new Error("Slopify hit an internal error (the copy of the sample kept changing while its steps were matched up). Delete the copy and try Make my own copy again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
103
|
+
}
|
|
104
|
+
function requestOf(payload) {
|
|
105
|
+
try {
|
|
106
|
+
const value = JSON.parse(payload);
|
|
107
|
+
return typeof value === "object" &&
|
|
108
|
+
value !== null &&
|
|
109
|
+
"requestFingerprint" in value &&
|
|
110
|
+
typeof value.requestFingerprint === "string"
|
|
111
|
+
? value.requestFingerprint
|
|
112
|
+
: undefined;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function listFiles(root) {
|
|
119
|
+
const out = [];
|
|
120
|
+
const walk = (directory) => {
|
|
121
|
+
let names;
|
|
122
|
+
try {
|
|
123
|
+
names = readdirSync(directory);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const name of names) {
|
|
129
|
+
const path = join(directory, name);
|
|
130
|
+
const stat = lstatSync(path);
|
|
131
|
+
if (stat.isDirectory())
|
|
132
|
+
walk(path);
|
|
133
|
+
else if (stat.isFile())
|
|
134
|
+
out.push(relative(root, path).split(sep).join("/"));
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
walk(root);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
function insert(db, table, row) {
|
|
141
|
+
const columns = Object.keys(row);
|
|
142
|
+
db.prepare(`INSERT INTO ${table} (${columns.join(",")}) VALUES (${columns.map(() => "?").join(",")})`).run(...columns.map((column) => row[column] ?? null));
|
|
143
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { starterPacks } from "./packs.js";
|
|
2
|
+
import { sampleProjectIds } from "./sample.js";
|
|
3
|
+
import { dismissFirstRun, firstRunDismissed, readPackRecords } from "./state.js";
|
|
4
|
+
// The first-run screen shows on a fresh install and never again once it is skipped or a real
|
|
5
|
+
// project (anything but the samples) exists. A real project also dismisses it for good, so
|
|
6
|
+
// deleting every project later does not bring it back.
|
|
7
|
+
export function firstRunView(db, statuses, at) {
|
|
8
|
+
const samples = sampleProjectIds(db);
|
|
9
|
+
const real = db
|
|
10
|
+
.prepare("SELECT 1 FROM projects WHERE id IS NOT ? AND id IS NOT ? AND id IS NOT ? LIMIT 1")
|
|
11
|
+
.get(samples.library, samples.audiobook, samples.podcast) !== undefined;
|
|
12
|
+
if (real)
|
|
13
|
+
dismissFirstRun(db, at);
|
|
14
|
+
const packs = readPackRecords(db);
|
|
15
|
+
return {
|
|
16
|
+
show: !real && !firstRunDismissed(db),
|
|
17
|
+
sampleProjectId: samples.library,
|
|
18
|
+
samples,
|
|
19
|
+
clis: detectedClis(statuses),
|
|
20
|
+
packs: starterPacks.map((pack) => ({
|
|
21
|
+
id: pack.id,
|
|
22
|
+
name: pack.name,
|
|
23
|
+
summary: pack.summary,
|
|
24
|
+
installed: packs[pack.id] !== undefined,
|
|
25
|
+
templateId: packs[pack.id]?.template ?? null,
|
|
26
|
+
})),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// What this machine can make without a key: each agent CLI Slopify found, and whether it
|
|
30
|
+
// answers (installed, a supported version, signed in as far as the probe can tell).
|
|
31
|
+
export function detectedClis(statuses) {
|
|
32
|
+
return ["claude-code", "codex", "gemini"].map((id) => {
|
|
33
|
+
const status = statuses.find((row) => row.id === id);
|
|
34
|
+
const readiness = status?.readiness;
|
|
35
|
+
const cli = readiness?.kind === "cli" ? readiness : undefined;
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
name: status?.displayName ?? id,
|
|
39
|
+
installed: cli?.installed === true,
|
|
40
|
+
ready: cli?.installed === true && cli.issue === undefined,
|
|
41
|
+
version: cli?.version ?? null,
|
|
42
|
+
issue: cli?.issue ?? null,
|
|
43
|
+
draws: id === "codex",
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|