@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
package/dist/edge/docker.js
CHANGED
|
@@ -6,16 +6,21 @@ import { installHostPackage } from "../host-cli/install.js";
|
|
|
6
6
|
import { ensureBridgeToken, hasCode, prepareHostPaths } from "../host-cli/paths.js";
|
|
7
7
|
import { ensureHostService, privateRead, privateWrite } from "../host-cli/service.js";
|
|
8
8
|
import { hostCommandName, resolveHostCommand } from "../host-cli/status.js";
|
|
9
|
-
import {
|
|
9
|
+
import { hostLlmIds } from "../kernel/ports/host-cli.js";
|
|
10
10
|
export function assertManagedDockerHost(platform, uid, gid) {
|
|
11
11
|
if (platform !== "linux")
|
|
12
|
-
throw new Error("The --docker
|
|
12
|
+
throw new Error("The --docker install only works on Linux. On this system, run Slopify without Docker (npx @gentbajko/slopify), or run the compose.yaml from the Slopify repository yourself (docker compose up -d).");
|
|
13
13
|
if (uid === undefined || gid === undefined || uid === 0)
|
|
14
14
|
throw new Error("Don't run the --docker launcher with sudo or as root. Run it as your normal user, and if Docker then says permission denied, give your user access: sudo usermod -aG docker $USER, then log out and back in.");
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Decides whether the Docker installation uses this machine's AI CLIs, asking once for consent,
|
|
18
|
+
* before anything changes. `ensure` then installs or updates the bridge: a private helper, run as
|
|
19
|
+
* the systemd user service slopify-cli-bridge.service, at the same version as the image.
|
|
20
|
+
*/
|
|
21
|
+
export async function planDockerHostCli(options) {
|
|
17
22
|
if (options.disabled)
|
|
18
|
-
return {};
|
|
23
|
+
return { enabled: false };
|
|
19
24
|
let installed = false;
|
|
20
25
|
for (const id of hostLlmIds) {
|
|
21
26
|
try {
|
|
@@ -28,7 +33,7 @@ export async function prepareDockerHostCli(options) {
|
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
if (!installed)
|
|
31
|
-
return {};
|
|
36
|
+
return { enabled: false };
|
|
32
37
|
if (process.platform !== "linux")
|
|
33
38
|
throw new Error("Using this machine's AI CLIs from Docker needs Linux with systemd. Start with --host-cli=off to use API keys only.");
|
|
34
39
|
const uid = process.getuid?.();
|
|
@@ -53,25 +58,12 @@ export async function prepareDockerHostCli(options) {
|
|
|
53
58
|
if (!approved)
|
|
54
59
|
throw new Error("You declined, so Slopify did not set up your host AI CLIs and nothing was changed. Run the launcher again and answer y to allow it, or add --host-cli=off to start with API keys only.");
|
|
55
60
|
}
|
|
56
|
-
const inspect = () => options.runner.exec("docker", [
|
|
57
|
-
"image",
|
|
58
|
-
"inspect",
|
|
59
|
-
"--format",
|
|
60
|
-
'{{index .Config.Labels "io.slopify.host-cli-protocol"}}',
|
|
61
|
-
options.image,
|
|
62
|
-
], options.signal);
|
|
63
|
-
let image = await inspect();
|
|
64
|
-
if (image.code !== 0 || image.stdout.trim() !== String(hostCliProtocol)) {
|
|
65
|
-
const pulled = await options.runner.exec("docker", ["pull", options.image], options.signal);
|
|
66
|
-
if (pulled.code !== 0)
|
|
67
|
-
throw new Error(`Could not download the Slopify Docker image ${options.image}. Check your internet connection and that Docker is running (docker info), then try again. Nothing was changed.`);
|
|
68
|
-
image = await inspect();
|
|
69
|
-
if (image.code !== 0 || image.stdout.trim() !== String(hostCliProtocol))
|
|
70
|
-
throw new Error(`The Docker image ${options.image} is too old to use the AI CLIs on this machine. Use the latest image (unset SLOPIFY_DOCKER_IMAGE, or point it to a current release) and try again, or start with --host-cli=off.`);
|
|
71
|
-
}
|
|
72
61
|
const supported = await options.runner.exec("systemctl", ["--user", "list-units", "--no-legend", "--no-pager", "slopify-cli-bridge.service"], options.signal);
|
|
73
62
|
if (supported.code !== 0)
|
|
74
63
|
throw new Error("Using this machine's AI CLIs from Docker needs systemd user services (systemctl --user), which aren't available here. Start with --host-cli=off to use API keys only, or run Slopify without Docker: npx @gentbajko/slopify.");
|
|
64
|
+
return { enabled: true, ensure: () => ensureDockerHostCli(options, uid, receiptPath) };
|
|
65
|
+
}
|
|
66
|
+
async function ensureDockerHostCli(options, uid, receiptPath) {
|
|
75
67
|
const paths = await prepareHostPaths(options.root);
|
|
76
68
|
const lock = join(paths.root, "setup.lock");
|
|
77
69
|
const deadline = Date.now() + 30_000;
|
|
@@ -107,7 +99,7 @@ export async function prepareDockerHostCli(options) {
|
|
|
107
99
|
env: options.env,
|
|
108
100
|
});
|
|
109
101
|
await privateWrite(receiptPath, JSON.stringify({ version: 1, automaticStartup: true }));
|
|
110
|
-
return
|
|
102
|
+
return paths.share;
|
|
111
103
|
}
|
|
112
104
|
finally {
|
|
113
105
|
await rmdir(lock);
|
package/dist/edge/host-cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import { startHostServer } from "../host-cli/server.js";
|
|
|
12
12
|
import { resolveHostCommand } from "../host-cli/status.js";
|
|
13
13
|
import { readVersion } from "../kernel/version.js";
|
|
14
14
|
import { nodeCliProbe } from "../slices/settings/cli-status.js";
|
|
15
|
-
import { dockerRoot } from "./docker-
|
|
15
|
+
import { dockerRoot } from "./docker-install/state.js";
|
|
16
16
|
const { values } = parseArgs({ options: { "state-dir": { type: "string" } }, strict: true });
|
|
17
17
|
if (!values["state-dir"])
|
|
18
18
|
throw new Error("A host helper state directory is required.");
|
|
@@ -11,6 +11,7 @@ import { revisionControlSchema, } from "../../slices/control/revision-control-sc
|
|
|
11
11
|
import { recoverProject } from "../../slices/rebuild/recovery.js";
|
|
12
12
|
import { recoveryResultSchema } from "../../slices/rebuild/recovery-model.js";
|
|
13
13
|
import { resumable } from "../../slices/rebuild/recovery-repo.js";
|
|
14
|
+
import { clearSoftening, refusedKeys, requestSoftening } from "../../slices/rebuild/soften.js";
|
|
14
15
|
import { adoptBaseline } from "../../slices/revisions/adopt.js";
|
|
15
16
|
import { currentRevisionId } from "../../slices/revisions/repo.js";
|
|
16
17
|
import { allowsCustomModel } from "../../slices/settings/models.js";
|
|
@@ -99,7 +100,7 @@ export function actionRoutes(deps) {
|
|
|
99
100
|
"revision-required": "This page is out of date. Reload the page, then try again.",
|
|
100
101
|
conflict: "This project changed since the page loaded. Reload the page to see the latest, then try again.",
|
|
101
102
|
"idempotency-conflict": "This action was already sent with different details. Reload the page, then try again.",
|
|
102
|
-
"rebuild-required": "This change needs some outputs to be made again. In the Edit tab, use
|
|
103
|
+
"rebuild-required": "This change needs some outputs to be made again. In the Edit tab, use Choose what to remake (in the project's More menu) to see what will be regenerated and what it costs.",
|
|
103
104
|
};
|
|
104
105
|
return problem(c, {
|
|
105
106
|
status: codes[result.reason],
|
|
@@ -130,14 +131,14 @@ export function actionRoutes(deps) {
|
|
|
130
131
|
const messages = {
|
|
131
132
|
"no-project": "This project no longer exists. Go back to Projects to pick another.",
|
|
132
133
|
"no-revision": "The project's saved settings changed since the page loaded. Reload the page, then try again.",
|
|
133
|
-
conflict: "This project changed since the page loaded. Reload the page to see the latest, then use
|
|
134
|
+
conflict: "This project changed since the page loaded. Reload the page to see the latest, then use Continue the run again.",
|
|
134
135
|
"idempotency-conflict": "This action was already sent with different details. Reload the page, then try again.",
|
|
135
136
|
"invalid-edit": "This section has settings that need fixing first. Open the Edit tab, choose Edit project and correct this section, then try again.",
|
|
136
|
-
"stale-preview": "What needs to be made changed since the page loaded. Reload the page, then use
|
|
137
|
+
"stale-preview": "What needs to be made changed since the page loaded. Reload the page, then use Continue the run again.",
|
|
137
138
|
"invalid-selection": "This section has nothing generated to re-run because its content was supplied by you. To change it, open the Edit tab and choose Edit project.",
|
|
138
|
-
"review-required": "Content you supplied or captions you edited by hand would be replaced. Change them in the Edit tab with Edit project, or use
|
|
139
|
-
"cost-ack-required": "This re-run needs you to review its cost first. In the Edit tab, use
|
|
140
|
-
readiness: "Slopify cannot run this yet. Check the provider, model, voice and any files you supplied, then use
|
|
139
|
+
"review-required": "Content you supplied or captions you edited by hand would be replaced. Change them in the Edit tab with Edit project, or use Choose what to remake (in the project's More menu) to confirm the replacement.",
|
|
140
|
+
"cost-ack-required": "This re-run needs you to review its cost first. In the Edit tab, use Choose what to remake (in the project's More menu).",
|
|
141
|
+
readiness: "Slopify cannot run this yet. Check the provider, model, voice and any files you supplied, then use Continue the run.",
|
|
141
142
|
running: "This section is still running. Wait for it to finish, or Pause the project, before re-running it.",
|
|
142
143
|
"accepted-job": "This section has a finished provider job that Slopify has not collected yet.",
|
|
143
144
|
"control-changed": "Another action on this project (such as Pause or Cancel) happened at the same time. Reload the page to see its current state, then try again.",
|
|
@@ -148,7 +149,7 @@ export function actionRoutes(deps) {
|
|
|
148
149
|
...(result.intentRevisionId === undefined
|
|
149
150
|
? []
|
|
150
151
|
: [
|
|
151
|
-
"Your re-run request was saved. Use
|
|
152
|
+
"Your re-run request was saved. Use Continue the run to continue it instead of re-running again.",
|
|
152
153
|
]),
|
|
153
154
|
].join(" ");
|
|
154
155
|
return problem(c, {
|
|
@@ -184,7 +185,7 @@ export function actionRoutes(deps) {
|
|
|
184
185
|
? parsed.data
|
|
185
186
|
: controlled(c, id, { ok: false, reason: "revision-required" });
|
|
186
187
|
};
|
|
187
|
-
return new Hono()
|
|
188
|
+
return (new Hono()
|
|
188
189
|
.post("/:id/pause", zValidator("param", idParam, onInvalid), async (c) => {
|
|
189
190
|
const { id } = c.req.valid("param");
|
|
190
191
|
const input = await controlInput(c, id);
|
|
@@ -208,19 +209,40 @@ export function actionRoutes(deps) {
|
|
|
208
209
|
kind: "retry",
|
|
209
210
|
stage: c.req.valid("param").kind,
|
|
210
211
|
}))
|
|
212
|
+
// Soften and retry: the refused image prompts of this stage are reworded by the project's
|
|
213
|
+
// AI model when the retry draws them again (`slices/rebuild/soften.ts`).
|
|
214
|
+
.post("/:id/stages/:kind/soften", zValidator("param", stageParam, onInvalid), zValidator("json", revisionControlSchema, onInvalid), async (c) => {
|
|
215
|
+
const { id, kind } = c.req.valid("param");
|
|
216
|
+
const keys = kind === "images" || kind === "thumbnail" ? refusedKeys(deps.db, id, kind) : [];
|
|
217
|
+
if (keys.length === 0)
|
|
218
|
+
return problem(c, {
|
|
219
|
+
status: 409,
|
|
220
|
+
title: titleOf(409),
|
|
221
|
+
detail: "Nothing in this section was refused by a content filter, so there is no prompt to soften. Reload the page; if the section still shows an error, use Try again.",
|
|
222
|
+
extensions: { reason: "nothing-refused" },
|
|
223
|
+
});
|
|
224
|
+
requestSoftening(deps.db, id, keys, deps.clock.now().toISOString());
|
|
225
|
+
const response = await recovery(c, id, c.req.valid("json"), {
|
|
226
|
+
kind: "retry",
|
|
227
|
+
stage: kind,
|
|
228
|
+
});
|
|
229
|
+
if (response.status !== 202)
|
|
230
|
+
clearSoftening(deps.db, id, keys);
|
|
231
|
+
return response;
|
|
232
|
+
})
|
|
211
233
|
.post("/:id/stages/:kind/rerun", zValidator("param", stageParam, onInvalid), zValidator("json", revisionControlSchema, onInvalid), (c) => recovery(c, c.req.valid("param").id, c.req.valid("json"), {
|
|
212
234
|
kind: "rerun",
|
|
213
235
|
stage: c.req.valid("param").kind,
|
|
214
236
|
}))
|
|
215
237
|
.put("/:id/article", zValidator("param", idParam, onInvalid), revisionRequired)
|
|
216
238
|
.delete("/:id/images/:outputId", zValidator("param", imageParam, onInvalid), revisionRequired)
|
|
217
|
-
.post("/:id/images/:outputId/regenerate", zValidator("param", imageParam, onInvalid), revisionRequired);
|
|
239
|
+
.post("/:id/images/:outputId/regenerate", zValidator("param", imageParam, onInvalid), revisionRequired));
|
|
218
240
|
}
|
|
219
241
|
function revisionRequired(c) {
|
|
220
242
|
return problem(c, {
|
|
221
243
|
status: 409,
|
|
222
244
|
title: titleOf(409),
|
|
223
|
-
detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use
|
|
245
|
+
detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use Choose what to remake (in the project's More menu).",
|
|
224
246
|
extensions: { reason: "revision-required" },
|
|
225
247
|
});
|
|
226
248
|
}
|
package/dist/edge/http/app.js
CHANGED
|
@@ -2,9 +2,14 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
3
3
|
import { Hono } from "hono";
|
|
4
4
|
import { streamSSE } from "hono/streaming";
|
|
5
|
+
import { isSampleProject } from "../../slices/onboarding/sample.js";
|
|
5
6
|
import { actionRoutes } from "./actions.js";
|
|
6
7
|
import { audioPreviewRoutes } from "./audio-preview.js";
|
|
8
|
+
import { auditionRoutes } from "./auditions.js";
|
|
9
|
+
import { autostartRoutes } from "./autostart.js";
|
|
7
10
|
import { backupRoutes } from "./backups.js";
|
|
11
|
+
import { channelMemoryRoutes } from "./channel-memory.js";
|
|
12
|
+
import { channelRoutes } from "./channels.js";
|
|
8
13
|
import { checkpointRoutes } from "./checkpoints.js";
|
|
9
14
|
import { diagnosticsRoutes } from "./diagnostics.js";
|
|
10
15
|
import { documentThemeRoutes } from "./document-themes.js";
|
|
@@ -12,7 +17,11 @@ import { draftRoutes } from "./drafts.js";
|
|
|
12
17
|
import { entryRoutes } from "./entries.js";
|
|
13
18
|
import { fileRoutes } from "./files.js";
|
|
14
19
|
import { fontsRoutes } from "./fonts.js";
|
|
20
|
+
import { homeRoutes, uploadedRoutes } from "./home.js";
|
|
21
|
+
import { narrationPeakRoutes } from "./narration-peaks.js";
|
|
22
|
+
import { onboardingRoutes } from "./onboarding.js";
|
|
15
23
|
import { openFolderRoutes } from "./open-folder.js";
|
|
24
|
+
import { patchNotesRoutes } from "./patch-notes.js";
|
|
16
25
|
import { planningRoutes } from "./planning.js";
|
|
17
26
|
import { problem, problemFromError, titleOf } from "./problem.js";
|
|
18
27
|
import { projectTemplateRoutes } from "./project-templates.js";
|
|
@@ -20,17 +29,25 @@ import { projectRoutes } from "./projects.js";
|
|
|
20
29
|
import { promptRoutes } from "./prompts.js";
|
|
21
30
|
import { pronunciationRoutes } from "./pronunciations.js";
|
|
22
31
|
import { providerRoutes } from "./providers.js";
|
|
32
|
+
import { reviewRoutes } from "./reviews.js";
|
|
23
33
|
import { revisionFileRoutes, revisionFolderRoutes } from "./revision-files.js";
|
|
24
34
|
import { revisionRoutes } from "./revisions.js";
|
|
25
|
-
import {
|
|
35
|
+
import { runCostRoutes } from "./run-cost.js";
|
|
36
|
+
import { calendarRoutes, scheduleRoutes } from "./schedules.js";
|
|
26
37
|
import { settingsRoutes } from "./settings.js";
|
|
27
38
|
import { stagingRoutes } from "./staging.js";
|
|
28
39
|
import { storageRoutes } from "./storage.js";
|
|
40
|
+
import { filesRoutes } from "./storage-files.js";
|
|
41
|
+
import { studioRoutes } from "./studio.js";
|
|
42
|
+
import { stylePreviewRoutes } from "./style-preview.js";
|
|
29
43
|
import { subtitleRoutes } from "./subtitles.js";
|
|
30
44
|
import { telemetryRoutes } from "./telemetry.js";
|
|
45
|
+
import { trashRoutes } from "./trash.js";
|
|
31
46
|
import { tutorialRoutes } from "./tutorial.js";
|
|
32
47
|
import { updateRoutes } from "./update.js";
|
|
33
48
|
import { usageRoutes } from "./usage.js";
|
|
49
|
+
import { whatsNewRoutes } from "./whats-new.js";
|
|
50
|
+
import { youtubeEditRoutes } from "./youtube-edits.js";
|
|
34
51
|
const notBuilt = "The Slopify UI is not built yet. The API is running: try /api/health.\n" +
|
|
35
52
|
"Build the SPA with `npm run build` at the repository root.\n";
|
|
36
53
|
function apiRoutes(deps, startedAt) {
|
|
@@ -41,34 +58,52 @@ function apiRoutes(deps, startedAt) {
|
|
|
41
58
|
uptimeMs: deps.clock.now().getTime() - startedAt,
|
|
42
59
|
}))
|
|
43
60
|
.route("/staging", stagingRoutes(deps))
|
|
61
|
+
.route("/storage/files", filesRoutes(deps))
|
|
44
62
|
.route("/storage", storageRoutes(deps))
|
|
45
63
|
.route("/backups", backupRoutes(deps.backups))
|
|
64
|
+
.route("/trash", trashRoutes(deps))
|
|
46
65
|
.route("/drafts", draftRoutes(deps.drafts))
|
|
47
66
|
.route("/diagnostics", diagnosticsRoutes(deps))
|
|
48
67
|
.route("/project-templates", projectTemplateRoutes(deps.drafts))
|
|
49
68
|
.route("/schedules", scheduleRoutes(deps.schedules))
|
|
69
|
+
.route("/channels", channelRoutes(deps))
|
|
70
|
+
.route("/channels", channelMemoryRoutes(deps))
|
|
71
|
+
.route("/calendar", calendarRoutes(deps.schedules))
|
|
50
72
|
.route("/projects", planningRoutes(deps))
|
|
51
73
|
.route("/projects", projectRoutes(deps))
|
|
52
74
|
.route("/projects", checkpointRoutes(deps))
|
|
75
|
+
.route("/projects", reviewRoutes(deps))
|
|
53
76
|
.route("/projects", revisionRoutes(deps))
|
|
54
77
|
.route("/projects", revisionFolderRoutes(deps))
|
|
55
78
|
.route("/projects", openFolderRoutes(deps))
|
|
56
79
|
.route("/projects", audioPreviewRoutes(deps))
|
|
80
|
+
.route("/projects", narrationPeakRoutes(deps))
|
|
81
|
+
.route("/projects", runCostRoutes(deps))
|
|
82
|
+
.route("/projects", uploadedRoutes(deps))
|
|
83
|
+
.route("/home", homeRoutes(deps))
|
|
57
84
|
.route("/update", updateRoutes(deps))
|
|
58
85
|
// The re-run and cancel actions sit on the same prefix as the project itself; they
|
|
59
86
|
// are their own router because they are their own concern.
|
|
60
87
|
.route("/projects", actionRoutes(deps))
|
|
61
88
|
.route("/projects", subtitleRoutes(deps))
|
|
89
|
+
.route("/projects", youtubeEditRoutes(deps))
|
|
62
90
|
.route("/fonts", fontsRoutes(deps))
|
|
63
91
|
.route("/prompts", promptRoutes(deps))
|
|
64
92
|
.route("/entries", entryRoutes(deps))
|
|
65
93
|
.route("/document-themes", documentThemeRoutes(deps))
|
|
66
94
|
.route("/pronunciations", pronunciationRoutes(deps))
|
|
95
|
+
.route("/auditions", auditionRoutes(deps))
|
|
67
96
|
.route("/telemetry", telemetryRoutes(deps))
|
|
68
97
|
.route("/usage", usageRoutes(deps))
|
|
98
|
+
.route("/settings/autostart", autostartRoutes(deps.autostart))
|
|
69
99
|
.route("/settings", settingsRoutes(deps))
|
|
100
|
+
.route("/studio", studioRoutes(deps))
|
|
101
|
+
.route("/style-preview", stylePreviewRoutes(deps))
|
|
70
102
|
.route("/tutorial", tutorialRoutes(deps))
|
|
71
|
-
.route("/
|
|
103
|
+
.route("/whats-new", whatsNewRoutes(deps))
|
|
104
|
+
.route("/patch-notes", patchNotesRoutes(deps))
|
|
105
|
+
.route("/providers", providerRoutes(deps))
|
|
106
|
+
.route("/onboarding", onboardingRoutes(deps)));
|
|
72
107
|
}
|
|
73
108
|
export function createApp(deps) {
|
|
74
109
|
const startedAt = deps.clock.now().getTime();
|
|
@@ -117,6 +152,24 @@ export function createApp(deps) {
|
|
|
117
152
|
releaseUpdate?.();
|
|
118
153
|
releaseRequest?.();
|
|
119
154
|
}
|
|
155
|
+
})
|
|
156
|
+
// The bundled sample is read-only, so exploring it never reaches a paid provider: only
|
|
157
|
+
// what reads or previews it is let through.
|
|
158
|
+
.use("/api/projects/:id/*", async (c, next) => {
|
|
159
|
+
if (["GET", "HEAD", "OPTIONS"].includes(c.req.method))
|
|
160
|
+
return next();
|
|
161
|
+
const id = c.req.param("id");
|
|
162
|
+
// Deleting the sample is allowed: Settings → Restore sample brings it back.
|
|
163
|
+
if (!isSampleProject(deps.db, id) ||
|
|
164
|
+
sampleReadOnlyExempt.test(c.req.path) ||
|
|
165
|
+
c.req.path.replace(/\/$/, "") === `/api/projects/${id}`)
|
|
166
|
+
return next();
|
|
167
|
+
return problem(c, {
|
|
168
|
+
status: 409,
|
|
169
|
+
title: titleOf(409),
|
|
170
|
+
detail: "This is the sample project, which is read-only so trying things on it never spends anything. Press Make my own copy at the top of the sample's page, then edit and rebuild the copy.",
|
|
171
|
+
extensions: { reason: "sample-read-only" },
|
|
172
|
+
});
|
|
120
173
|
})
|
|
121
174
|
.onError((error, c) => problemFromError(c, error, deps))
|
|
122
175
|
.notFound(missing)
|
|
@@ -141,6 +194,9 @@ export function createApp(deps) {
|
|
|
141
194
|
}
|
|
142
195
|
return app;
|
|
143
196
|
}
|
|
197
|
+
// What the sample's page may still do: prepare its revision view, preview a rebuild (which
|
|
198
|
+
// starts nothing) and open its folder.
|
|
199
|
+
const sampleReadOnlyExempt = /\/(revisions\/prepare|rebuild\/preview|open-folder)\/?$/;
|
|
144
200
|
function missing(c) {
|
|
145
201
|
return problem(c, {
|
|
146
202
|
status: 404,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { isProviderError } from "../../kernel/ports/model.js";
|
|
5
|
+
import { estimateRequests } from "../../slices/estimate/index.js";
|
|
6
|
+
import { auditionLineMax } from "../../slices/voices/audition.js";
|
|
7
|
+
import { onInvalid, problem } from "./problem.js";
|
|
8
|
+
const line = z.object({
|
|
9
|
+
provider: z.string().min(1).max(100),
|
|
10
|
+
model: z.string().min(1).max(200),
|
|
11
|
+
text: z.string().trim().min(1).max(auditionLineMax),
|
|
12
|
+
});
|
|
13
|
+
const quoteBody = z.object({
|
|
14
|
+
lines: z
|
|
15
|
+
.array(line.extend({ speaker: z.string().max(100) }))
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(10),
|
|
18
|
+
});
|
|
19
|
+
const speakBody = line.extend({
|
|
20
|
+
voice: z.string().min(1).max(200),
|
|
21
|
+
// The page sends this only from the Audition button, after showing the price.
|
|
22
|
+
confirmed: z.literal(true),
|
|
23
|
+
});
|
|
24
|
+
export function auditionRoutes(deps) {
|
|
25
|
+
return new Hono()
|
|
26
|
+
.post("/quote", zValidator("json", quoteBody, onInvalid), (c) => {
|
|
27
|
+
const { lines } = c.req.valid("json");
|
|
28
|
+
if (deps.catalogue === undefined)
|
|
29
|
+
return c.json({ estimate: null });
|
|
30
|
+
const estimate = estimateRequests(lines.map((one) => ({
|
|
31
|
+
kind: "tts",
|
|
32
|
+
stage: one.speaker,
|
|
33
|
+
provider: one.provider,
|
|
34
|
+
model: one.model,
|
|
35
|
+
text: one.text,
|
|
36
|
+
})), deps.catalogue.read());
|
|
37
|
+
return c.json({ estimate });
|
|
38
|
+
})
|
|
39
|
+
.post("/", zValidator("json", speakBody, onInvalid), async (c) => {
|
|
40
|
+
const body = c.req.valid("json");
|
|
41
|
+
if (deps.audition === undefined)
|
|
42
|
+
return problem(c, {
|
|
43
|
+
status: 500,
|
|
44
|
+
title: "Internal Server Error",
|
|
45
|
+
detail: "Voice auditions aren't available in this build. Start the run and listen to the narration in the project instead.",
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
const spoken = await deps.audition({ provider: body.provider, model: body.model, voiceId: body.voice, text: body.text }, c.req.raw.signal);
|
|
49
|
+
if (!spoken.ok)
|
|
50
|
+
return problem(c, {
|
|
51
|
+
status: 409,
|
|
52
|
+
title: "Conflict",
|
|
53
|
+
detail: "The audition was held back. Try the Audition button again in a moment.",
|
|
54
|
+
});
|
|
55
|
+
return c.body(spoken.value.bytes.slice().buffer, 200, {
|
|
56
|
+
"content-type": "audio/mpeg",
|
|
57
|
+
"cache-control": "no-store",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (isProviderError(error))
|
|
62
|
+
return problem(c, {
|
|
63
|
+
status: 502,
|
|
64
|
+
title: "Bad Gateway",
|
|
65
|
+
detail: `The audition couldn't be spoken: ${error.message}`,
|
|
66
|
+
});
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { AutostartRefusal } from "../autostart/service.js";
|
|
5
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
6
|
+
const body = z.object({ enabled: z.boolean() });
|
|
7
|
+
// Settings → General's "Start Slopify when I log in", and the first-run screen's offer of it.
|
|
8
|
+
// In Docker the view says whether Docker starts at login, and turning it on or off is refused
|
|
9
|
+
// with where Docker's own setting is.
|
|
10
|
+
export function autostartRoutes(service) {
|
|
11
|
+
const unavailable = "Starting at login can't be set from this Slopify. Start it with npx @gentbajko/slopify and try Settings → General again.";
|
|
12
|
+
return (new Hono()
|
|
13
|
+
.get("/", async (c) => {
|
|
14
|
+
if (service === undefined)
|
|
15
|
+
return problem(c, { status: 404, title: titleOf(404), detail: unavailable });
|
|
16
|
+
c.header("Cache-Control", "no-store");
|
|
17
|
+
return c.json(await service.view());
|
|
18
|
+
})
|
|
19
|
+
.put("/", zValidator("json", body, onInvalid), async (c) => {
|
|
20
|
+
if (service === undefined)
|
|
21
|
+
return problem(c, { status: 404, title: titleOf(404), detail: unavailable });
|
|
22
|
+
try {
|
|
23
|
+
return c.json(await service.set(c.req.valid("json").enabled));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (!(error instanceof AutostartRefusal))
|
|
27
|
+
throw error;
|
|
28
|
+
return problem(c, { status: 409, title: titleOf(409), detail: error.message });
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
// The first-run screen's No thanks: never offer it there again, change nothing.
|
|
32
|
+
.post("/answer", async (c) => {
|
|
33
|
+
if (service === undefined)
|
|
34
|
+
return problem(c, { status: 404, title: titleOf(404), detail: unavailable });
|
|
35
|
+
return c.json(await service.answer());
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { zValidator } from "@hono/zod-validator";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { bodyLimit } from "hono/body-limit";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { clearChannelVideos, deleteChannelVideo, importChannelVideos, listChannelVideos, previewChannelVideos, } from "../../slices/channels/videos.js";
|
|
7
|
+
import { channelEpisodes, deleteMemory, editMemory, setEpisodeMemory, } from "../../slices/episodes/service.js";
|
|
8
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
9
|
+
const idParam = z.object({ id: z.uuid() });
|
|
10
|
+
const memoryParam = idParam.extend({ memoryId: z.string().min(1).max(100) });
|
|
11
|
+
const videoParam = idParam.extend({ videoId: z.uuid() });
|
|
12
|
+
// A YouTube Studio export of a large channel is a few megabytes.
|
|
13
|
+
const importMaxBytes = 20 * 1024 * 1024;
|
|
14
|
+
// Channel page → Episodes (episode memory: the setting and the summaries) and Existing videos
|
|
15
|
+
// (titles made before Slopify; a CSV is previewed, then its ticked titles are saved). Mounted
|
|
16
|
+
// beside `channelRoutes` under /api/channels.
|
|
17
|
+
export function channelMemoryRoutes(deps) {
|
|
18
|
+
const service = { db: deps.db, clock: deps.clock, uuid: randomUUID };
|
|
19
|
+
return new Hono()
|
|
20
|
+
.get("/:id/episodes", zValidator("param", idParam, onInvalid), (c) => {
|
|
21
|
+
const result = channelEpisodes(service, c.req.valid("param").id);
|
|
22
|
+
return result.ok ? c.json(result.value) : refused(c, result, "episode");
|
|
23
|
+
})
|
|
24
|
+
.put("/:id/episodes/setting", zValidator("param", idParam, onInvalid), async (c) => {
|
|
25
|
+
const result = setEpisodeMemory(service, c.req.valid("param").id, await body(c));
|
|
26
|
+
return result.ok ? c.json(result.value) : refused(c, result, "episode");
|
|
27
|
+
})
|
|
28
|
+
.put("/:id/episodes/:memoryId", zValidator("param", memoryParam, onInvalid), async (c) => {
|
|
29
|
+
const { id, memoryId } = c.req.valid("param");
|
|
30
|
+
const result = editMemory(service, id, memoryId, await body(c));
|
|
31
|
+
return result.ok ? c.json(result.value) : refused(c, result, "episode");
|
|
32
|
+
})
|
|
33
|
+
.delete("/:id/episodes/:memoryId", zValidator("param", memoryParam, onInvalid), (c) => {
|
|
34
|
+
const { id, memoryId } = c.req.valid("param");
|
|
35
|
+
const result = deleteMemory(service, id, memoryId);
|
|
36
|
+
return result.ok ? c.body(null, 204) : refused(c, result, "episode");
|
|
37
|
+
})
|
|
38
|
+
.get("/:id/videos", zValidator("param", idParam, onInvalid), (c) => {
|
|
39
|
+
const result = listChannelVideos(service, c.req.valid("param").id);
|
|
40
|
+
return result.ok ? c.json({ videos: result.value }) : refused(c, result, "video");
|
|
41
|
+
})
|
|
42
|
+
.post("/:id/videos/preview", zValidator("param", idParam, onInvalid), importLimit(), async (c) => {
|
|
43
|
+
const result = previewChannelVideos(service, c.req.valid("param").id, await body(c));
|
|
44
|
+
return result.ok ? c.json(result.value) : refused(c, result, "video");
|
|
45
|
+
})
|
|
46
|
+
.post("/:id/videos", zValidator("param", idParam, onInvalid), importLimit(), async (c) => {
|
|
47
|
+
const result = importChannelVideos(service, c.req.valid("param").id, await body(c));
|
|
48
|
+
return result.ok ? c.json(result.value, 201) : refused(c, result, "video");
|
|
49
|
+
})
|
|
50
|
+
.delete("/:id/videos", zValidator("param", idParam, onInvalid), (c) => {
|
|
51
|
+
const result = clearChannelVideos(service, c.req.valid("param").id);
|
|
52
|
+
return result.ok ? c.json(result.value) : refused(c, result, "video");
|
|
53
|
+
})
|
|
54
|
+
.delete("/:id/videos/:videoId", zValidator("param", videoParam, onInvalid), (c) => {
|
|
55
|
+
const { id, videoId } = c.req.valid("param");
|
|
56
|
+
const result = deleteChannelVideo(service, id, videoId);
|
|
57
|
+
return result.ok ? c.body(null, 204) : refused(c, result, "video");
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function importLimit() {
|
|
61
|
+
return bodyLimit({
|
|
62
|
+
maxSize: importMaxBytes,
|
|
63
|
+
onError: (c) => refused(c, {
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: "invalid-input",
|
|
66
|
+
message: "This file is larger than 20 MB. Export fewer videos from YouTube Studio, or paste the titles instead.",
|
|
67
|
+
}, "video"),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function body(c) {
|
|
71
|
+
try {
|
|
72
|
+
return await c.req.json();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function refused(c, result, about) {
|
|
79
|
+
const status = result.reason === "not-found" ? 404 : 400;
|
|
80
|
+
const detail = result.message ??
|
|
81
|
+
(result.reason === "not-found"
|
|
82
|
+
? about === "episode"
|
|
83
|
+
? "This channel or episode summary no longer exists; it may have been deleted in another tab. Reload the channel's Episodes tab."
|
|
84
|
+
: "This channel or video title no longer exists; it may have been deleted in another tab. Reload the channel's Existing videos tab."
|
|
85
|
+
: "This request isn't valid. Reload the page and try again.");
|
|
86
|
+
return problem(c, {
|
|
87
|
+
status,
|
|
88
|
+
title: titleOf(status),
|
|
89
|
+
detail,
|
|
90
|
+
extensions: { reason: result.reason },
|
|
91
|
+
});
|
|
92
|
+
}
|