@gentbajko/slopify 2.5.0 → 3.0.1
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 +24 -41
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +31 -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 +85 -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/gemini.js +206 -0
- 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/pcm-mp3.js +62 -0
- package/dist/adapters/tts/system.js +147 -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 +38 -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/assets/style-preview/narration.mp3 +0 -0
- package/dist/assets/style-preview/tall-1.jpg +0 -0
- package/dist/assets/style-preview/tall-2.jpg +0 -0
- package/dist/assets/style-preview/tall-3.jpg +0 -0
- package/dist/assets/style-preview/wide-1.jpg +0 -0
- package/dist/assets/style-preview/wide-2.jpg +0 -0
- package/dist/assets/style-preview/wide-3.jpg +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +17 -14
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +2 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +118 -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 +72 -0
- package/dist/edge/autostart/service.js +90 -0
- package/dist/edge/cli-args.js +71 -0
- package/dist/edge/cli.js +64 -63
- 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 +160 -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 +3 -1
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +38 -11
- 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/byte-range.js +42 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/diagnostics.js +1 -0
- package/dist/edge/http/draft-files.js +2 -4
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +11 -7
- 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 +212 -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/project-templates.js +19 -2
- package/dist/edge/http/projects.js +37 -100
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +119 -3
- package/dist/edge/http/reviews.js +131 -0
- package/dist/edge/http/revision-files.js +3 -5
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +115 -6
- package/dist/edge/http/settings.js +84 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +43 -2
- package/dist/edge/http/studio.js +287 -0
- package/dist/edge/http/style-preview.js +61 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/tutorials.js +59 -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 +92 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/gemini-login.js +83 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +9 -1
- package/dist/host-cli/status.js +4 -2
- 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/db/migrations/0041-cast-hosts.sql +3 -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 +10 -5
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/ports/system-speech.js +203 -0
- package/dist/kernel/runner/attempt.js +17 -8
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +40 -5
- package/dist/kernel/runner/index.js +39 -9
- 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 +277 -16
- package/dist/model-catalog.js +2 -0
- 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/3.0.1.md +105 -0
- package/dist/patch-notes/index.json +32 -0
- package/dist/slices/admission/model.js +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +80 -2
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +42 -6
- 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 +221 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +152 -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 +5 -2
- package/dist/slices/control/providers.js +4 -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 +68 -2
- package/dist/slices/eta/history.js +56 -0
- package/dist/slices/eta/model.js +63 -0
- package/dist/slices/eta/view.js +25 -0
- 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 +185 -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 +47 -3
- package/dist/slices/notifications/rules.js +48 -3
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +72 -0
- package/dist/slices/onboarding/full-video.js +81 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +85 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +144 -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 +127 -8
- package/dist/slices/play-drafts/readiness.js +4 -1
- 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 +59 -6
- package/dist/slices/project-templates/next-chapter.js +94 -0
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/schema.js +7 -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 +67 -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 +125 -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 +267 -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 +62 -11
- 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 +3 -3
- 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 +118 -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 +256 -0
- package/dist/slices/run-cost/pricing.js +95 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +222 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +68 -17
- package/dist/slices/schedules/schema.js +179 -1
- package/dist/slices/schedules/service.js +43 -4
- package/dist/slices/schedules/similar.js +128 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +523 -0
- package/dist/slices/settings/first-run.js +106 -0
- package/dist/slices/settings/health.js +247 -0
- package/dist/slices/settings/key-guides.js +138 -0
- package/dist/slices/settings/key-test.js +93 -0
- package/dist/slices/settings/keys.js +10 -5
- package/dist/slices/settings/model-reach.js +52 -0
- package/dist/slices/settings/model.js +55 -1
- package/dist/slices/settings/models.js +5 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/readiness.js +14 -2
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/tutorial-schema.js +5 -0
- 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 +63 -13
- 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 +87 -0
- package/dist/slices/storage/downloads.js +33 -1
- 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 +94 -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 +30 -0
- package/dist/slices/studio/pack.js +211 -0
- package/dist/slices/studio/queue.js +69 -0
- package/dist/slices/studio/settings.js +170 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/narration.js +33 -0
- package/dist/slices/style-preview/render.js +148 -0
- package/dist/slices/style-preview/schema.js +59 -0
- package/dist/slices/style-preview/service.js +92 -0
- package/dist/slices/style-preview/settings.js +139 -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/tutorials/anchors.js +57 -0
- package/dist/slices/tutorials/library.js +185 -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 +87 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +74 -0
- package/dist/slices/voices/grouping.js +59 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +218 -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 +84 -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/tutorials/AI-CLIs.md +146 -0
- package/dist/tutorials/Backups.md +121 -0
- package/dist/tutorials/Calendar.md +103 -0
- package/dist/tutorials/Cast-Library.md +112 -0
- package/dist/tutorials/Channels.md +148 -0
- package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
- package/dist/tutorials/Docker.md +171 -0
- package/dist/tutorials/Document-Themes.md +207 -0
- package/dist/tutorials/Editing-a-Project.md +204 -0
- package/dist/tutorials/FAQ.md +134 -0
- package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
- package/dist/tutorials/Home-and-Projects.md +174 -0
- package/dist/tutorials/Home.md +119 -0
- package/dist/tutorials/Install.md +154 -0
- package/dist/tutorials/Intros-and-Outros.md +86 -0
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
- package/dist/tutorials/Library-Overview.md +75 -0
- package/dist/tutorials/Models.md +133 -0
- package/dist/tutorials/Multiple-Voices.md +161 -0
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
- package/dist/tutorials/Notifications.md +86 -0
- package/dist/tutorials/Other-Languages.md +110 -0
- package/dist/tutorials/PDF-Documents.md +100 -0
- package/dist/tutorials/Play-Images.md +135 -0
- package/dist/tutorials/Play-Narration.md +167 -0
- package/dist/tutorials/Play-Outputs.md +109 -0
- package/dist/tutorials/Play-Overview.md +216 -0
- package/dist/tutorials/Play-Title-and-Article.md +135 -0
- package/dist/tutorials/Play-Video-and-Style.md +143 -0
- package/dist/tutorials/Project-Page.md +228 -0
- package/dist/tutorials/Prompts.md +136 -0
- package/dist/tutorials/Providers-and-Keys.md +290 -0
- package/dist/tutorials/Publishing-to-YouTube.md +135 -0
- package/dist/tutorials/Recovery-and-Retries.md +141 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
- package/dist/tutorials/Schedules.md +154 -0
- package/dist/tutorials/Settings-Reference.md +258 -0
- package/dist/tutorials/Shorts.md +116 -0
- package/dist/tutorials/Start-at-Login.md +126 -0
- package/dist/tutorials/Studio-Extension.md +146 -0
- package/dist/tutorials/Templates.md +146 -0
- package/dist/tutorials/Trash.md +87 -0
- package/dist/tutorials/Troubleshooting.md +149 -0
- package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
- package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
- package/dist/tutorials/Video-Editing.md +135 -0
- package/dist/tutorials/Where-Your-Files-Live.md +149 -0
- package/dist/tutorials/YouTube-Description.md +132 -0
- package/dist/tutorials/Your-First-Short.md +125 -0
- package/dist/tutorials/_Footer.md +1 -0
- package/dist/tutorials/_Sidebar.md +69 -0
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-DBLSfqA-.css +1 -0
- package/dist/web/assets/index-DFdIhmm0.js +307 -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-B9OY1ioX.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -5
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-CktpxlJd.js +0 -171
- package/dist/web/assets/index-DB-vOnGU.css +0 -1
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { choiceSites, retiredModelUsage, slotLabels, } from "../model-upkeep/usage.js";
|
|
2
|
+
import { cliPathStatus } from "./cli-paths.js";
|
|
3
|
+
import { keyGuides } from "./key-guides.js";
|
|
4
|
+
import { testProviderKey } from "./key-test.js";
|
|
5
|
+
import { isLocalCliProvider } from "./model.js";
|
|
6
|
+
import { modelReach } from "./model-reach.js";
|
|
7
|
+
import { providerStatuses } from "./readiness.js";
|
|
8
|
+
const signInCommand = {
|
|
9
|
+
"claude-code": "claude auth login",
|
|
10
|
+
codex: "codex login",
|
|
11
|
+
gemini: "gemini",
|
|
12
|
+
};
|
|
13
|
+
function loginSource(id) {
|
|
14
|
+
return id === "codex-image" ? "codex" : id;
|
|
15
|
+
}
|
|
16
|
+
// Where each provider's models are chosen, as "Text model in template “Weekly”".
|
|
17
|
+
function uses(db) {
|
|
18
|
+
const found = new Map();
|
|
19
|
+
for (const site of choiceSites(db))
|
|
20
|
+
for (const [slot, choice] of Object.entries(site.choices)) {
|
|
21
|
+
if (choice === undefined || choice.provider === "" || choice.model.trim() === "")
|
|
22
|
+
continue;
|
|
23
|
+
// Images drawn by Codex use the image provider id; the text choice uses the CLI's own.
|
|
24
|
+
const list = found.get(choice.provider) ?? [];
|
|
25
|
+
list.push({
|
|
26
|
+
model: choice.model,
|
|
27
|
+
where: `${slotLabels[slot]} in ${site.kind} “${site.name}”`,
|
|
28
|
+
});
|
|
29
|
+
found.set(choice.provider, list);
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
function overall(checks, used, setUp) {
|
|
34
|
+
if (checks.some((check) => check.state === "problem"))
|
|
35
|
+
return "problem";
|
|
36
|
+
if (checks.some((check) => check.state === "warning"))
|
|
37
|
+
return "warning";
|
|
38
|
+
return setUp || used ? "ok" : "unused";
|
|
39
|
+
}
|
|
40
|
+
// "Check all" on Settings → Providers: each command-line tool found and signed in, each saved
|
|
41
|
+
// key accepted by its provider, and each chosen model still offered and answering for the key.
|
|
42
|
+
// `only` checks one provider: its row's Check again, or a sign-in fix-it's.
|
|
43
|
+
export async function checkProviderHealth(deps, only) {
|
|
44
|
+
const statuses = (await providerStatuses(deps)).filter((status) => only === undefined || status.id === only);
|
|
45
|
+
const inUse = uses(deps.db);
|
|
46
|
+
const catalogue = deps.catalogue?.();
|
|
47
|
+
const retired = catalogue === undefined ? [] : retiredModelUsage(deps.db, catalogue);
|
|
48
|
+
const providers = await Promise.all(statuses.map((status) => status.readiness.kind === "cli"
|
|
49
|
+
? cliHealth(deps, status, inUse.get(status.id) ?? [])
|
|
50
|
+
: status.readiness.kind === "local"
|
|
51
|
+
? Promise.resolve(localHealth(status, inUse.get(status.id) ?? []))
|
|
52
|
+
: keyedHealth(deps, status, inUse.get(status.id) ?? [], retired)));
|
|
53
|
+
return { checkedAt: deps.clock.now().toISOString(), providers };
|
|
54
|
+
}
|
|
55
|
+
// The system voice: a speech program was found, or not. Nothing to sign in to or pay for.
|
|
56
|
+
function localHealth(status, used) {
|
|
57
|
+
const { readiness } = status;
|
|
58
|
+
const base = { id: status.id, displayName: status.displayName, family: status.family };
|
|
59
|
+
if (readiness.kind !== "local")
|
|
60
|
+
throw new Error("Expected the system voice.");
|
|
61
|
+
const found = readiness.available;
|
|
62
|
+
const checks = [
|
|
63
|
+
{
|
|
64
|
+
label: "Speech program found",
|
|
65
|
+
state: found ? "ok" : used.length > 0 ? "problem" : "skipped",
|
|
66
|
+
detail: found
|
|
67
|
+
? `Speaks with ${readiness.engine ?? "this computer's speech program"}.`
|
|
68
|
+
: `${readiness.issue ?? "No speech program was found on this computer."}${used.length > 0 ? ` It is chosen for ${describe(used)}.` : ""}`,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
return { ...base, state: overall(checks, used.length > 0, found), checks };
|
|
72
|
+
}
|
|
73
|
+
async function cliHealth(deps, status, used) {
|
|
74
|
+
const { readiness } = status;
|
|
75
|
+
if (readiness.kind !== "cli" || !isLocalCliProvider(status.id))
|
|
76
|
+
throw new Error("Expected a command-line provider.");
|
|
77
|
+
const name = status.displayName;
|
|
78
|
+
const command = status.cliPath?.command ?? loginSource(status.id);
|
|
79
|
+
const checks = [];
|
|
80
|
+
const base = { id: status.id, displayName: name, family: status.family };
|
|
81
|
+
if (!readiness.installed ||
|
|
82
|
+
readiness.issueKind === "missing" ||
|
|
83
|
+
readiness.issueKind === "bridge") {
|
|
84
|
+
checks.push({
|
|
85
|
+
label: "Installed",
|
|
86
|
+
state: used.length > 0 ? "problem" : "skipped",
|
|
87
|
+
detail: readiness.issue ??
|
|
88
|
+
`${name} was not found on this computer. Install it, sign in once in a terminal, then choose Check all again. If it is installed somewhere unusual, set its path in Settings → Providers.`,
|
|
89
|
+
});
|
|
90
|
+
return { ...base, state: used.length > 0 ? "problem" : "unused", checks };
|
|
91
|
+
}
|
|
92
|
+
checks.push({
|
|
93
|
+
label: "Installed",
|
|
94
|
+
state: readiness.issueKind === "version" ? "problem" : "ok",
|
|
95
|
+
detail: readiness.issueKind === "version"
|
|
96
|
+
? (readiness.issue ?? `Update ${name}, then choose Check all again.`)
|
|
97
|
+
: `Found ${command}${readiness.version === undefined ? "" : ` (version ${readiness.version})`}.`,
|
|
98
|
+
});
|
|
99
|
+
const source = loginSource(status.id);
|
|
100
|
+
let login = "unknown";
|
|
101
|
+
if (status.cliPath?.managedOnHost === true && deps.hostCliStatus !== undefined) {
|
|
102
|
+
login = (await deps.hostCliStatus(status.id)).login;
|
|
103
|
+
}
|
|
104
|
+
else if (deps.login !== undefined) {
|
|
105
|
+
try {
|
|
106
|
+
login = await deps.login(cliPathStatus(deps.db, status.id).command, source, AbortSignal.timeout(15_000));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
login = "unknown";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
checks.push(login === "signed-in"
|
|
113
|
+
? { label: "Signed in", state: "ok", detail: `${name} is signed in.` }
|
|
114
|
+
: login === "signed-out"
|
|
115
|
+
? {
|
|
116
|
+
label: "Signed in",
|
|
117
|
+
state: "problem",
|
|
118
|
+
detail: source === "gemini"
|
|
119
|
+
? `Gemini CLI is not signed in: ~/.gemini has no Google sign-in and no GEMINI_API_KEY is set. Open a terminal on the computer running it, run "gemini" and choose Login with Google (or set GEMINI_API_KEY), then choose Check all again.`
|
|
120
|
+
: `${name} is not signed in. Open a terminal on the computer running it, run "${signInCommand[source]}" and sign in, then choose Check all again.`,
|
|
121
|
+
}
|
|
122
|
+
: {
|
|
123
|
+
label: "Signed in",
|
|
124
|
+
state: "skipped",
|
|
125
|
+
detail: source === "gemini"
|
|
126
|
+
? `Slopify could not tell from the Gemini CLI's files (~/.gemini/settings.json) whether it is signed in. Run "gemini" in a terminal once to confirm it is signed in.`
|
|
127
|
+
: `Slopify could not tell whether ${name} is signed in. Run "${signInCommand[source]}" in a terminal if runs fail with a sign-in error.`,
|
|
128
|
+
});
|
|
129
|
+
if (used.length > 0)
|
|
130
|
+
checks.push(await cliModels(deps, status, used));
|
|
131
|
+
return { ...base, state: overall(checks, used.length > 0, true), checks };
|
|
132
|
+
}
|
|
133
|
+
async function cliModels(deps, status, used) {
|
|
134
|
+
if (deps.modelsFor === undefined)
|
|
135
|
+
return {
|
|
136
|
+
label: "Chosen models",
|
|
137
|
+
state: "skipped",
|
|
138
|
+
detail: "The model list is not loaded yet.",
|
|
139
|
+
};
|
|
140
|
+
let offered;
|
|
141
|
+
try {
|
|
142
|
+
offered = await deps.modelsFor(status.id, status.family);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return {
|
|
146
|
+
label: "Chosen models",
|
|
147
|
+
state: "warning",
|
|
148
|
+
detail: `Slopify could not read ${status.displayName}'s model list, so it could not check the chosen models. Make sure it is signed in and up to date, then choose Check all again.`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const ids = new Set(offered.map((model) => model.id));
|
|
152
|
+
const missing = used.filter((use) => !ids.has(use.model));
|
|
153
|
+
if (missing.length === 0)
|
|
154
|
+
return {
|
|
155
|
+
label: "Chosen models",
|
|
156
|
+
state: "ok",
|
|
157
|
+
detail: `Every chosen model (${[...new Set(used.map((use) => use.model))].join(", ")}) is available.`,
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
label: "Chosen models",
|
|
161
|
+
state: "problem",
|
|
162
|
+
detail: `${status.displayName} does not offer ${describe(missing)}. Update ${status.displayName}, or pick another model there and save.`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// Asks the provider, with the saved key, for each chosen model: a model the account can't use
|
|
166
|
+
// fails here before a run does.
|
|
167
|
+
async function reachable(deps, status, used) {
|
|
168
|
+
const name = status.displayName;
|
|
169
|
+
const found = await modelReach(deps, status.id, used.map((use) => use.model));
|
|
170
|
+
if (found === undefined)
|
|
171
|
+
return {
|
|
172
|
+
label: "Model reachable",
|
|
173
|
+
state: "skipped",
|
|
174
|
+
detail: `${name} has no model check Slopify can ask without generating something. The key works; the first run shows whether the model does.`,
|
|
175
|
+
};
|
|
176
|
+
const missing = used.filter((use) => found.get(use.model) === "missing");
|
|
177
|
+
if (missing.length > 0)
|
|
178
|
+
return {
|
|
179
|
+
label: "Model reachable",
|
|
180
|
+
state: "problem",
|
|
181
|
+
detail: `${name} does not offer ${describe(missing)} to this key: the model id is wrong, retired, or the account has no access to it. Pick another model there and save, or enable the model for your account, then choose Check all again.`,
|
|
182
|
+
};
|
|
183
|
+
const unknown = [...found].filter(([, reach]) => reach === "unknown").map(([model]) => model);
|
|
184
|
+
if (unknown.length > 0)
|
|
185
|
+
return {
|
|
186
|
+
label: "Model reachable",
|
|
187
|
+
state: "warning",
|
|
188
|
+
detail: `${name} didn't answer for ${unknown.join(", ")}, so Slopify couldn't confirm the key can use ${unknown.length === 1 ? "it" : "them"}. Check your internet connection, then choose Check all again.`,
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
label: "Model reachable",
|
|
192
|
+
state: "ok",
|
|
193
|
+
detail: `${name} answered for ${[...found.keys()].join(", ")} with this key.`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function describe(missing) {
|
|
197
|
+
const shown = missing.slice(0, 3).map((use) => `${use.model} (${use.where})`);
|
|
198
|
+
return missing.length > 3
|
|
199
|
+
? `${shown.join("; ")} and ${missing.length - 3} more`
|
|
200
|
+
: shown.join("; ");
|
|
201
|
+
}
|
|
202
|
+
async function keyedHealth(deps, status, used, retired) {
|
|
203
|
+
const { readiness } = status;
|
|
204
|
+
const base = { id: status.id, displayName: status.displayName, family: status.family };
|
|
205
|
+
const guide = keyGuides[status.id];
|
|
206
|
+
const checks = [];
|
|
207
|
+
const hasKey = readiness.kind === "keyed" && readiness.hasKey;
|
|
208
|
+
if (!hasKey) {
|
|
209
|
+
checks.push({
|
|
210
|
+
label: "Key saved",
|
|
211
|
+
state: used.length > 0 ? "problem" : "skipped",
|
|
212
|
+
detail: `No key is saved.${used.length > 0 ? ` It is chosen for ${describe(used)}.` : ""} Make one at ${guide?.keyPage.url ?? "the provider's website"}, paste it in Settings → Providers → ${status.displayName} and choose Save.`,
|
|
213
|
+
});
|
|
214
|
+
return { ...base, state: used.length > 0 ? "problem" : "unused", checks };
|
|
215
|
+
}
|
|
216
|
+
const test = await testProviderKey(deps, status.id);
|
|
217
|
+
checks.push({
|
|
218
|
+
label: "Key valid",
|
|
219
|
+
state: test.ok
|
|
220
|
+
? "ok"
|
|
221
|
+
: test.result === "rate-limited" ||
|
|
222
|
+
test.result === "provider-down" ||
|
|
223
|
+
test.result === "unreachable"
|
|
224
|
+
? "warning"
|
|
225
|
+
: "problem",
|
|
226
|
+
detail: test.message,
|
|
227
|
+
});
|
|
228
|
+
const mine = retired.filter((usage) => usage.provider === status.id);
|
|
229
|
+
if (mine.length > 0)
|
|
230
|
+
checks.push({
|
|
231
|
+
label: "Chosen models",
|
|
232
|
+
state: "problem",
|
|
233
|
+
detail: `${describe(mine.map((usage) => ({
|
|
234
|
+
model: usage.model,
|
|
235
|
+
where: `${slotLabels[usage.slot]} in ${usage.kind} “${usage.name}”`,
|
|
236
|
+
})))} ${mine.length === 1 ? "is" : "are"} no longer offered. Use Switch in Settings → Models → Retired models in use.`,
|
|
237
|
+
});
|
|
238
|
+
else if (used.length > 0)
|
|
239
|
+
checks.push({
|
|
240
|
+
label: "Chosen models",
|
|
241
|
+
state: "ok",
|
|
242
|
+
detail: "Every chosen model is still offered.",
|
|
243
|
+
});
|
|
244
|
+
if (used.length > 0 && test.ok)
|
|
245
|
+
checks.push(await reachable(deps, status, used));
|
|
246
|
+
return { ...base, state: overall(checks, used.length > 0, true), checks };
|
|
247
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const openAi = {
|
|
2
|
+
signUp: { label: "platform.openai.com/signup", url: "https://platform.openai.com/signup" },
|
|
3
|
+
keyPage: { label: "platform.openai.com/api-keys", url: "https://platform.openai.com/api-keys" },
|
|
4
|
+
billing: {
|
|
5
|
+
label: "Billing",
|
|
6
|
+
url: "https://platform.openai.com/settings/organization/billing/overview",
|
|
7
|
+
},
|
|
8
|
+
steps: [
|
|
9
|
+
"Sign up or sign in to the OpenAI platform.",
|
|
10
|
+
"Open Billing and buy prepaid credit (new accounts need at least $5 before any call works).",
|
|
11
|
+
"Open API keys, choose Create new secret key, and copy it (it is shown once).",
|
|
12
|
+
"Paste it here, choose Save, then Test.",
|
|
13
|
+
],
|
|
14
|
+
permissions: "A key with All permissions works. A restricted key needs Models: Read (for Test) and Model capabilities: Request (for speech and images). GPT Image models may also need Verify Organization under Settings → Organization → General on the OpenAI platform.",
|
|
15
|
+
docs: {
|
|
16
|
+
label: "OpenAI API docs",
|
|
17
|
+
url: "https://developers.openai.com/api/docs/guides/error-codes",
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
const google = {
|
|
21
|
+
signUp: { label: "aistudio.google.com", url: "https://aistudio.google.com" },
|
|
22
|
+
keyPage: { label: "aistudio.google.com/apikey", url: "https://aistudio.google.com/apikey" },
|
|
23
|
+
billing: { label: "Projects → Set up billing", url: "https://aistudio.google.com/projects" },
|
|
24
|
+
steps: [
|
|
25
|
+
"Sign in to Google AI Studio with your Google account.",
|
|
26
|
+
"Open Projects and choose Set up billing next to your project: Gemini image models are not available on the free tier.",
|
|
27
|
+
"Open API keys, choose Create API key, and copy it.",
|
|
28
|
+
"Paste it here, choose Save, then Test.",
|
|
29
|
+
],
|
|
30
|
+
permissions: "Use a key made in AI Studio. An older Google Cloud key must be set to Restrict to Gemini API only, or Google rejects it.",
|
|
31
|
+
docs: { label: "Gemini API keys", url: "https://ai.google.dev/gemini-api/docs/api-key" },
|
|
32
|
+
};
|
|
33
|
+
export const keyGuides = {
|
|
34
|
+
openrouter: {
|
|
35
|
+
signUp: { label: "openrouter.ai/sign-up", url: "https://openrouter.ai/sign-up" },
|
|
36
|
+
keyPage: { label: "openrouter.ai/settings/keys", url: "https://openrouter.ai/settings/keys" },
|
|
37
|
+
billing: { label: "Credits", url: "https://openrouter.ai/settings/credits" },
|
|
38
|
+
steps: [
|
|
39
|
+
"Sign up at OpenRouter.",
|
|
40
|
+
"Open Credits and buy credit (paid models are charged from it; a negative balance stops even free models).",
|
|
41
|
+
"Open Keys, choose Create API key, and copy it.",
|
|
42
|
+
"Paste it here, choose Save, then Test.",
|
|
43
|
+
],
|
|
44
|
+
permissions: "Keys have no scopes. If you set a credit limit on the key, keep it above what a run costs.",
|
|
45
|
+
docs: { label: "OpenRouter docs", url: "https://openrouter.ai/docs/api-reference/limits" },
|
|
46
|
+
},
|
|
47
|
+
"openai-tts": openAi,
|
|
48
|
+
"openai-image": openAi,
|
|
49
|
+
"google-image": google,
|
|
50
|
+
"google-tts": {
|
|
51
|
+
...google,
|
|
52
|
+
steps: [
|
|
53
|
+
"Sign in to Google AI Studio with your Google account.",
|
|
54
|
+
"Open API keys, choose Create API key, and copy it.",
|
|
55
|
+
"Paste it here, choose Save, then Test.",
|
|
56
|
+
],
|
|
57
|
+
permissions: `The same Gemini API key as Google images: with one saved there, Gemini voices use it and nothing is needed here. ${google.permissions}`,
|
|
58
|
+
},
|
|
59
|
+
fal: {
|
|
60
|
+
signUp: { label: "fal.ai/login", url: "https://fal.ai/login" },
|
|
61
|
+
keyPage: { label: "fal.ai/dashboard/keys", url: "https://fal.ai/dashboard/keys" },
|
|
62
|
+
billing: { label: "Billing", url: "https://fal.ai/dashboard/billing" },
|
|
63
|
+
steps: [
|
|
64
|
+
"Sign up at fal.ai.",
|
|
65
|
+
"Open Billing and buy credit: fal.ai is prepaid and locks the account when the balance runs out.",
|
|
66
|
+
"Open Keys, choose Add key with the API scope, and copy it.",
|
|
67
|
+
"Paste it here, choose Save, then Test.",
|
|
68
|
+
],
|
|
69
|
+
permissions: "The API scope is enough; Slopify does not need an ADMIN key.",
|
|
70
|
+
docs: {
|
|
71
|
+
label: "fal.ai authentication",
|
|
72
|
+
url: "https://fal.ai/docs/model-apis/authentication",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
replicate: {
|
|
76
|
+
signUp: { label: "replicate.com/signin", url: "https://replicate.com/signin" },
|
|
77
|
+
keyPage: {
|
|
78
|
+
label: "replicate.com/account/api-tokens",
|
|
79
|
+
url: "https://replicate.com/account/api-tokens",
|
|
80
|
+
},
|
|
81
|
+
billing: { label: "Billing", url: "https://replicate.com/account/billing" },
|
|
82
|
+
steps: [
|
|
83
|
+
"Sign in to Replicate.",
|
|
84
|
+
"Open Billing and buy credit: Replicate needs credit up front before it runs paid models.",
|
|
85
|
+
"Open API tokens, create a token, and copy it (it starts with r8_).",
|
|
86
|
+
"Paste it here, choose Save, then Test.",
|
|
87
|
+
],
|
|
88
|
+
permissions: "Tokens have no scopes.",
|
|
89
|
+
docs: { label: "Replicate billing", url: "https://replicate.com/docs/topics/billing" },
|
|
90
|
+
},
|
|
91
|
+
elevenlabs: {
|
|
92
|
+
signUp: { label: "elevenlabs.io/app/sign-up", url: "https://elevenlabs.io/app/sign-up" },
|
|
93
|
+
keyPage: {
|
|
94
|
+
label: "elevenlabs.io/app/developers/api-keys",
|
|
95
|
+
url: "https://elevenlabs.io/app/developers/api-keys",
|
|
96
|
+
},
|
|
97
|
+
steps: [
|
|
98
|
+
"Sign up at ElevenLabs.",
|
|
99
|
+
"Open Developers → API keys, choose Create key, and copy it.",
|
|
100
|
+
"Paste it here, choose Save, then Test.",
|
|
101
|
+
],
|
|
102
|
+
permissions: "A restricted key needs Text to Speech access and Models read access (models_read) for Test; Voices read helps when picking voices. Remove any credit quota or IP allowlist that would block this computer.",
|
|
103
|
+
docs: {
|
|
104
|
+
label: "ElevenLabs API keys",
|
|
105
|
+
url: "https://elevenlabs.io/docs/api-reference/models/list",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
cartesia: {
|
|
109
|
+
signUp: { label: "play.cartesia.ai/sign-up", url: "https://play.cartesia.ai/sign-up" },
|
|
110
|
+
keyPage: { label: "play.cartesia.ai/keys", url: "https://play.cartesia.ai/keys" },
|
|
111
|
+
steps: [
|
|
112
|
+
"Sign up at Cartesia (the free plan needs no card).",
|
|
113
|
+
"Open API Keys, create a key, and copy it (it starts with sk_car_).",
|
|
114
|
+
"Paste it here, choose Save, then Test.",
|
|
115
|
+
],
|
|
116
|
+
permissions: "Keys have no scopes.",
|
|
117
|
+
docs: {
|
|
118
|
+
label: "Cartesia authentication",
|
|
119
|
+
url: "https://docs.cartesia.ai/get-started/authenticate-your-client-applications",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
inworld: {
|
|
123
|
+
signUp: { label: "platform.inworld.ai/signup", url: "https://platform.inworld.ai/signup" },
|
|
124
|
+
keyPage: {
|
|
125
|
+
label: "platform.inworld.ai/api-keys",
|
|
126
|
+
url: "https://platform.inworld.ai/api-keys",
|
|
127
|
+
},
|
|
128
|
+
billing: { label: "Billing", url: "https://platform.inworld.ai/billing" },
|
|
129
|
+
steps: [
|
|
130
|
+
"Sign up at the Inworld platform.",
|
|
131
|
+
"Open API Keys and choose Generate new key (a Standard key, not Realtime-only).",
|
|
132
|
+
"Copy the Base64 credentials value exactly as shown, without encoding it again.",
|
|
133
|
+
"Paste it here, choose Save, then Test.",
|
|
134
|
+
],
|
|
135
|
+
permissions: "A Standard key covers speech and reading voices. Realtime-only keys do not work.",
|
|
136
|
+
docs: { label: "Inworld API keys", url: "https://docs.inworld.ai/portal/api-keys" },
|
|
137
|
+
},
|
|
138
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { keyGuides } from "./key-guides.js";
|
|
2
|
+
import { keyWithShared } from "./keys.js";
|
|
3
|
+
import { providerById } from "./model.js";
|
|
4
|
+
// What the Test button found. `ok` is the only thing the screen needs to colour the line;
|
|
5
|
+
// `message` says what happened and, when it failed, exactly what to do.
|
|
6
|
+
export const keyTestResults = [
|
|
7
|
+
"valid",
|
|
8
|
+
"no-key",
|
|
9
|
+
"rejected",
|
|
10
|
+
"forbidden",
|
|
11
|
+
"no-credit",
|
|
12
|
+
"rate-limited",
|
|
13
|
+
"provider-down",
|
|
14
|
+
"unreachable",
|
|
15
|
+
"unexpected",
|
|
16
|
+
];
|
|
17
|
+
// The mapping from what came back to what the user reads. Pure, so every branch is tested
|
|
18
|
+
// without a network.
|
|
19
|
+
export function keyTestOutcome(provider, answer, checkedAt, badKey) {
|
|
20
|
+
const name = providerById(provider).displayName;
|
|
21
|
+
const guide = keyGuides[provider];
|
|
22
|
+
const keyPage = guide === undefined ? "the provider's website" : guide.keyPage.url;
|
|
23
|
+
const billing = guide?.billing?.url;
|
|
24
|
+
const place = `Settings → Providers → ${name}`;
|
|
25
|
+
const done = (result, message) => ({
|
|
26
|
+
provider,
|
|
27
|
+
result,
|
|
28
|
+
ok: result === "valid",
|
|
29
|
+
message,
|
|
30
|
+
checkedAt,
|
|
31
|
+
});
|
|
32
|
+
if ("failure" in answer) {
|
|
33
|
+
if (answer.failure === "no-key")
|
|
34
|
+
return done("no-key", `No ${name} key is saved yet. Make one at ${keyPage}, paste it in ${place} and choose Save, then Test.`);
|
|
35
|
+
return done("unreachable", answer.failure === "timeout"
|
|
36
|
+
? `${name} did not answer within 15 seconds, so the key could not be checked. Check your internet connection, then choose Test again.`
|
|
37
|
+
: `Slopify could not reach ${name}, so the key could not be checked. Check your internet connection (and any firewall or proxy), then choose Test again.`);
|
|
38
|
+
}
|
|
39
|
+
const { status } = answer;
|
|
40
|
+
if (status >= 200 && status < 300)
|
|
41
|
+
return done("valid", provider === "openrouter"
|
|
42
|
+
? `${name} accepted this key.`
|
|
43
|
+
: `${name} accepted this key. The account's credit is only checked when Slopify first generates with it.`);
|
|
44
|
+
if (status === 401 || badKey?.(status, answer.body ?? "") === true)
|
|
45
|
+
return done("rejected", `${name} did not accept this key (HTTP ${status}): it is mistyped, revoked or for another account. Make a new key at ${keyPage}, paste it in ${place}, choose Save, then Test again.`);
|
|
46
|
+
if (status === 402)
|
|
47
|
+
return done("no-credit", `${name} accepted the key but the account has no credit or payment method (HTTP 402). Add credit${billing === undefined ? " in your account" : ` at ${billing}`}, then choose Test again.`);
|
|
48
|
+
if (status === 403)
|
|
49
|
+
return done("forbidden", `${name} recognised the key but refused this request (HTTP 403). The key may be restricted, or the account may need verifying or billing set up. ${guide?.permissions ?? ""} Fix it at ${keyPage}, then choose Test again.`.replace(/\s+/g, " "));
|
|
50
|
+
if (status === 429)
|
|
51
|
+
return done("rate-limited", `${name} said "too many requests" (HTTP 429), so the key could not be checked just now. It is usually valid; wait a minute and choose Test again. If it keeps happening, check the account's limits and credit${billing === undefined ? "" : ` at ${billing}`}.`);
|
|
52
|
+
if (status >= 500)
|
|
53
|
+
return done("provider-down", `${name} had a problem on its side (HTTP ${status}), so the key could not be checked. Wait a few minutes and choose Test again.`);
|
|
54
|
+
return done("unexpected", `${name} answered the key check with HTTP ${status}, which Slopify did not expect. Check the key at ${keyPage}, then choose Test again; if it keeps happening, use Download diagnostics in Settings and report it.`);
|
|
55
|
+
}
|
|
56
|
+
// The Test button. The key is read for this one request, sent only to its own provider, and
|
|
57
|
+
// never appears in the answer, a log line or an error. `pasted` is a key typed into the field
|
|
58
|
+
// and not saved yet: it is tried instead of the saved one and is not stored.
|
|
59
|
+
export async function testProviderKey(deps, provider, pasted) {
|
|
60
|
+
const at = () => deps.clock.now().toISOString();
|
|
61
|
+
const probe = Object.hasOwn(deps.probes, provider) ? deps.probes[provider] : undefined;
|
|
62
|
+
if (probe === undefined || providerById(provider).auth !== "key")
|
|
63
|
+
throw new Error(`${provider} has no key to test`);
|
|
64
|
+
const candidate = pasted?.trim();
|
|
65
|
+
const key = candidate === undefined || candidate === "" ? keyWithShared(deps.db, provider) : candidate;
|
|
66
|
+
if (key === undefined)
|
|
67
|
+
return keyTestOutcome(provider, { failure: "no-key" }, at());
|
|
68
|
+
const outcome = await probeKey(deps, provider, probe, key, at);
|
|
69
|
+
return candidate === undefined || candidate === "" || !outcome.ok
|
|
70
|
+
? outcome
|
|
71
|
+
: { ...outcome, message: `${outcome.message} It is not saved yet: choose Save to keep it.` };
|
|
72
|
+
}
|
|
73
|
+
async function probeKey(deps, provider, probe, key, at) {
|
|
74
|
+
let status;
|
|
75
|
+
let body = "";
|
|
76
|
+
try {
|
|
77
|
+
const response = await deps.fetch(probe.url, {
|
|
78
|
+
headers: probe.headers(key),
|
|
79
|
+
signal: AbortSignal.timeout(15_000),
|
|
80
|
+
redirect: "error",
|
|
81
|
+
});
|
|
82
|
+
status = response.status;
|
|
83
|
+
// Only an error body is read, and only its start: enough for the provider's error code.
|
|
84
|
+
body = status >= 400 ? (await response.text()).slice(0, 4096) : "";
|
|
85
|
+
if (status < 400)
|
|
86
|
+
await response.body?.cancel().catch(() => { });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const timeout = error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
90
|
+
return keyTestOutcome(provider, { failure: timeout ? "timeout" : "network" }, at());
|
|
91
|
+
}
|
|
92
|
+
return keyTestOutcome(provider, { status, body }, at(), probe.badKey);
|
|
93
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { providerById } from "./model.js";
|
|
1
|
+
import { providerById, sharedKeyOf } from "./model.js";
|
|
2
2
|
import { deleteKey, hasKey, keyOf, upsertKey } from "./repo.js";
|
|
3
3
|
// The Settings field shows a stored key masked, with nothing legible in it, and a key never
|
|
4
4
|
// appears outside a provider call. So the mask is a constant: it carries no character of
|
|
@@ -12,7 +12,7 @@ export function keyStatus(deps, provider) {
|
|
|
12
12
|
// Trimmed, stored as given, overwriting any previous one. No format
|
|
13
13
|
// check and no test call.
|
|
14
14
|
export function saveProviderKey(deps, provider, key) {
|
|
15
|
-
if (providerById(provider).auth
|
|
15
|
+
if (providerById(provider).auth !== "key") {
|
|
16
16
|
return { ok: false, reason: "cli-provider" };
|
|
17
17
|
}
|
|
18
18
|
const trimmed = key.trim();
|
|
@@ -31,7 +31,7 @@ export function saveProviderKey(deps, provider, key) {
|
|
|
31
31
|
return { ok: true };
|
|
32
32
|
}
|
|
33
33
|
export function removeProviderKey(deps, provider) {
|
|
34
|
-
if (providerById(provider).auth
|
|
34
|
+
if (providerById(provider).auth !== "key") {
|
|
35
35
|
return { ok: false, reason: "cli-provider" };
|
|
36
36
|
}
|
|
37
37
|
return deleteKey(deps.db, provider) ? { ok: true } : { ok: false, reason: "absent" };
|
|
@@ -40,12 +40,17 @@ export function removeProviderKey(deps, provider) {
|
|
|
40
40
|
// every time and hands back a plain string, so an attempt holds the value it started
|
|
41
41
|
// with and a save or a remove landing mid-run reaches the next attempt only.
|
|
42
42
|
export function keyForAttempt(deps, provider) {
|
|
43
|
-
if (providerById(provider).auth
|
|
43
|
+
if (providerById(provider).auth !== "key") {
|
|
44
44
|
return { ok: false, reason: "cli-provider" };
|
|
45
45
|
}
|
|
46
|
-
const key =
|
|
46
|
+
const key = keyWithShared(deps.db, provider);
|
|
47
47
|
return key === undefined ? { ok: false, reason: "key-missing" } : { ok: true, key };
|
|
48
48
|
}
|
|
49
|
+
// The provider's own key, else the key of the provider it shares one with (`sharedKeyOf`).
|
|
50
|
+
export function keyWithShared(db, provider) {
|
|
51
|
+
const shared = sharedKeyOf[provider];
|
|
52
|
+
return keyOf(db, provider) ?? (shared === undefined ? undefined : keyOf(db, shared));
|
|
53
|
+
}
|
|
49
54
|
function codeOf(error) {
|
|
50
55
|
if (error instanceof Error && "errcode" in error && typeof error.errcode === "number") {
|
|
51
56
|
return `sqlite error ${String(error.errcode)}`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { keyOf } from "./repo.js";
|
|
2
|
+
// ceiling: a provider chosen in many templates still costs a handful of reads, not one per
|
|
3
|
+
// model anywhere in the library.
|
|
4
|
+
const modelsChecked = 5;
|
|
5
|
+
export async function modelReach(deps, provider, models) {
|
|
6
|
+
const probe = Object.hasOwn(deps.probes, provider) ? deps.probes[provider] : undefined;
|
|
7
|
+
const key = keyOf(deps.db, provider);
|
|
8
|
+
if (probe?.model === undefined || key === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
const reads = new Map();
|
|
11
|
+
const read = (url) => {
|
|
12
|
+
const known = reads.get(url);
|
|
13
|
+
if (known !== undefined)
|
|
14
|
+
return known;
|
|
15
|
+
const started = (async () => {
|
|
16
|
+
try {
|
|
17
|
+
const response = await deps.fetch(url, {
|
|
18
|
+
headers: probe.headers(key),
|
|
19
|
+
signal: AbortSignal.timeout(15_000),
|
|
20
|
+
redirect: "error",
|
|
21
|
+
});
|
|
22
|
+
// A list is read whole; a single model's page only for its status.
|
|
23
|
+
const body = probe.model?.lists === undefined ? "" : await response.text();
|
|
24
|
+
if (probe.model?.lists === undefined)
|
|
25
|
+
await response.body?.cancel().catch(() => { });
|
|
26
|
+
return { status: response.status, body };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
reads.set(url, started);
|
|
33
|
+
return started;
|
|
34
|
+
};
|
|
35
|
+
const found = new Map();
|
|
36
|
+
for (const model of [...new Set(models)].slice(0, modelsChecked)) {
|
|
37
|
+
const answer = await read(probe.model.url(model));
|
|
38
|
+
found.set(model, verdict(answer, model, probe.model.lists));
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
function verdict(answer, model, lists) {
|
|
43
|
+
if (answer === undefined)
|
|
44
|
+
return "unknown";
|
|
45
|
+
if (answer.status >= 200 && answer.status < 300) {
|
|
46
|
+
if (lists === undefined)
|
|
47
|
+
return "found";
|
|
48
|
+
const listed = lists(answer.body, model);
|
|
49
|
+
return listed === undefined ? "unknown" : listed ? "found" : "missing";
|
|
50
|
+
}
|
|
51
|
+
return answer.status === 404 && lists === undefined ? "missing" : "unknown";
|
|
52
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// The provider catalogue and the settings domain types.
|
|
2
|
+
import { defaultLoudness } from "../loudness/model.js";
|
|
2
3
|
// The three families are the three ports, so the set is named beside them.
|
|
3
4
|
export { providerFamilies } from "../../kernel/ports/model.js";
|
|
4
5
|
// The supported set. OpenAI ships two adapters with two ids, one per family:
|
|
@@ -13,6 +14,8 @@ export const providerIds = [
|
|
|
13
14
|
"openai-tts",
|
|
14
15
|
"cartesia",
|
|
15
16
|
"inworld",
|
|
17
|
+
"system-voice",
|
|
18
|
+
"google-tts",
|
|
16
19
|
"fal",
|
|
17
20
|
"replicate",
|
|
18
21
|
"openai-image",
|
|
@@ -27,6 +30,12 @@ export function isLocalCliProvider(id) {
|
|
|
27
30
|
export function localCliConcurrency(id) {
|
|
28
31
|
return id === "codex-image" ? 4 : 3;
|
|
29
32
|
}
|
|
33
|
+
export const systemVoiceProvider = "system-voice";
|
|
34
|
+
// A provider Slopify's model list (models.yaml) does not carry: an agent CLI lists its own
|
|
35
|
+
// models, and the system voice's "models" are the speech programs found on this computer.
|
|
36
|
+
export function isUncataloguedProvider(id) {
|
|
37
|
+
return isLocalCliProvider(id) || id === systemVoiceProvider;
|
|
38
|
+
}
|
|
30
39
|
export const providers = [
|
|
31
40
|
{ id: "openrouter", family: "llm", displayName: "OpenRouter", auth: "key" },
|
|
32
41
|
{
|
|
@@ -57,6 +66,8 @@ export const providers = [
|
|
|
57
66
|
{ id: "openai-tts", family: "tts", displayName: "OpenAI", auth: "key" },
|
|
58
67
|
{ id: "cartesia", family: "tts", displayName: "Cartesia", auth: "key" },
|
|
59
68
|
{ id: "inworld", family: "tts", displayName: "Inworld", auth: "key" },
|
|
69
|
+
{ id: "system-voice", family: "tts", displayName: "System voice", auth: "local" },
|
|
70
|
+
{ id: "google-tts", family: "tts", displayName: "Google Gemini", auth: "key" },
|
|
60
71
|
{ id: "fal", family: "image", displayName: "fal.ai", auth: "key" },
|
|
61
72
|
{ id: "replicate", family: "image", displayName: "Replicate", auth: "key" },
|
|
62
73
|
{ id: "openai-image", family: "image", displayName: "OpenAI", auth: "key" },
|
|
@@ -70,6 +81,45 @@ export const providers = [
|
|
|
70
81
|
versionArgs: ["--version"],
|
|
71
82
|
},
|
|
72
83
|
];
|
|
84
|
+
// One Gemini API key pays for Google's speech and its images, so Gemini TTS uses the key saved
|
|
85
|
+
// for Google images until one of its own is saved.
|
|
86
|
+
export const sharedKeyOf = {
|
|
87
|
+
"google-tts": "google-image",
|
|
88
|
+
};
|
|
89
|
+
// The 30 prebuilt voices Gemini TTS speaks with (https://ai.google.dev/gemini-api/docs/speech-generation#voices),
|
|
90
|
+
// each with the style Google gives it. Settings → Voices offers them for Google Gemini.
|
|
91
|
+
export const geminiVoices = [
|
|
92
|
+
{ name: "Zephyr", style: "Bright" },
|
|
93
|
+
{ name: "Puck", style: "Upbeat" },
|
|
94
|
+
{ name: "Charon", style: "Informative" },
|
|
95
|
+
{ name: "Kore", style: "Firm" },
|
|
96
|
+
{ name: "Fenrir", style: "Excitable" },
|
|
97
|
+
{ name: "Leda", style: "Youthful" },
|
|
98
|
+
{ name: "Orus", style: "Firm" },
|
|
99
|
+
{ name: "Aoede", style: "Breezy" },
|
|
100
|
+
{ name: "Callirrhoe", style: "Easy-going" },
|
|
101
|
+
{ name: "Autonoe", style: "Bright" },
|
|
102
|
+
{ name: "Enceladus", style: "Breathy" },
|
|
103
|
+
{ name: "Iapetus", style: "Clear" },
|
|
104
|
+
{ name: "Umbriel", style: "Easy-going" },
|
|
105
|
+
{ name: "Algieba", style: "Smooth" },
|
|
106
|
+
{ name: "Despina", style: "Smooth" },
|
|
107
|
+
{ name: "Erinome", style: "Clear" },
|
|
108
|
+
{ name: "Algenib", style: "Gravelly" },
|
|
109
|
+
{ name: "Rasalgethi", style: "Informative" },
|
|
110
|
+
{ name: "Laomedeia", style: "Upbeat" },
|
|
111
|
+
{ name: "Achernar", style: "Soft" },
|
|
112
|
+
{ name: "Alnilam", style: "Firm" },
|
|
113
|
+
{ name: "Schedar", style: "Even" },
|
|
114
|
+
{ name: "Gacrux", style: "Mature" },
|
|
115
|
+
{ name: "Pulcherrima", style: "Forward" },
|
|
116
|
+
{ name: "Achird", style: "Friendly" },
|
|
117
|
+
{ name: "Zubenelgenubi", style: "Casual" },
|
|
118
|
+
{ name: "Vindemiatrix", style: "Gentle" },
|
|
119
|
+
{ name: "Sadachbia", style: "Lively" },
|
|
120
|
+
{ name: "Sadaltager", style: "Knowledgeable" },
|
|
121
|
+
{ name: "Sulafat", style: "Warm" },
|
|
122
|
+
];
|
|
73
123
|
export function providerById(id) {
|
|
74
124
|
const found = providers.find((provider) => provider.id === id);
|
|
75
125
|
if (found === undefined) {
|
|
@@ -81,4 +131,8 @@ export function providerById(id) {
|
|
|
81
131
|
}
|
|
82
132
|
// The theme override the Appearance control writes.
|
|
83
133
|
export const appearances = ["system", "light", "dark"];
|
|
84
|
-
export const defaultSettings = {
|
|
134
|
+
export const defaultSettings = {
|
|
135
|
+
silenceGapSeconds: 3,
|
|
136
|
+
appearance: "system",
|
|
137
|
+
loudness: defaultLoudness,
|
|
138
|
+
};
|