@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
|
@@ -1,21 +1,68 @@
|
|
|
1
|
+
import { aliasMatches } from "../../kernel/ports/narration-aliases.js";
|
|
1
2
|
import { cardinal, numberForms } from "./numbers.js";
|
|
2
3
|
// Original display words remain intact; only acoustic matching uses normalized English.
|
|
3
|
-
|
|
4
|
+
// Narration aliases ("Dr." said as "Doctor") make the aliased form the default for the
|
|
5
|
+
// written words they cover, with the written form kept as an alternative for audio that
|
|
6
|
+
// was not aliased (an uploaded chunk, say).
|
|
7
|
+
// `formsOf` is the language's normaliser (`multilingual.ts` passes its own); the default is
|
|
8
|
+
// English, and `empty` is the sentence for a text with nothing to say.
|
|
9
|
+
export function speechWords(text, observed = "", aliases = [], formsOf = wordForms, empty = "Local subtitles currently require an English transcript with spoken words.") {
|
|
10
|
+
const matches = aliases.length === 0 ? [] : aliasMatches(text, aliases);
|
|
4
11
|
const words = [];
|
|
5
|
-
for (const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
12
|
+
for (const token of text.matchAll(/\S+/g)) {
|
|
13
|
+
const textWord = token[0];
|
|
14
|
+
const start = token.index;
|
|
15
|
+
const end = start + textWord.length;
|
|
16
|
+
const plain = formsOf(textWord);
|
|
17
|
+
const touching = matches.filter((match) => match.start < end && start < match.end);
|
|
18
|
+
let forms = [...plain];
|
|
19
|
+
if (touching.length > 0) {
|
|
20
|
+
let said = "";
|
|
21
|
+
let at = start;
|
|
22
|
+
for (const match of touching) {
|
|
23
|
+
if (match.start >= at)
|
|
24
|
+
said += textWord.slice(at - start, match.start - start);
|
|
25
|
+
// The spoken form belongs to the word the written form starts in.
|
|
26
|
+
if (match.start >= start)
|
|
27
|
+
said += ` ${match.spoken} `;
|
|
28
|
+
at = Math.max(at, Math.min(match.end, end));
|
|
29
|
+
}
|
|
30
|
+
said += textWord.slice(at - start);
|
|
31
|
+
const aliased = said
|
|
32
|
+
.trim()
|
|
33
|
+
.split(/\s+/)
|
|
34
|
+
.map((piece) => (piece === "" ? "" : (formsOf(piece)[0] ?? "")))
|
|
35
|
+
.filter((form) => form !== "")
|
|
36
|
+
.join(" ");
|
|
37
|
+
forms = [aliased, ...plain.filter((form) => form !== aliased)];
|
|
38
|
+
}
|
|
39
|
+
const spoken = pick(forms, observed);
|
|
40
|
+
const previous = words.at(-1);
|
|
8
41
|
if (spoken === "") {
|
|
9
|
-
|
|
10
|
-
if (previous !== undefined)
|
|
42
|
+
if (previous !== undefined) {
|
|
11
43
|
previous.text += ` ${textWord}`;
|
|
44
|
+
// A word an alias swallowed ("al." of "et al.") still counts when the written
|
|
45
|
+
// form was read out.
|
|
46
|
+
const next = plain[0];
|
|
47
|
+
if (touching.length > 0 && next !== undefined)
|
|
48
|
+
previous.forms = previous.forms.map((form, index) => index === 0 && previous.aliased ? form : `${form} ${next}`);
|
|
49
|
+
}
|
|
12
50
|
continue;
|
|
13
51
|
}
|
|
14
|
-
words.push({ text: textWord, spoken });
|
|
52
|
+
words.push({ text: textWord, spoken, forms, aliased: touching.length > 0 });
|
|
15
53
|
}
|
|
16
54
|
if (words.length === 0)
|
|
17
|
-
throw new Error(
|
|
18
|
-
return words;
|
|
55
|
+
throw new Error(empty);
|
|
56
|
+
return words.map(({ text: shown, spoken, forms }) => forms.length > 1 ? { text: shown, spoken, forms } : { text: shown, spoken });
|
|
57
|
+
}
|
|
58
|
+
// The form of a word the recording seems to say, else its default.
|
|
59
|
+
export function respoken(word, observed, formsOf = wordForms) {
|
|
60
|
+
if (word.forms === undefined)
|
|
61
|
+
return speechWords(word.text, observed, [], formsOf)[0] ?? word;
|
|
62
|
+
return { ...word, spoken: pick(word.forms, observed) };
|
|
63
|
+
}
|
|
64
|
+
function pick(forms, observed) {
|
|
65
|
+
return (forms.find((form) => form !== "" && ` ${observed} `.includes(` ${form} `)) ?? forms[0] ?? "");
|
|
19
66
|
}
|
|
20
67
|
function wordForms(raw) {
|
|
21
68
|
const plain = raw.normalize("NFKD").replace(/\p{M}/gu, "").replace(/[‘’]/g, "'");
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { alignWindow, frameSeconds
|
|
1
|
+
import { alignWindow, frameSeconds } from "./ctc.js";
|
|
2
2
|
import { agreesWithSpeech } from "./quality.js";
|
|
3
|
-
import {
|
|
3
|
+
import { englishSpec } from "./spec.js";
|
|
4
4
|
// Recovery omits bounded transcript spans only after confirming surrounding speech.
|
|
5
|
-
export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, skipBudget) {
|
|
5
|
+
export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, skipBudget, spec = englishSpec) {
|
|
6
|
+
const { gates, mismatch } = spec;
|
|
7
|
+
const agrees = (expected, observed, maximumError) => agreesWithSpeech(expected, observed, maximumError, spec.comparable);
|
|
6
8
|
try {
|
|
7
9
|
return {
|
|
8
|
-
words: accepted(logits, frames, candidate, complete, cutoff),
|
|
10
|
+
words: accepted(logits, frames, candidate, complete, cutoff, spec),
|
|
9
11
|
skipped: 0,
|
|
10
12
|
omissionStart: 0,
|
|
11
13
|
};
|
|
@@ -16,19 +18,20 @@ export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, s
|
|
|
16
18
|
}
|
|
17
19
|
if (skipBudget <= 0)
|
|
18
20
|
throw new Error(mismatch);
|
|
19
|
-
const heard = greedy(logits, frames).split(/\s+/);
|
|
21
|
+
const heard = greedy(logits, frames, spec).split(/\s+/);
|
|
20
22
|
for (let skipped = 1; skipped <= Math.min(40, skipBudget, candidate.length - 4); skipped += 1) {
|
|
21
23
|
const remaining = candidate.slice(skipped);
|
|
22
24
|
const anchor = remaining
|
|
23
25
|
.slice(0, 4)
|
|
24
26
|
.map((word) => word.spoken)
|
|
25
27
|
.join(" ");
|
|
26
|
-
if (
|
|
27
|
-
!
|
|
28
|
+
if (spec.lettersOnly(anchor).length < gates.anchorLetters ||
|
|
29
|
+
!agrees(anchor, heard.slice(0, anchor.split(/\s+/).length).join(" "), 0.2))
|
|
28
30
|
continue;
|
|
29
31
|
try {
|
|
30
|
-
const words = accepted(logits, frames, remaining, complete, cutoff);
|
|
31
|
-
if (words.length < 4 ||
|
|
32
|
+
const words = accepted(logits, frames, remaining, complete, cutoff, spec);
|
|
33
|
+
if (words.length < 4 ||
|
|
34
|
+
words.slice(0, 4).some((word) => (word.confidence ?? 0) < gates.anchorConfidence))
|
|
32
35
|
continue;
|
|
33
36
|
return { words, skipped, omissionStart: 0 };
|
|
34
37
|
}
|
|
@@ -37,32 +40,32 @@ export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, s
|
|
|
37
40
|
throw error;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
const observed = greedy(logits, frames
|
|
43
|
+
const observed = spec.comparable(greedy(logits, frames, spec));
|
|
41
44
|
for (let start = candidate.length - 4; start >= 4; start -= 1) {
|
|
42
|
-
const prefix = candidate
|
|
45
|
+
const prefix = spec.comparable(candidate
|
|
43
46
|
.slice(0, start)
|
|
44
47
|
.map((word) => word.spoken)
|
|
45
|
-
.join("")
|
|
46
|
-
.replace(/[^A-Z']/g, "");
|
|
48
|
+
.join(""));
|
|
47
49
|
if (prefix.length > observed.length)
|
|
48
50
|
continue;
|
|
49
|
-
if (!
|
|
51
|
+
if (!agrees(prefix, observed.slice(0, prefix.length), 0.2))
|
|
50
52
|
continue;
|
|
51
53
|
for (let skipped = 1; skipped <= Math.min(40, skipBudget, candidate.length - start - 4); skipped += 1) {
|
|
52
54
|
const remaining = [...candidate.slice(0, start), ...candidate.slice(start + skipped)];
|
|
53
|
-
const anchor = remaining
|
|
55
|
+
const anchor = spec.comparable(remaining
|
|
54
56
|
.slice(start, start + 4)
|
|
55
57
|
.map((word) => word.spoken)
|
|
56
|
-
.join("")
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
!
|
|
60
|
-
!agreesWithSpeech(prefix + anchor, observed.slice(0, prefix.length + anchor.length), 0.15))
|
|
58
|
+
.join(""));
|
|
59
|
+
if (anchor.length < gates.anchorLetters ||
|
|
60
|
+
!agrees(anchor, observed.slice(prefix.length, prefix.length + anchor.length), 0.2) ||
|
|
61
|
+
!agrees(prefix + anchor, observed.slice(0, prefix.length + anchor.length), 0.15))
|
|
61
62
|
continue;
|
|
62
63
|
try {
|
|
63
|
-
const words = accepted(logits, frames, remaining, complete, cutoff, 0.2);
|
|
64
|
+
const words = accepted(logits, frames, remaining, complete, cutoff, spec, 0.2);
|
|
64
65
|
if (words.length < start + 4 ||
|
|
65
|
-
words
|
|
66
|
+
words
|
|
67
|
+
.slice(start - 4, start + 4)
|
|
68
|
+
.some((word) => (word.confidence ?? 0) < gates.anchorConfidence))
|
|
66
69
|
continue;
|
|
67
70
|
return { words, skipped, omissionStart: start };
|
|
68
71
|
}
|
|
@@ -74,27 +77,28 @@ export function alignSpeechWindow(logits, frames, candidate, complete, cutoff, s
|
|
|
74
77
|
}
|
|
75
78
|
throw new Error(mismatch);
|
|
76
79
|
}
|
|
77
|
-
function accepted(logits, frames, candidate, complete, cutoff, maximumError =
|
|
78
|
-
const words = alignWindow(logits, frames, candidate, complete).words.filter((word) => word.end <= cutoff);
|
|
80
|
+
function accepted(logits, frames, candidate, complete, cutoff, spec, maximumError = spec.gates.maximumError) {
|
|
81
|
+
const words = alignWindow(logits, frames, candidate, complete, spec).words.filter((word) => word.end <= cutoff);
|
|
79
82
|
const last = words.at(-1);
|
|
80
83
|
if (last === undefined ||
|
|
81
84
|
!agreesWithSpeech(candidate
|
|
82
85
|
.slice(0, words.length)
|
|
83
86
|
.map((word) => word.spoken)
|
|
84
|
-
.join(" "), greedy(logits, Math.min(frames, Math.ceil(last.end / frameSeconds))), maximumError))
|
|
85
|
-
throw new Error(mismatch);
|
|
87
|
+
.join(" "), greedy(logits, Math.min(frames, Math.ceil(last.end / frameSeconds)), spec), maximumError, spec.comparable))
|
|
88
|
+
throw new Error(spec.mismatch);
|
|
86
89
|
return words;
|
|
87
90
|
}
|
|
88
|
-
export function greedy(logits, frames) {
|
|
91
|
+
export function greedy(logits, frames, spec = englishSpec) {
|
|
92
|
+
const labels = spec.labels;
|
|
89
93
|
let previous = -1;
|
|
90
94
|
let text = "";
|
|
91
95
|
for (let frame = 0; frame < frames; frame += 1) {
|
|
92
96
|
let best = 0;
|
|
93
|
-
for (let label = 1; label <
|
|
94
|
-
if ((logits[frame *
|
|
97
|
+
for (let label = 1; label < labels; label += 1)
|
|
98
|
+
if ((logits[frame * labels + label] ?? -Infinity) > (logits[frame * labels + best] ?? -Infinity))
|
|
95
99
|
best = label;
|
|
96
100
|
if (best !== previous && best !== 0)
|
|
97
|
-
text += letters[best] ?? "";
|
|
101
|
+
text += spec.letters[best] ?? "";
|
|
98
102
|
previous = best;
|
|
99
103
|
}
|
|
100
104
|
return text.trim().replace(/\s+/g, " ");
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { open, readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { SubtitleMismatch } from "../../kernel/ports/subtitles.js";
|
|
3
|
-
import {
|
|
3
|
+
import { alignmentSpecFor } from "./multilingual.js";
|
|
4
4
|
import { workerInput } from "./protocol.js";
|
|
5
|
-
import { speechWords } from "./text.js";
|
|
6
5
|
import { subtitleThreads } from "./threads.js";
|
|
7
6
|
import { alignSpeechWindow, greedy } from "./window.js";
|
|
8
7
|
const sampleRate = 16000;
|
|
@@ -26,13 +25,15 @@ function send(message) {
|
|
|
26
25
|
process.send?.(message);
|
|
27
26
|
}
|
|
28
27
|
async function run(input) {
|
|
28
|
+
const spec = alignmentSpecFor(input.language);
|
|
29
|
+
const { mismatch } = spec;
|
|
29
30
|
const { ort, session } = await openSession(input.modelPath);
|
|
30
31
|
const file = await open(input.pcmPath, "r");
|
|
31
32
|
try {
|
|
32
33
|
const totalSamples = (await stat(input.pcmPath)).size / 4;
|
|
33
34
|
if (!Number.isInteger(totalSamples) || totalSamples < sampleRate / 10)
|
|
34
35
|
throw new Error("The narration is too short to align subtitles.");
|
|
35
|
-
const source =
|
|
36
|
+
const source = spec.words(input.text, "", input.aliases ?? []);
|
|
36
37
|
const output = [];
|
|
37
38
|
let cursor = 0;
|
|
38
39
|
let omitted = 0;
|
|
@@ -51,28 +52,29 @@ async function run(input) {
|
|
|
51
52
|
const result = await session.run({
|
|
52
53
|
input_values: new ort.Tensor("float32", normalized, [1, count]),
|
|
53
54
|
});
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
!(
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
const raw = result.logits;
|
|
56
|
+
if (raw === undefined ||
|
|
57
|
+
!(raw.data instanceof Float32Array) ||
|
|
58
|
+
raw.dims.length !== 3 ||
|
|
59
|
+
raw.dims[2] !== spec.modelLabels)
|
|
59
60
|
throw new Error("The local subtitle model returned an unsupported result.");
|
|
60
|
-
const frames =
|
|
61
|
-
const
|
|
62
|
-
|
|
61
|
+
const frames = raw.dims[1] ?? 0;
|
|
62
|
+
const logits = { data: spec.compact?.(raw.data, frames) ?? raw.data };
|
|
63
|
+
const observed = greedy(logits.data, frames, spec);
|
|
64
|
+
if (spec.lettersOnly(observed).length === 0) {
|
|
63
65
|
sampleAt += count;
|
|
64
66
|
for (const tensor of Object.values(result))
|
|
65
67
|
tensor.dispose();
|
|
66
68
|
continue;
|
|
67
69
|
}
|
|
68
|
-
const candidate = candidates(source, cursor, observed);
|
|
70
|
+
const candidate = candidates(source, cursor, observed, spec);
|
|
69
71
|
const finalWindow = sampleAt + count >= totalSamples;
|
|
70
72
|
const complete = finalWindow && cursor + candidate.length === source.length;
|
|
71
73
|
const cutoff = finalWindow ? count / sampleRate : count / sampleRate - overlapSeconds;
|
|
72
74
|
const stuck = () => new SubtitleMismatch(mismatch, sampleAt / sampleRate, snippet(candidate.map((word) => word.text)), snippet(observed.split(/\s+/)));
|
|
73
75
|
let recovered;
|
|
74
76
|
try {
|
|
75
|
-
recovered = alignSpeechWindow(logits.data, frames, candidate, complete, cutoff, cursor === 0 ? 0 : omissionBudget - omitted);
|
|
77
|
+
recovered = alignSpeechWindow(logits.data, frames, candidate, complete, cutoff, cursor === 0 ? 0 : omissionBudget - omitted, spec);
|
|
76
78
|
}
|
|
77
79
|
catch (error) {
|
|
78
80
|
if (error instanceof Error && error.message === mismatch)
|
|
@@ -121,10 +123,12 @@ async function run(input) {
|
|
|
121
123
|
input_values: new ort.Tensor("float32", audio, [1, count]),
|
|
122
124
|
});
|
|
123
125
|
const logits = result.logits;
|
|
124
|
-
if (logits !== undefined &&
|
|
125
|
-
logits.
|
|
126
|
-
greedy(logits.data,
|
|
127
|
-
|
|
126
|
+
if (logits !== undefined && logits.data instanceof Float32Array) {
|
|
127
|
+
const frames = logits.dims[1] ?? 0;
|
|
128
|
+
const heard = greedy(spec.compact?.(logits.data, frames) ?? logits.data, frames, spec);
|
|
129
|
+
if (spec.lettersOnly(heard).length > 8)
|
|
130
|
+
throw new SubtitleMismatch(mismatch, sampleAt / sampleRate, "", snippet(heard.split(/\s+/)));
|
|
131
|
+
}
|
|
128
132
|
for (const tensor of Object.values(result))
|
|
129
133
|
tensor.dispose();
|
|
130
134
|
}
|
|
@@ -173,16 +177,14 @@ async function openSession(modelPath) {
|
|
|
173
177
|
});
|
|
174
178
|
return { ort: native, session };
|
|
175
179
|
}
|
|
176
|
-
function candidates(source, cursor, observed) {
|
|
180
|
+
function candidates(source, cursor, observed, spec) {
|
|
177
181
|
const selected = [];
|
|
178
182
|
let length = 0;
|
|
179
183
|
for (let index = cursor; index < source.length; index += 1) {
|
|
180
184
|
const word = source[index];
|
|
181
185
|
if (word === undefined)
|
|
182
186
|
break;
|
|
183
|
-
const normalized =
|
|
184
|
-
if (normalized === undefined)
|
|
185
|
-
continue;
|
|
187
|
+
const normalized = spec.respoken(word, observed);
|
|
186
188
|
if (length + normalized.spoken.length + 1 > 900)
|
|
187
189
|
break;
|
|
188
190
|
selected.push(normalized);
|
package/dist/adapters/explain.js
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
// A provider's error body can be a whole HTML page; the first few hundred characters say why.
|
|
7
7
|
const detailMax = 300;
|
|
8
8
|
export function missingKey(provider) {
|
|
9
|
-
return `No ${provider} API key is saved. Add one in Settings → Providers, then use
|
|
9
|
+
return `No ${provider} API key is saved. Add one in Settings → Providers, then use Try again.`;
|
|
10
10
|
}
|
|
11
11
|
export const refreshList = "refresh the list";
|
|
12
|
-
export const checkChoice = "Check the provider and model chosen in the Providers section of Edit project, then use
|
|
12
|
+
export const checkChoice = "Check the provider and model chosen in the Providers section of Edit project, then use Try again.";
|
|
13
13
|
export function httpFailure(failure) {
|
|
14
14
|
const { provider, status } = failure;
|
|
15
15
|
const said = quoted(failure.label ?? `error ${String(status)}`, failure.detail);
|
|
16
|
-
const next = failure.next ?? "use
|
|
16
|
+
const next = failure.next ?? "use Try again";
|
|
17
17
|
if (status === 401) {
|
|
18
18
|
return `${provider} did not accept the API key${said}. The key may be wrong, expired or revoked: paste a current key in Settings → Providers, then ${next}.`;
|
|
19
19
|
}
|
|
@@ -32,23 +32,23 @@ export function httpFailure(failure) {
|
|
|
32
32
|
if (status >= 500) {
|
|
33
33
|
return `${provider} had a problem on its side${said}. This is usually temporary: wait a few minutes, then ${next}.`;
|
|
34
34
|
}
|
|
35
|
-
return `${provider} answered with an unexpected error${said}. ${failure.next === undefined ? "Use
|
|
35
|
+
return `${provider} answered with an unexpected error${said}. ${failure.next === undefined ? "Use Try again" : "Try again"}; if it keeps failing, use Download diagnostics in Settings and report it.`;
|
|
36
36
|
}
|
|
37
37
|
// A failure the provider reported inside an otherwise successful answer, with no status.
|
|
38
38
|
export function providerSaid(provider, what, detail, next) {
|
|
39
39
|
return `${provider} ${what}${quoted("", detail)}. ${next}`;
|
|
40
40
|
}
|
|
41
41
|
export function unreadable(provider) {
|
|
42
|
-
return `${provider} sent back an answer Slopify could not read. This is usually temporary: use
|
|
42
|
+
return `${provider} sent back an answer Slopify could not read. This is usually temporary: use Try again; if it keeps happening, use Download diagnostics in Settings and report it.`;
|
|
43
43
|
}
|
|
44
44
|
export function droppedStream(provider) {
|
|
45
|
-
return `The connection to ${provider} dropped before the answer was complete. Check your internet connection, then use
|
|
45
|
+
return `The connection to ${provider} dropped before the answer was complete. Check your internet connection, then use Try again.`;
|
|
46
46
|
}
|
|
47
47
|
export function noAudio(provider) {
|
|
48
|
-
return `${provider} finished without sending any audio. Use
|
|
48
|
+
return `${provider} finished without sending any audio. Use Try again; if it keeps happening, check the voice in Settings → Voices.`;
|
|
49
49
|
}
|
|
50
50
|
export function noImage(provider) {
|
|
51
|
-
return `${provider} finished without sending an image. Use
|
|
51
|
+
return `${provider} finished without sending an image. Use Try again; if it keeps happening, reword the image prompt in the Images section of Edit project or choose another image model in its Providers section.`;
|
|
52
52
|
}
|
|
53
53
|
export function refusedImage(provider, detail) {
|
|
54
54
|
return `${provider} refused to make this image under its content rules${quoted("", detail)}. Reword the image prompt in the Images section of Edit project, or choose another image provider in its Providers section.`;
|
|
@@ -56,10 +56,10 @@ export function refusedImage(provider, detail) {
|
|
|
56
56
|
// A narration request the provider turned down is most often a voice that no longer exists
|
|
57
57
|
// or a part longer than the model takes.
|
|
58
58
|
export function voiceFix(voiceId) {
|
|
59
|
-
return `Check that voice "${voiceId}" still exists in Settings → Voices and that each narration part fits the model's length limit (Chunking, in the Providers section of Edit project), then use
|
|
59
|
+
return `Check that voice "${voiceId}" still exists in Settings → Voices and that each narration part fits the model's length limit (Chunking, in the Providers section of Edit project), then use Try again.`;
|
|
60
60
|
}
|
|
61
61
|
export function internalError(what) {
|
|
62
|
-
return `Slopify hit an internal error (${what}). Use
|
|
62
|
+
return `Slopify hit an internal error (${what}). Use Try again; if it happens again, use Download diagnostics in Settings and report it.`;
|
|
63
63
|
}
|
|
64
64
|
const cliNames = {
|
|
65
65
|
claude: "Claude Code CLI",
|
|
@@ -81,7 +81,7 @@ export function cliReported(binary, detail, next) {
|
|
|
81
81
|
return `The ${cliName(binary)} reported an error${quoted("", detail)}. ${next}`;
|
|
82
82
|
}
|
|
83
83
|
export function cliCheck(binary) {
|
|
84
|
-
return `Use
|
|
84
|
+
return `Use Try again; if it keeps failing, run ${commandOf(binary)} in a terminal to check it works and is signed in, or choose another model in the Providers section of Edit project.`;
|
|
85
85
|
}
|
|
86
86
|
// ` (error 401: "Unauthorized")`, ` (error 401)`, ` ("Unauthorized")`, or nothing.
|
|
87
87
|
export function quoted(label, detail) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { redact } from "../../kernel/log.js";
|
|
2
|
-
import { bridgeLimits, hostFaultSchema, hostFrameSchema, hostHealthSchema, hostImageSchema, hostLlmSchema, hostModelsSchema, hostOpenedSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
|
|
2
|
+
import { bridgeLimits, decodeHostImageReport, hostFaultSchema, hostFrameSchema, hostHealthSchema, hostImageReportHeader, hostImageSchema, hostLlmSchema, hostModelsSchema, hostOpenedSchema, hostOpenFolderSchema, hostStatusSchema, } from "../../kernel/ports/host-cli.js";
|
|
3
3
|
import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
|
|
4
4
|
import { isProviderError, providerError } from "../../kernel/ports/model.js";
|
|
5
5
|
import { sniffImage } from "../image/bytes.js";
|
|
6
|
+
import { claudePlanLimit } from "../llm/claude-code.js";
|
|
7
|
+
import { codexPlanLimit } from "../llm/codex-limits.js";
|
|
6
8
|
import { hostRequest, hostUnavailable, readHostBytes, } from "./transport.js";
|
|
7
|
-
const tooBigForHost = "Slopify could not send this request to the host helper because it is larger than, or shaped differently from, what the helper accepts. Make the inputs shorter in Edit project, then use
|
|
9
|
+
const tooBigForHost = "Slopify could not send this request to the host helper because it is larger than, or shaped differently from, what the helper accepts. Make the inputs shorter in Edit project, then use Try again; if it happens again, use Download diagnostics in Settings and report it.";
|
|
8
10
|
export function createHostCliClient(options) {
|
|
9
11
|
async function request(input) {
|
|
10
12
|
if (!options.directory)
|
|
@@ -125,6 +127,8 @@ export function createHostCliClient(options) {
|
|
|
125
127
|
kind: "llm",
|
|
126
128
|
body: Buffer.from(JSON.stringify(parsed.data)),
|
|
127
129
|
signal: req.signal,
|
|
130
|
+
}).catch((error) => {
|
|
131
|
+
throw isProviderError(error) ? withPlanLimit(error, id) : error;
|
|
128
132
|
});
|
|
129
133
|
try {
|
|
130
134
|
if (response.headers["content-type"]?.split(";")[0] !== "application/x-ndjson")
|
|
@@ -133,7 +137,7 @@ export function createHostCliClient(options) {
|
|
|
133
137
|
}
|
|
134
138
|
catch (error) {
|
|
135
139
|
if (isProviderError(error))
|
|
136
|
-
throw error;
|
|
140
|
+
throw withPlanLimit(error, id);
|
|
137
141
|
throw hostUnavailable(true);
|
|
138
142
|
}
|
|
139
143
|
finally {
|
|
@@ -159,6 +163,18 @@ export function createHostCliClient(options) {
|
|
|
159
163
|
base64: Buffer.from(req.reference.bytes).toString("base64"),
|
|
160
164
|
},
|
|
161
165
|
}),
|
|
166
|
+
...(req.cast === undefined
|
|
167
|
+
? {}
|
|
168
|
+
: {
|
|
169
|
+
cast: req.cast.map((member) => ({
|
|
170
|
+
name: member.name,
|
|
171
|
+
description: member.description,
|
|
172
|
+
images: member.images.map((image) => ({
|
|
173
|
+
mime: image.mime,
|
|
174
|
+
base64: Buffer.from(image.bytes).toString("base64"),
|
|
175
|
+
})),
|
|
176
|
+
})),
|
|
177
|
+
}),
|
|
162
178
|
});
|
|
163
179
|
if (!parsed.success)
|
|
164
180
|
throw providerError({
|
|
@@ -172,6 +188,8 @@ export function createHostCliClient(options) {
|
|
|
172
188
|
kind: "image",
|
|
173
189
|
body: Buffer.from(JSON.stringify(parsed.data)),
|
|
174
190
|
signal: req.signal,
|
|
191
|
+
}).catch((error) => {
|
|
192
|
+
throw isProviderError(error) ? withPlanLimit(error, "codex-image") : error;
|
|
175
193
|
});
|
|
176
194
|
try {
|
|
177
195
|
const mime = response.headers["content-type"];
|
|
@@ -180,11 +198,15 @@ export function createHostCliClient(options) {
|
|
|
180
198
|
const bytes = await readHostBytes(response, bridgeLimits.image);
|
|
181
199
|
if (sniffImage(bytes) !== mime)
|
|
182
200
|
throw hostUnavailable(true);
|
|
183
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
bytes,
|
|
203
|
+
mime,
|
|
204
|
+
...decodeHostImageReport(response.headers[hostImageReportHeader]),
|
|
205
|
+
};
|
|
184
206
|
}
|
|
185
207
|
catch (error) {
|
|
186
208
|
if (isProviderError(error))
|
|
187
|
-
throw error;
|
|
209
|
+
throw withPlanLimit(error, "codex-image");
|
|
188
210
|
throw hostUnavailable(true);
|
|
189
211
|
}
|
|
190
212
|
finally {
|
|
@@ -194,6 +216,23 @@ export function createHostCliClient(options) {
|
|
|
194
216
|
},
|
|
195
217
|
};
|
|
196
218
|
}
|
|
219
|
+
// A used-up plan crosses the bridge as a rate-limit fault carrying the CLI's own words, which
|
|
220
|
+
// the host-side adapter already quoted; they are read again here so a Docker run waits for the
|
|
221
|
+
// reset like a local one instead of failing.
|
|
222
|
+
export function withPlanLimit(error, id) {
|
|
223
|
+
if (error.fault.kind !== "rate_limit" || error.fault.planLimit !== undefined)
|
|
224
|
+
return error;
|
|
225
|
+
const planLimit = id === "claude-code"
|
|
226
|
+
? claudePlanLimit(error.message, 429, undefined)
|
|
227
|
+
: id === "gemini"
|
|
228
|
+
? /daily quota/i.test(error.message)
|
|
229
|
+
? { account: "gemini", resetsAt: null }
|
|
230
|
+
: undefined
|
|
231
|
+
: codexPlanLimit(error.message, new Date());
|
|
232
|
+
return planLimit === undefined
|
|
233
|
+
? error
|
|
234
|
+
: providerError({ kind: "rate_limit", message: error.message, planLimit });
|
|
235
|
+
}
|
|
197
236
|
async function* readEvents(response) {
|
|
198
237
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
199
238
|
let pending = "";
|
|
@@ -2,15 +2,15 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { open } from "node:fs/promises";
|
|
3
3
|
import { request } from "node:http";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { bridgeLimits } from "../../kernel/ports/host-cli.js";
|
|
5
|
+
import { bridgeLimits, hostFramesHeader, hostFramesVersion } from "../../kernel/ports/host-cli.js";
|
|
6
6
|
import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
|
|
7
7
|
import { providerError } from "../../kernel/ports/model.js";
|
|
8
8
|
export function hostUnavailable(submitted = false) {
|
|
9
9
|
return providerError({
|
|
10
10
|
kind: "unavailable",
|
|
11
11
|
message: submitted
|
|
12
|
-
? "The connection to the host helper dropped before the CLI's answer arrived, so Slopify cannot tell whether it finished (it may already have used your quota). Check the host helper is still running, then use
|
|
13
|
-
: "Slopify cannot reach its host helper, which runs the Claude Code, Codex and Gemini CLIs outside Docker. On the host computer, run npx @gentbajko/slopify@latest --docker to set it up or restart it, then use
|
|
12
|
+
? "The connection to the host helper dropped before the CLI's answer arrived, so Slopify cannot tell whether it finished (it may already have used your quota). Check the host helper is still running, then use Try again."
|
|
13
|
+
: "Slopify cannot reach its host helper, which runs the Claude Code, Codex and Gemini CLIs outside Docker. On the host computer, run npx @gentbajko/slopify@latest --docker to set it up or restart it, then use Try again.",
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
async function tokenAt(directory) {
|
|
@@ -34,7 +34,7 @@ export async function hostRequest(options) {
|
|
|
34
34
|
if (options.body && options.body.byteLength > bridgeLimits.request)
|
|
35
35
|
throw providerError({
|
|
36
36
|
kind: "unsupported",
|
|
37
|
-
message: "This request is larger than the host helper accepts (16 MB), so nothing was sent. Make the inputs shorter in Edit project, or choose an API provider such as OpenRouter in its Providers section, then use
|
|
37
|
+
message: "This request is larger than the host helper accepts (16 MB), so nothing was sent. Make the inputs shorter in Edit project, or choose an API provider such as OpenRouter in its Providers section, then use Try again.",
|
|
38
38
|
});
|
|
39
39
|
let token;
|
|
40
40
|
try {
|
|
@@ -55,6 +55,8 @@ export async function hostRequest(options) {
|
|
|
55
55
|
headers: {
|
|
56
56
|
authorization: `Bearer ${token}`,
|
|
57
57
|
"content-type": "application/json",
|
|
58
|
+
// The stream frames this app reads; an older helper ignores the header.
|
|
59
|
+
...(options.kind === "llm" ? { [hostFramesHeader]: String(hostFramesVersion) } : {}),
|
|
58
60
|
...(options.body ? { "content-length": options.body.byteLength } : {}),
|
|
59
61
|
},
|
|
60
62
|
}, (incoming) => {
|
|
@@ -126,7 +128,7 @@ export async function readHostBytes(response, maximum) {
|
|
|
126
128
|
if (length > maximum)
|
|
127
129
|
throw providerError({
|
|
128
130
|
kind: "unavailable",
|
|
129
|
-
message: "The host helper sent back more data than Slopify accepts, so the answer was dropped. Use
|
|
131
|
+
message: "The host helper sent back more data than Slopify accepts, so the answer was dropped. Use Try again; if it happens again, use Download diagnostics in Settings and report it.",
|
|
130
132
|
});
|
|
131
133
|
chunks.push(value);
|
|
132
134
|
}
|
|
@@ -40,7 +40,7 @@ export async function downloadImage(download) {
|
|
|
40
40
|
// quoted back - a fal or Replicate delivery link carries its own signature.
|
|
41
41
|
throw providerError({
|
|
42
42
|
kind: "other",
|
|
43
|
-
message: `${download.provider} made the image, but Slopify could not download it (error ${String(response.status)}). Check your internet connection, then use
|
|
43
|
+
message: `${download.provider} made the image, but Slopify could not download it (error ${String(response.status)}). Check your internet connection, then use Try again.`,
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
@@ -48,7 +48,7 @@ export async function downloadImage(download) {
|
|
|
48
48
|
if (mime === undefined) {
|
|
49
49
|
throw providerError({
|
|
50
50
|
kind: "other",
|
|
51
|
-
message: `${download.provider} sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use
|
|
51
|
+
message: `${download.provider} sent back something that is not a PNG or JPEG image (${describeBytes(bytes)}). Use Try again; if it keeps happening, choose another image model in the Providers section of Edit project.`,
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
return { bytes, mime };
|
|
@@ -8,7 +8,7 @@ const maxOutputBytes = 32 * 1024 * 1024;
|
|
|
8
8
|
function unavailable(detail) {
|
|
9
9
|
return providerError({
|
|
10
10
|
kind: "unavailable",
|
|
11
|
-
message: `The Codex CLI finished, but Slopify could not collect the image: ${detail}. Use
|
|
11
|
+
message: `The Codex CLI finished, but Slopify could not collect the image: ${detail}. Use Try again to make it again (this uses your Codex quota again).`,
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
// A thread that drew more than this is not an image job any more.
|