@gentbajko/slopify 2.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +3 -0
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +44 -5
- package/dist/adapters/host-cli/transport.js +7 -5
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +1 -1
- package/dist/adapters/image/codex.js +87 -22
- package/dist/adapters/image/fal.js +13 -5
- package/dist/adapters/image/google.js +11 -8
- package/dist/adapters/image/openai.js +14 -7
- package/dist/adapters/image/reference.js +34 -0
- package/dist/adapters/image/replicate.js +7 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +12 -7
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +13 -13
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +58 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +29 -5
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +84 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +9 -1
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +122 -14
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +17 -8
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +142 -7
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +272 -16
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +79 -1
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +41 -5
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/folder.js +14 -6
- package/dist/slices/batch/index.js +7 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +109 -28
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +7 -1
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +21 -3
- package/dist/slices/notifications/rules.js +22 -1
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +125 -6
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +2 -0
- package/dist/slices/play-drafts/review-inputs.js +20 -3
- package/dist/slices/play-drafts/schema.js +89 -0
- package/dist/slices/play-drafts/service.js +4 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/project-templates/from-project.js +50 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +18 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +46 -3
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +8 -4
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +35 -0
- package/dist/slices/rebuild/recipe-legacy.js +9 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +15 -1
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +23 -3
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +14 -2
- package/dist/slices/rebuild/recipe-visual.js +96 -33
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +5 -0
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +21 -2
- package/dist/slices/rebuild/runtime-local.js +148 -24
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +27 -0
- package/dist/slices/rebuild/runtime-provider.js +126 -18
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +44 -17
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +9 -2
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +5 -2
- package/dist/slices/revisions/adopt.js +3 -1
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +10 -0
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +11 -1
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +43 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +6 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +14 -4
- package/dist/slices/storage/model.js +11 -0
- package/dist/slices/storage/portable.js +92 -12
- package/dist/slices/storage/reconcile.js +14 -1
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-CktpxlJd.js +0 -171
- package/dist/web/assets/index-DB-vOnGU.css +0 -1
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
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 { deleteCastImage, generateCastImage, uploadCastImage, } from "../../slices/channels/cast-images.js";
|
|
7
|
+
import { castImageMaxBytes } from "../../slices/channels/images.js";
|
|
8
|
+
import { imageBlob } from "../../slices/channels/repo.js";
|
|
9
|
+
import { createCastMember, createChannel, deleteCastMember, deleteChannel, listChannels, moveTemplate, readChannel, setChannelAiDisclosure, updateCastMember, updateChannel, } from "../../slices/channels/service.js";
|
|
10
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
11
|
+
const idParam = z.object({ id: z.uuid() });
|
|
12
|
+
const memberParam = z.object({ memberId: z.uuid() });
|
|
13
|
+
const imageParam = memberParam.extend({ imageId: z.uuid() });
|
|
14
|
+
const pictureParam = z.object({ sha256: z.string().regex(/^[0-9a-f]{64}$/) });
|
|
15
|
+
const templateParam = z.object({ templateId: z.uuid() });
|
|
16
|
+
// Channels: the channels, their brand kit, series brief and cast, and which channel
|
|
17
|
+
// each template belongs to. The bodies are checked by the slice, so a refusal names the field.
|
|
18
|
+
export function channelRoutes(deps) {
|
|
19
|
+
const service = {
|
|
20
|
+
db: deps.db,
|
|
21
|
+
clock: deps.clock,
|
|
22
|
+
uuid: randomUUID,
|
|
23
|
+
generateImage: deps.generateCastImage,
|
|
24
|
+
log: deps.log,
|
|
25
|
+
};
|
|
26
|
+
return (new Hono()
|
|
27
|
+
.get("/", (c) => c.json({ channels: listChannels(service) }))
|
|
28
|
+
.post("/", async (c) => {
|
|
29
|
+
const result = createChannel(service, await body(c));
|
|
30
|
+
return result.ok ? c.json(result.value, 201) : refused(c, result);
|
|
31
|
+
})
|
|
32
|
+
// The pictures by content hash: they never change, so the browser may keep them.
|
|
33
|
+
.get("/pictures/:sha256", zValidator("param", pictureParam, onInvalid), (c) => {
|
|
34
|
+
const blob = imageBlob(deps.db, c.req.valid("param").sha256);
|
|
35
|
+
if (blob === undefined)
|
|
36
|
+
return refused(c, { ok: false, reason: "not-found" });
|
|
37
|
+
return c.body(new Uint8Array(blob.bytes), 200, {
|
|
38
|
+
"Content-Type": blob.mime,
|
|
39
|
+
"Cache-Control": "private, max-age=31536000, immutable",
|
|
40
|
+
"X-Content-Type-Options": "nosniff",
|
|
41
|
+
});
|
|
42
|
+
})
|
|
43
|
+
.put("/templates/:templateId", zValidator("param", templateParam, onInvalid), async (c) => {
|
|
44
|
+
const result = moveTemplate(service, c.req.valid("param").templateId, await body(c));
|
|
45
|
+
return result.ok ? c.json(result.value) : refused(c, result);
|
|
46
|
+
})
|
|
47
|
+
.put("/cast/:memberId", zValidator("param", memberParam, onInvalid), async (c) => {
|
|
48
|
+
const result = updateCastMember(service, c.req.valid("param").memberId, await body(c));
|
|
49
|
+
return result.ok ? c.json(result.value) : refused(c, result);
|
|
50
|
+
})
|
|
51
|
+
.delete("/cast/:memberId", zValidator("param", memberParam, onInvalid), (c) => {
|
|
52
|
+
const result = deleteCastMember(service, c.req.valid("param").memberId);
|
|
53
|
+
return result.ok ? c.body(null, 204) : refused(c, result);
|
|
54
|
+
})
|
|
55
|
+
// The picture's bytes are the body, as the file input read them.
|
|
56
|
+
.post("/cast/:memberId/images", zValidator("param", memberParam, onInvalid), bodyLimit({
|
|
57
|
+
maxSize: castImageMaxBytes,
|
|
58
|
+
onError: (c) => refused(c, {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: "not-an-image",
|
|
61
|
+
message: `This picture is larger than ${String(castImageMaxBytes / 1024 / 1024)} MB. Save it smaller (for example as a JPEG) and upload it again.`,
|
|
62
|
+
}),
|
|
63
|
+
}), async (c) => {
|
|
64
|
+
const bytes = new Uint8Array(await c.req.arrayBuffer());
|
|
65
|
+
const result = uploadCastImage(service, c.req.valid("param").memberId, bytes);
|
|
66
|
+
return result.ok ? c.json(result.value, 201) : refused(c, result);
|
|
67
|
+
})
|
|
68
|
+
.post("/cast/:memberId/generate", zValidator("param", memberParam, onInvalid), async (c) => {
|
|
69
|
+
const result = generateCastImage(service, c.req.valid("param").memberId, await body(c));
|
|
70
|
+
return result.ok ? c.json(result.value, 202) : refused(c, result);
|
|
71
|
+
})
|
|
72
|
+
.delete("/cast/:memberId/images/:imageId", zValidator("param", imageParam, onInvalid), (c) => {
|
|
73
|
+
const { memberId, imageId } = c.req.valid("param");
|
|
74
|
+
const result = deleteCastImage(service, memberId, imageId);
|
|
75
|
+
return result.ok ? c.body(null, 204) : refused(c, result);
|
|
76
|
+
})
|
|
77
|
+
.get("/:id", zValidator("param", idParam, onInvalid), (c) => {
|
|
78
|
+
const result = readChannel(service, c.req.valid("param").id);
|
|
79
|
+
return result.ok ? c.json(result.value) : refused(c, result);
|
|
80
|
+
})
|
|
81
|
+
.put("/:id", zValidator("param", idParam, onInvalid), async (c) => {
|
|
82
|
+
const result = updateChannel(service, c.req.valid("param").id, await body(c));
|
|
83
|
+
return result.ok ? c.json(result.value) : refused(c, result);
|
|
84
|
+
})
|
|
85
|
+
.put("/:id/ai-disclosure", zValidator("param", idParam, onInvalid), async (c) => {
|
|
86
|
+
const result = setChannelAiDisclosure(service, c.req.valid("param").id, await body(c));
|
|
87
|
+
return result.ok ? c.json(result.value) : refused(c, result);
|
|
88
|
+
})
|
|
89
|
+
.delete("/:id", zValidator("param", idParam, onInvalid), (c) => {
|
|
90
|
+
const result = deleteChannel(service, c.req.valid("param").id);
|
|
91
|
+
return result.ok ? c.body(null, 204) : refused(c, result);
|
|
92
|
+
})
|
|
93
|
+
.post("/:id/cast", zValidator("param", idParam, onInvalid), async (c) => {
|
|
94
|
+
const result = createCastMember(service, c.req.valid("param").id, await body(c));
|
|
95
|
+
return result.ok ? c.json(result.value, 201) : refused(c, result);
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
async function body(c) {
|
|
99
|
+
try {
|
|
100
|
+
return await c.req.json();
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const details = {
|
|
107
|
+
"not-found": "This channel, cast member or picture no longer exists; it may have been deleted in another tab. Go back to Channels.",
|
|
108
|
+
conflict: "This changed in another tab while you were editing. Reload the page to see the latest, then make your change again.",
|
|
109
|
+
"invalid-input": "Some of these settings are not valid. Check the highlighted fields and try again.",
|
|
110
|
+
"default-channel": "The default channel can't be deleted; projects and templates without a channel belong to it. Rename it in its Brand tab instead.",
|
|
111
|
+
"has-templates": "This channel still has templates. Move them to another channel in its Templates tab, or delete them, then delete the channel.",
|
|
112
|
+
"not-an-image": "This file is not a PNG or JPEG picture. Upload a PNG or JPEG file.",
|
|
113
|
+
"too-many-images": "This cast member already has the most pictures it can keep. Delete one first.",
|
|
114
|
+
"no-image-provider": "Slopify can't make pictures here yet. Set up an image provider in Settings → Providers, or upload a picture instead.",
|
|
115
|
+
};
|
|
116
|
+
function refused(c, result) {
|
|
117
|
+
const status = result.reason === "not-found"
|
|
118
|
+
? 404
|
|
119
|
+
: result.reason === "conflict" || result.reason === "has-templates"
|
|
120
|
+
? 409
|
|
121
|
+
: 400;
|
|
122
|
+
return problem(c, {
|
|
123
|
+
status,
|
|
124
|
+
title: titleOf(status),
|
|
125
|
+
detail: result.message ?? details[result.reason],
|
|
126
|
+
extensions: { reason: result.reason },
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { zValidator } from "@hono/zod-validator";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { versionsOrCurrent } from "../../slices/library/history.js";
|
|
4
5
|
import { entryCategories, entryModes } from "../../slices/library/model.js";
|
|
5
|
-
import { listEntries } from "../../slices/library/repo.js";
|
|
6
|
-
import { createEntry, removeEntry, updateEntry } from "../../slices/library/save.js";
|
|
6
|
+
import { entryById, listEntries } from "../../slices/library/repo.js";
|
|
7
|
+
import { createEntry, removeEntry, restoreEntry, updateEntry } from "../../slices/library/save.js";
|
|
8
|
+
import { usedBy } from "../../slices/library/used-by.js";
|
|
7
9
|
import { onInvalid } from "./problem.js";
|
|
8
10
|
// One rule set for prompts and entries, so one refusal mapping too.
|
|
9
|
-
import { refused } from "./prompts.js";
|
|
11
|
+
import { refused, versionParam } from "./prompts.js";
|
|
10
12
|
const idParam = z.object({
|
|
11
13
|
id: z
|
|
12
14
|
.string()
|
|
@@ -33,6 +35,23 @@ export function entryRoutes(deps) {
|
|
|
33
35
|
.put("/:id", zValidator("param", idParam, onInvalid), zValidator("json", entryBody, onInvalid), (c) => {
|
|
34
36
|
const result = updateEntry(library, c.req.valid("param").id, c.req.valid("json"));
|
|
35
37
|
return result.ok ? c.json(result.value) : refused(c, result, "entry");
|
|
38
|
+
})
|
|
39
|
+
.get("/:id/history", zValidator("param", idParam, onInvalid), (c) => {
|
|
40
|
+
const entry = entryById(deps.db, c.req.valid("param").id);
|
|
41
|
+
return entry === undefined
|
|
42
|
+
? refused(c, { ok: false, reason: "not-found" }, "entry")
|
|
43
|
+
: c.json({ versions: versionsOrCurrent(deps.db, "entry", entry) });
|
|
44
|
+
})
|
|
45
|
+
.post("/:id/history/:version/restore", zValidator("param", versionParam, onInvalid), (c) => {
|
|
46
|
+
const { id, version } = c.req.valid("param");
|
|
47
|
+
const result = restoreEntry(library, id, version);
|
|
48
|
+
return result.ok ? c.json(result.value) : refused(c, result, "entry");
|
|
49
|
+
})
|
|
50
|
+
.get("/:id/used-by", zValidator("param", idParam, onInvalid), (c) => {
|
|
51
|
+
const entry = entryById(deps.db, c.req.valid("param").id);
|
|
52
|
+
return entry === undefined
|
|
53
|
+
? refused(c, { ok: false, reason: "not-found" }, "entry")
|
|
54
|
+
: c.json(usedBy(deps.db, { item: "entry", category: entry.category, name: entry.name }));
|
|
36
55
|
})
|
|
37
56
|
.delete("/:id", zValidator("param", idParam, onInvalid), (c) => {
|
|
38
57
|
const result = removeEntry(library, c.req.valid("param").id);
|
package/dist/edge/http/files.js
CHANGED
|
@@ -60,7 +60,7 @@ function missing(c, reason) {
|
|
|
60
60
|
const details = {
|
|
61
61
|
"unknown-project": "This project no longer exists. Go back to Projects to pick another.",
|
|
62
62
|
"unknown-asset": "This project has no file by that name. Reload the page to see its current files.",
|
|
63
|
-
"missing-file": "This file was deleted or moved from the project folder. Use
|
|
63
|
+
"missing-file": "This file was deleted or moved from the project folder. Use More → make it again in its section on the project page to make it again.",
|
|
64
64
|
"no-images": "This project has no images or thumbnail yet. Wait for the Images step to finish, then try again.",
|
|
65
65
|
};
|
|
66
66
|
return problem(c, { status: 404, title: titleOf(404), detail: details[reason] });
|
|
@@ -46,7 +46,7 @@ export async function replyForFolder(c, deps, projectId, file) {
|
|
|
46
46
|
return problem(c, {
|
|
47
47
|
status: 404,
|
|
48
48
|
title: titleOf(404),
|
|
49
|
-
detail: "This output's folder was deleted or moved. Use
|
|
49
|
+
detail: "This output's folder was deleted or moved. Use More → make it again in its section on the project page to make the file again.",
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { weekSummary } from "../../slices/run-cost/week.js";
|
|
5
|
+
import { markUploaded } from "../../slices/uploads/repo.js";
|
|
6
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
7
|
+
const weekQuery = z.object({
|
|
8
|
+
since: z.iso.datetime({ offset: true }),
|
|
9
|
+
channelId: z.string().min(1).max(64).optional(),
|
|
10
|
+
});
|
|
11
|
+
// Home's "This week": videos made, spend and the API equivalent since `since` (the browser's
|
|
12
|
+
// own Monday), for one channel or all of them, and where each CLI plan's windows stand.
|
|
13
|
+
export function homeRoutes(deps) {
|
|
14
|
+
return new Hono().get("/week", zValidator("query", weekQuery, onInvalid), (c) => {
|
|
15
|
+
const { since, channelId } = c.req.valid("query");
|
|
16
|
+
return c.json(weekSummary(deps.db, new Date(since).toISOString(), channelId));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const idParam = z.object({
|
|
20
|
+
id: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(64)
|
|
24
|
+
.regex(/^[0-9A-Za-z_-]+$/),
|
|
25
|
+
});
|
|
26
|
+
// Home's "Mark uploaded" and its undo. Slopify never uploads, so this only records what the
|
|
27
|
+
// person says they did.
|
|
28
|
+
export function uploadedRoutes(deps) {
|
|
29
|
+
return new Hono().put("/:id/uploaded", zValidator("param", idParam, onInvalid), zValidator("json", z.object({ uploaded: z.boolean() }).strict(), onInvalid), (c) => {
|
|
30
|
+
const result = markUploaded(deps.db, c.req.valid("param").id, c.req.valid("json").uploaded, deps.clock.now().toISOString());
|
|
31
|
+
if (!result.ok)
|
|
32
|
+
return problem(c, {
|
|
33
|
+
status: 404,
|
|
34
|
+
title: titleOf(404),
|
|
35
|
+
detail: "This project no longer exists. Go back to Projects to pick another.",
|
|
36
|
+
});
|
|
37
|
+
return c.json({ uploadedAt: result.uploadedAt });
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -5,7 +5,7 @@ import { stream } from "hono/streaming";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { sniffImage } from "../../adapters/image/bytes.js";
|
|
7
7
|
import { redact } from "../../kernel/log.js";
|
|
8
|
-
import { bridgeLimits, HostFolderRefused, hostCliIds, hostCliProtocol, hostFrameSchema, hostImageSchema, hostLlmIds, hostLlmSchema, hostModelsSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
|
|
8
|
+
import { bridgeLimits, encodeHostImageReport, HostFolderRefused, hostCliIds, hostCliProtocol, hostDoneFrame, hostFrameSchema, hostFramesHeader, hostImageReportHeader, hostImageSchema, hostLlmIds, hostLlmSchema, hostModelsSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
|
|
9
9
|
import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
|
|
10
10
|
import { isProviderError, providerError } from "../../kernel/ports/model.js";
|
|
11
11
|
export function hostGate() {
|
|
@@ -49,7 +49,7 @@ export function hostGate() {
|
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
const unavailable = "The AI command-line tool on your computer stopped before it finished, so this result is uncertain. Check the section's outputs on the project page, then use
|
|
52
|
+
const unavailable = "The AI command-line tool on your computer stopped before it finished, so this result is uncertain. Check the section's outputs on the project page, then use Try again.";
|
|
53
53
|
function fault(error, token) {
|
|
54
54
|
return {
|
|
55
55
|
type: "error",
|
|
@@ -90,7 +90,7 @@ export function hostCliRoutes(options) {
|
|
|
90
90
|
return c.json({
|
|
91
91
|
type: "error",
|
|
92
92
|
kind: "unavailable",
|
|
93
|
-
message: "The Slopify helper on your computer is busy or restarting. Wait a moment, then use
|
|
93
|
+
message: "The Slopify helper on your computer is busy or restarting. Wait a moment, then use Try again.",
|
|
94
94
|
}, 503);
|
|
95
95
|
const outgoing = c.env?.outgoing;
|
|
96
96
|
let responseEnded = !outgoing;
|
|
@@ -174,6 +174,8 @@ export function hostCliRoutes(options) {
|
|
|
174
174
|
const body = hostLlmSchema.safeParse(await json(c.req.raw));
|
|
175
175
|
if (!body.success)
|
|
176
176
|
return invalid(c);
|
|
177
|
+
// An app that sends no header reads the original frames (version 1).
|
|
178
|
+
const frames = Number(c.req.header(hostFramesHeader) ?? 1);
|
|
177
179
|
const job = c.get("job");
|
|
178
180
|
job.holdStream();
|
|
179
181
|
const controller = new AbortController();
|
|
@@ -191,7 +193,14 @@ export function hostCliRoutes(options) {
|
|
|
191
193
|
signal.throwIfAborted();
|
|
192
194
|
clearTimeout(timer);
|
|
193
195
|
timer = setTimeout(() => controller.abort(), 120_000);
|
|
194
|
-
|
|
196
|
+
// The cached-token count, the answering model and the plan windows cross only to
|
|
197
|
+
// an app that asked for frame version 2; an older app rejects fields it does not
|
|
198
|
+
// know. Typed-ahead text stays here: the bridge carries the committed answer only.
|
|
199
|
+
const parsed = hostFrameSchema.parse(event.type === "done"
|
|
200
|
+
? hostDoneFrame(event, frames)
|
|
201
|
+
: event.type === "partial"
|
|
202
|
+
? { type: "activity" }
|
|
203
|
+
: event);
|
|
195
204
|
if (done || parsed.type === "error")
|
|
196
205
|
throw new Error("Slopify hit an internal error (the AI tool on your computer sent a reply Slopify could not read). Try again; if it keeps happening, use Download diagnostics in Settings and report it.");
|
|
197
206
|
const frame = `${JSON.stringify(parsed)}\n`;
|
|
@@ -229,7 +238,17 @@ export function hostCliRoutes(options) {
|
|
|
229
238
|
const signal = AbortSignal.any([job.signal, controller.signal]);
|
|
230
239
|
const timer = setTimeout(() => controller.abort(), agentImageTimeoutMs);
|
|
231
240
|
try {
|
|
232
|
-
const { reference, thinking, ...rest } = body.data;
|
|
241
|
+
const { reference, thinking, cast: bridgedCast, ...rest } = body.data;
|
|
242
|
+
const cast = bridgedCast?.map((member) => ({
|
|
243
|
+
name: member.name,
|
|
244
|
+
description: member.description,
|
|
245
|
+
images: member.images.map((picture) => ({
|
|
246
|
+
bytes: new Uint8Array(Buffer.from(picture.base64, "base64")),
|
|
247
|
+
mime: picture.mime,
|
|
248
|
+
})),
|
|
249
|
+
}));
|
|
250
|
+
if (cast?.some((member) => member.images.some((picture) => sniffImage(picture.bytes) !== picture.mime)))
|
|
251
|
+
return invalid(c);
|
|
233
252
|
const image = reference === undefined
|
|
234
253
|
? undefined
|
|
235
254
|
: {
|
|
@@ -242,6 +261,7 @@ export function hostCliRoutes(options) {
|
|
|
242
261
|
...rest,
|
|
243
262
|
...(thinking === undefined ? {} : { thinking }),
|
|
244
263
|
...(image === undefined ? {} : { reference: image }),
|
|
264
|
+
...(cast === undefined ? {} : { cast }),
|
|
245
265
|
signal,
|
|
246
266
|
});
|
|
247
267
|
if (signal.aborted)
|
|
@@ -250,6 +270,10 @@ export function hostCliRoutes(options) {
|
|
|
250
270
|
throw new Error("Slopify hit an internal error (the image made on your computer was too large or not a valid image). Try again; if it keeps happening, use Download diagnostics in Settings and report it.");
|
|
251
271
|
c.header("Content-Type", result.mime);
|
|
252
272
|
c.header("Content-Length", String(result.bytes.byteLength));
|
|
273
|
+
// Any app may get this: one from before it ignores headers it does not know.
|
|
274
|
+
const report = encodeHostImageReport(result);
|
|
275
|
+
if (report !== undefined)
|
|
276
|
+
c.header(hostImageReportHeader, report);
|
|
253
277
|
return c.body(new Uint8Array(result.bytes));
|
|
254
278
|
}
|
|
255
279
|
catch (error) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { narrationAudio } from "../../slices/narration/peaks.js";
|
|
5
|
+
import { assetPath } from "../../slices/revisions/mutation-assets.js";
|
|
6
|
+
import { currentRevisionId } from "../../slices/revisions/repo.js";
|
|
7
|
+
import { getRevisionView } from "../../slices/revisions/view.js";
|
|
8
|
+
import { outputPath } from "../../slices/storage/layout.js";
|
|
9
|
+
import { projectTitle } from "../../slices/storage/repo.js";
|
|
10
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
11
|
+
const projectParam = z.object({
|
|
12
|
+
projectId: z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(64)
|
|
16
|
+
.regex(/^[0-9A-Za-z_-]+$/),
|
|
17
|
+
});
|
|
18
|
+
// ceiling: a few projects' worth of pieces. An asset never changes, so a cached row is never
|
|
19
|
+
// stale; the oldest goes first.
|
|
20
|
+
const cachedPieces = 400;
|
|
21
|
+
// The live view's waveform: the peaks of every finished narration piece of the current
|
|
22
|
+
// version, fetched again each time a `narration.piece` event says one landed.
|
|
23
|
+
export function narrationPeakRoutes(deps) {
|
|
24
|
+
const cache = new Map();
|
|
25
|
+
return new Hono().get("/:projectId/narration/peaks", zValidator("param", projectParam, onInvalid), async (c) => {
|
|
26
|
+
const { projectId } = c.req.valid("param");
|
|
27
|
+
if (projectTitle(deps.db, projectId) === undefined)
|
|
28
|
+
return problem(c, {
|
|
29
|
+
status: 404,
|
|
30
|
+
title: titleOf(404),
|
|
31
|
+
detail: "This project no longer exists. Go back to Projects to pick another.",
|
|
32
|
+
});
|
|
33
|
+
c.header("Cache-Control", "no-store");
|
|
34
|
+
const revisionId = currentRevisionId(deps.db, projectId);
|
|
35
|
+
const view = revisionId === undefined ? undefined : getRevisionView(deps, projectId, revisionId);
|
|
36
|
+
if (revisionId === undefined || view === undefined)
|
|
37
|
+
return c.json({
|
|
38
|
+
revisionId: null,
|
|
39
|
+
complete: false,
|
|
40
|
+
pieces: [],
|
|
41
|
+
});
|
|
42
|
+
const decode = deps.decodePeaks;
|
|
43
|
+
if (decode === undefined)
|
|
44
|
+
return problem(c, {
|
|
45
|
+
status: 503,
|
|
46
|
+
title: titleOf(503),
|
|
47
|
+
detail: "Slopify can't draw the narration waveform because ffmpeg isn't available. Restart Slopify; if it keeps happening, use Download diagnostics in Settings and report it.",
|
|
48
|
+
});
|
|
49
|
+
const audio = narrationAudio(view);
|
|
50
|
+
const pieces = [];
|
|
51
|
+
for (const piece of audio.pieces) {
|
|
52
|
+
let peaks = cache.get(piece.assetId);
|
|
53
|
+
if (peaks === undefined) {
|
|
54
|
+
try {
|
|
55
|
+
peaks = await decode(outputPath(deps.paths, projectId, assetPath(deps, projectId, piece.assetId)), c.req.raw.signal);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
deps.log.write("warn", "narration.peaks", {
|
|
59
|
+
projectId,
|
|
60
|
+
stage: "audio",
|
|
61
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
cache.set(piece.assetId, peaks);
|
|
66
|
+
while (cache.size > cachedPieces) {
|
|
67
|
+
const oldest = cache.keys().next().value;
|
|
68
|
+
if (oldest !== undefined)
|
|
69
|
+
cache.delete(oldest);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
pieces.push({ key: piece.key, seconds: peaks.seconds, peaks: peaks.peaks });
|
|
73
|
+
}
|
|
74
|
+
return c.json({ revisionId, complete: audio.complete, pieces });
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { zValidator } from "@hono/zod-validator";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { loudnessOfDefault } from "../../slices/loudness/model.js";
|
|
6
|
+
import { firstRunView } from "../../slices/onboarding/first-run.js";
|
|
7
|
+
import { installPack } from "../../slices/onboarding/install.js";
|
|
8
|
+
import { quickShortInputSchema, sampleCopyInputSchema } from "../../slices/onboarding/model.js";
|
|
9
|
+
import { packById, starterSet } from "../../slices/onboarding/packs.js";
|
|
10
|
+
import { planShortProviders, shortDraft } from "../../slices/onboarding/quick-short.js";
|
|
11
|
+
import { copySample, restoreSamples, sampleProjectIds, } from "../../slices/onboarding/sample.js";
|
|
12
|
+
import { dismissFirstRun, recordShortRequest, shortRequestProject, } from "../../slices/onboarding/state.js";
|
|
13
|
+
import { readSettings } from "../../slices/settings/playback.js";
|
|
14
|
+
import { providerStatuses } from "../../slices/settings/readiness.js";
|
|
15
|
+
import { listVoices } from "../../slices/settings/repo.js";
|
|
16
|
+
import { projectTitle } from "../../slices/storage/repo.js";
|
|
17
|
+
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
18
|
+
import { createProject } from "./project-create.js";
|
|
19
|
+
const packParam = z.object({ id: z.string().min(1).max(40) });
|
|
20
|
+
// The first five minutes: the first-run screen's state, starter packs, "Make a 60-second
|
|
21
|
+
// short" and the bundled samples' Restore and Make my own copy.
|
|
22
|
+
export function onboardingRoutes(deps) {
|
|
23
|
+
const statuses = () => providerStatuses({ db: deps.db, probe: deps.probe, hostCliStatus: deps.hostCliStatus });
|
|
24
|
+
const templates = { ...deps, uuid: randomUUID };
|
|
25
|
+
const sample = {
|
|
26
|
+
db: deps.db,
|
|
27
|
+
paths: deps.paths,
|
|
28
|
+
clock: deps.clock,
|
|
29
|
+
ids: deps.ids,
|
|
30
|
+
log: deps.log,
|
|
31
|
+
appVersion: deps.version,
|
|
32
|
+
hasInflight: deps.runner.hasInflight,
|
|
33
|
+
...(deps.sampleArchives === undefined ? {} : { archives: deps.sampleArchives }),
|
|
34
|
+
};
|
|
35
|
+
return new Hono()
|
|
36
|
+
.get("/", async (c) => {
|
|
37
|
+
c.header("Cache-Control", "no-store");
|
|
38
|
+
return c.json(firstRunView(deps.db, await statuses(), deps.clock.now().toISOString()));
|
|
39
|
+
})
|
|
40
|
+
.post("/dismiss", (c) => {
|
|
41
|
+
dismissFirstRun(deps.db, deps.clock.now().toISOString());
|
|
42
|
+
return c.json({ dismissed: true });
|
|
43
|
+
})
|
|
44
|
+
.post("/packs/:id", zValidator("param", packParam, onInvalid), (c) => {
|
|
45
|
+
const installed = installPack(templates, c.req.valid("param").id);
|
|
46
|
+
if (!installed.ok)
|
|
47
|
+
return problem(c, {
|
|
48
|
+
status: 404,
|
|
49
|
+
title: titleOf(404),
|
|
50
|
+
detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
|
|
51
|
+
});
|
|
52
|
+
return c.json(installed.value);
|
|
53
|
+
})
|
|
54
|
+
.post("/short", zValidator("json", quickShortInputSchema, onInvalid), async (c) => {
|
|
55
|
+
const input = c.req.valid("json");
|
|
56
|
+
const earlier = shortRequestProject(deps.db, input.requestId);
|
|
57
|
+
if (earlier !== undefined && projectTitle(deps.db, earlier) !== undefined)
|
|
58
|
+
return c.json({ projectId: earlier, replayed: true });
|
|
59
|
+
const pack = input.packId === undefined ? starterSet : packById(input.packId);
|
|
60
|
+
if (pack === undefined)
|
|
61
|
+
return problem(c, {
|
|
62
|
+
status: 404,
|
|
63
|
+
title: titleOf(404),
|
|
64
|
+
detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
|
|
65
|
+
});
|
|
66
|
+
const modelsFor = deps.modelsFor;
|
|
67
|
+
if (modelsFor === undefined)
|
|
68
|
+
return problem(c, {
|
|
69
|
+
status: 503,
|
|
70
|
+
title: titleOf(503),
|
|
71
|
+
detail: "Slopify can't list the models on this machine right now. Restart Slopify and try again.",
|
|
72
|
+
});
|
|
73
|
+
const plan = await planShortProviders({
|
|
74
|
+
statuses: await statuses(),
|
|
75
|
+
voices: listVoices(deps.db),
|
|
76
|
+
modelsFor,
|
|
77
|
+
suggested: pack.voice,
|
|
78
|
+
});
|
|
79
|
+
if (!plan.ok)
|
|
80
|
+
return problem(c, {
|
|
81
|
+
status: 409,
|
|
82
|
+
title: titleOf(409),
|
|
83
|
+
detail: plan.gaps.map((gap) => gap.message).join(" "),
|
|
84
|
+
extensions: { gaps: plan.gaps },
|
|
85
|
+
});
|
|
86
|
+
const installed = installPack(templates, pack.id, { template: false });
|
|
87
|
+
if (!installed.ok)
|
|
88
|
+
return problem(c, {
|
|
89
|
+
status: 404,
|
|
90
|
+
title: titleOf(404),
|
|
91
|
+
detail: "Slopify doesn't have that starter pack. Reload the page and pick one of the packs listed.",
|
|
92
|
+
});
|
|
93
|
+
const created = await createProject(deps, shortDraft({
|
|
94
|
+
topic: input.topic,
|
|
95
|
+
pack,
|
|
96
|
+
names: installed.value.prompts,
|
|
97
|
+
providers: plan.providers,
|
|
98
|
+
loudness: loudnessOfDefault(readSettings({ db: deps.db, log: deps.log }).loudness),
|
|
99
|
+
}));
|
|
100
|
+
if (!created.ok)
|
|
101
|
+
return problem(c, {
|
|
102
|
+
status: created.status,
|
|
103
|
+
title: titleOf(created.status),
|
|
104
|
+
detail: `${created.detail} ${created.fields.map((field) => field.message).join(" ")}`.trim(),
|
|
105
|
+
...(created.fields.length === 0 ? {} : { extensions: { fields: created.fields } }),
|
|
106
|
+
});
|
|
107
|
+
recordShortRequest(deps.db, input.requestId, created.project.id);
|
|
108
|
+
return c.json({ projectId: created.project.id, replayed: false }, 201);
|
|
109
|
+
})
|
|
110
|
+
.get("/sample", (c) => {
|
|
111
|
+
const samples = sampleProjectIds(deps.db);
|
|
112
|
+
return c.json({ projectId: samples.library, samples });
|
|
113
|
+
})
|
|
114
|
+
.post("/sample/restore", async (c) => {
|
|
115
|
+
const restored = await restoreSamples(sample);
|
|
116
|
+
if (!restored.ok)
|
|
117
|
+
return problem(c, {
|
|
118
|
+
status: restored.status,
|
|
119
|
+
title: titleOf(restored.status),
|
|
120
|
+
detail: restored.detail,
|
|
121
|
+
});
|
|
122
|
+
return c.json({ projectId: restored.samples.library, samples: restored.samples });
|
|
123
|
+
})
|
|
124
|
+
.post("/sample/copy", async (c) => {
|
|
125
|
+
// Which sample to copy is in the body; without one, the Library of Alexandria.
|
|
126
|
+
const body = await c.req.json().catch(() => ({}));
|
|
127
|
+
const input = sampleCopyInputSchema.safeParse(body ?? {});
|
|
128
|
+
if (!input.success)
|
|
129
|
+
return problem(c, {
|
|
130
|
+
status: 400,
|
|
131
|
+
title: titleOf(400),
|
|
132
|
+
detail: "Slopify couldn't tell which sample to copy. Reload the page and try again.",
|
|
133
|
+
});
|
|
134
|
+
const catalogue = deps.catalogue;
|
|
135
|
+
const id = input.data.projectId ?? sampleProjectIds(deps.db).library ?? undefined;
|
|
136
|
+
if (catalogue === undefined || id === undefined)
|
|
137
|
+
return problem(c, {
|
|
138
|
+
status: 404,
|
|
139
|
+
title: titleOf(404),
|
|
140
|
+
detail: "The sample project isn't here. Bring it back with Settings → Backup & storage → Restore samples, then press Make my own copy.",
|
|
141
|
+
});
|
|
142
|
+
const copied = copySample({ ...sample, catalogue: catalogue.read() }, id);
|
|
143
|
+
if (!copied.ok)
|
|
144
|
+
return problem(c, {
|
|
145
|
+
status: copied.status,
|
|
146
|
+
title: titleOf(copied.status),
|
|
147
|
+
detail: copied.detail,
|
|
148
|
+
});
|
|
149
|
+
deps.hub.emitGlobal({ type: "project.updated", projectId: copied.projectId });
|
|
150
|
+
return c.json({ projectId: copied.projectId }, 201);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -41,7 +41,7 @@ export function openFolderRoutes(deps) {
|
|
|
41
41
|
return problem(c, {
|
|
42
42
|
status: 404,
|
|
43
43
|
title: titleOf(404),
|
|
44
|
-
detail: "This output's file was deleted or moved. Use
|
|
44
|
+
detail: "This output's file was deleted or moved. Use More → make it again in its section on the project page to make it again.",
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
return replyForFolder(c, deps, id, download.download.path);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { bundledPatchNotesDir, PatchNotesMissing, readPatchNote, } from "../../slices/patch-notes/library.js";
|
|
3
|
+
import { markPatchNotesSeen, readPatchNotes } from "../../slices/patch-notes/seen.js";
|
|
4
|
+
import { problem, titleOf } from "./problem.js";
|
|
5
|
+
// A build without its notes says so in plain words; anything else is the app's error handler's.
|
|
6
|
+
function missing(c, error) {
|
|
7
|
+
if (!(error instanceof PatchNotesMissing))
|
|
8
|
+
throw error;
|
|
9
|
+
return problem(c, { status: 500, title: titleOf(500), detail: error.message });
|
|
10
|
+
}
|
|
11
|
+
// Settings → Patch notes: the list (with which note is due to open by itself), one note's
|
|
12
|
+
// Markdown, and recording that the note that opened by itself was closed.
|
|
13
|
+
// Preserve Hono's inferred route types for the web client.
|
|
14
|
+
export function patchNotesRoutes(deps) {
|
|
15
|
+
const dir = deps.patchNotesDir ?? bundledPatchNotesDir();
|
|
16
|
+
return new Hono()
|
|
17
|
+
.get("/", async (c) => {
|
|
18
|
+
try {
|
|
19
|
+
return c.json(await readPatchNotes(deps.db, dir, deps.version));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return missing(c, error);
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.post("/seen", (c) => {
|
|
26
|
+
markPatchNotesSeen(deps.db, deps.version);
|
|
27
|
+
return c.json({ seen: true });
|
|
28
|
+
})
|
|
29
|
+
.get("/:id", async (c) => {
|
|
30
|
+
let markdown;
|
|
31
|
+
try {
|
|
32
|
+
markdown = await readPatchNote(dir, c.req.param("id"));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return missing(c, error);
|
|
36
|
+
}
|
|
37
|
+
if (markdown === undefined)
|
|
38
|
+
return problem(c, {
|
|
39
|
+
status: 404,
|
|
40
|
+
title: titleOf(404),
|
|
41
|
+
detail: "These patch notes are not in this version of Slopify. Open Settings → Patch notes and pick one from the list.",
|
|
42
|
+
});
|
|
43
|
+
return c.body(markdown, 200, { "content-type": "text/markdown; charset=utf-8" });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -5,6 +5,7 @@ import { modelFields } from "../../catalog/validate.js";
|
|
|
5
5
|
import { runDraftSchema } from "../../slices/admission/repo.js";
|
|
6
6
|
import { admit } from "../../slices/admission/rules.js";
|
|
7
7
|
import { batchExists, enqueueBatch, pumpQueue, queueEntries } from "../../slices/batch/index.js";
|
|
8
|
+
import { castVoicedRun } from "../../slices/channels/runs.js";
|
|
8
9
|
import { estimateRun } from "../../slices/estimate/index.js";
|
|
9
10
|
import { resolveFont } from "../../slices/fonts/index.js";
|
|
10
11
|
import { pickTemplates, renderPicked } from "../../slices/library/slots.js";
|
|
@@ -32,8 +33,9 @@ export function planningRoutes(deps) {
|
|
|
32
33
|
title: item.title,
|
|
33
34
|
values: { ...input.draft.values, ...item.values },
|
|
34
35
|
})) ?? [input.draft];
|
|
35
|
-
return drafts.map((
|
|
36
|
-
|
|
36
|
+
return drafts.map((sent, index) => {
|
|
37
|
+
// Cast speakers take the cast's voices as they are now, as a run started from Play does.
|
|
38
|
+
const picked = pickTemplates(deps.db, castVoicedRun(deps.db, sent));
|
|
37
39
|
const admitted = admit({
|
|
38
40
|
draft: picked.draft,
|
|
39
41
|
staged: stagedFiles(deps.db),
|