@gentbajko/slopify 2.4.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 +11 -1
- 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 +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -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-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- 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 +26 -9
- 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/models.yaml +7 -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 +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- 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/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -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 +45 -6
- 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/problem.js +1 -0
- 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 +128 -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 +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- 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 +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- 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 +18 -9
- 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 +164 -9
- 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 +321 -20
- 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 +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- 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/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -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 +113 -24
- 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 +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- 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 +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- 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 +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -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 +29 -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 +67 -2
- 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 +15 -5
- 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 +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -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 +58 -0
- 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 +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- 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 +14 -2
- 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 +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- 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 +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- 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 +48 -23
- 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/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- 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 +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- 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 +48 -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 +11 -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 +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- 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-CAJNT254.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-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- 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,9 +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
|
+
import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
|
|
3
4
|
import { isProviderError, providerError } from "../../kernel/ports/model.js";
|
|
4
5
|
import { sniffImage } from "../image/bytes.js";
|
|
6
|
+
import { claudePlanLimit } from "../llm/claude-code.js";
|
|
7
|
+
import { codexPlanLimit } from "../llm/codex-limits.js";
|
|
5
8
|
import { hostRequest, hostUnavailable, readHostBytes, } from "./transport.js";
|
|
6
|
-
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.";
|
|
7
10
|
export function createHostCliClient(options) {
|
|
8
11
|
async function request(input) {
|
|
9
12
|
if (!options.directory)
|
|
@@ -124,6 +127,8 @@ export function createHostCliClient(options) {
|
|
|
124
127
|
kind: "llm",
|
|
125
128
|
body: Buffer.from(JSON.stringify(parsed.data)),
|
|
126
129
|
signal: req.signal,
|
|
130
|
+
}).catch((error) => {
|
|
131
|
+
throw isProviderError(error) ? withPlanLimit(error, id) : error;
|
|
127
132
|
});
|
|
128
133
|
try {
|
|
129
134
|
if (response.headers["content-type"]?.split(";")[0] !== "application/x-ndjson")
|
|
@@ -132,7 +137,7 @@ export function createHostCliClient(options) {
|
|
|
132
137
|
}
|
|
133
138
|
catch (error) {
|
|
134
139
|
if (isProviderError(error))
|
|
135
|
-
throw error;
|
|
140
|
+
throw withPlanLimit(error, id);
|
|
136
141
|
throw hostUnavailable(true);
|
|
137
142
|
}
|
|
138
143
|
finally {
|
|
@@ -142,12 +147,34 @@ export function createHostCliClient(options) {
|
|
|
142
147
|
}),
|
|
143
148
|
image: {
|
|
144
149
|
id: "codex-image",
|
|
150
|
+
timeoutMs: agentImageTimeoutMs,
|
|
145
151
|
models: () => models("codex-image"),
|
|
146
152
|
generate: async (req) => {
|
|
147
153
|
const parsed = hostImageSchema.safeParse({
|
|
148
154
|
model: req.model,
|
|
149
155
|
prompt: req.prompt,
|
|
150
156
|
aspect: req.aspect,
|
|
157
|
+
...(req.thinking === undefined ? {} : { thinking: req.thinking }),
|
|
158
|
+
...(req.reference === undefined
|
|
159
|
+
? {}
|
|
160
|
+
: {
|
|
161
|
+
reference: {
|
|
162
|
+
mime: req.reference.mime,
|
|
163
|
+
base64: Buffer.from(req.reference.bytes).toString("base64"),
|
|
164
|
+
},
|
|
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
|
+
}),
|
|
151
178
|
});
|
|
152
179
|
if (!parsed.success)
|
|
153
180
|
throw providerError({
|
|
@@ -161,6 +188,8 @@ export function createHostCliClient(options) {
|
|
|
161
188
|
kind: "image",
|
|
162
189
|
body: Buffer.from(JSON.stringify(parsed.data)),
|
|
163
190
|
signal: req.signal,
|
|
191
|
+
}).catch((error) => {
|
|
192
|
+
throw isProviderError(error) ? withPlanLimit(error, "codex-image") : error;
|
|
164
193
|
});
|
|
165
194
|
try {
|
|
166
195
|
const mime = response.headers["content-type"];
|
|
@@ -169,11 +198,15 @@ export function createHostCliClient(options) {
|
|
|
169
198
|
const bytes = await readHostBytes(response, bridgeLimits.image);
|
|
170
199
|
if (sniffImage(bytes) !== mime)
|
|
171
200
|
throw hostUnavailable(true);
|
|
172
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
bytes,
|
|
203
|
+
mime,
|
|
204
|
+
...decodeHostImageReport(response.headers[hostImageReportHeader]),
|
|
205
|
+
};
|
|
173
206
|
}
|
|
174
207
|
catch (error) {
|
|
175
208
|
if (isProviderError(error))
|
|
176
|
-
throw error;
|
|
209
|
+
throw withPlanLimit(error, "codex-image");
|
|
177
210
|
throw hostUnavailable(true);
|
|
178
211
|
}
|
|
179
212
|
finally {
|
|
@@ -183,6 +216,23 @@ export function createHostCliClient(options) {
|
|
|
183
216
|
},
|
|
184
217
|
};
|
|
185
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
|
+
}
|
|
186
236
|
async function* readEvents(response) {
|
|
187
237
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
188
238
|
let pending = "";
|
|
@@ -2,14 +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
|
+
import { agentImageTimeoutMs } from "../../kernel/ports/image.js";
|
|
6
7
|
import { providerError } from "../../kernel/ports/model.js";
|
|
7
8
|
export function hostUnavailable(submitted = false) {
|
|
8
9
|
return providerError({
|
|
9
10
|
kind: "unavailable",
|
|
10
11
|
message: submitted
|
|
11
|
-
? "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
|
|
12
|
-
: "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.",
|
|
13
14
|
});
|
|
14
15
|
}
|
|
15
16
|
async function tokenAt(directory) {
|
|
@@ -33,7 +34,7 @@ export async function hostRequest(options) {
|
|
|
33
34
|
if (options.body && options.body.byteLength > bridgeLimits.request)
|
|
34
35
|
throw providerError({
|
|
35
36
|
kind: "unsupported",
|
|
36
|
-
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.",
|
|
37
38
|
});
|
|
38
39
|
let token;
|
|
39
40
|
try {
|
|
@@ -54,6 +55,8 @@ export async function hostRequest(options) {
|
|
|
54
55
|
headers: {
|
|
55
56
|
authorization: `Bearer ${token}`,
|
|
56
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) } : {}),
|
|
57
60
|
...(options.body ? { "content-length": options.body.byteLength } : {}),
|
|
58
61
|
},
|
|
59
62
|
}, (incoming) => {
|
|
@@ -81,7 +84,11 @@ export async function hostRequest(options) {
|
|
|
81
84
|
reject(error);
|
|
82
85
|
};
|
|
83
86
|
const connectTimer = setTimeout(fail, 5000);
|
|
84
|
-
const deadline = setTimeout(fail, options.kind === "image"
|
|
87
|
+
const deadline = setTimeout(fail, options.kind === "image"
|
|
88
|
+
? agentImageTimeoutMs
|
|
89
|
+
: options.kind === "metadata"
|
|
90
|
+
? 35_000
|
|
91
|
+
: 120_000);
|
|
85
92
|
const cleanup = () => {
|
|
86
93
|
clearTimeout(connectTimer);
|
|
87
94
|
clearTimeout(deadline);
|
|
@@ -121,7 +128,7 @@ export async function readHostBytes(response, maximum) {
|
|
|
121
128
|
if (length > maximum)
|
|
122
129
|
throw providerError({
|
|
123
130
|
kind: "unavailable",
|
|
124
|
-
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.",
|
|
125
132
|
});
|
|
126
133
|
chunks.push(value);
|
|
127
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 };
|