@gentbajko/slopify 4.0.0 → 4.2.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/dist/adapters/account-limits.js +1 -1
- package/dist/adapters/alignment/audio.js +1 -1
- package/dist/adapters/alignment/cache.js +1 -1
- package/dist/adapters/alignment/multilingual.js +2 -1
- package/dist/adapters/alignment/runner.js +1 -1
- package/dist/adapters/alignment/worker.js +1 -1
- package/dist/adapters/explain.js +10 -9
- package/dist/adapters/host-cli/index.js +2 -1
- package/dist/adapters/host-cli/transport.js +2 -1
- package/dist/adapters/image/bytes.js +2 -1
- package/dist/adapters/image/codex.js +6 -5
- package/dist/adapters/image/fal.js +4 -3
- package/dist/adapters/image/google.js +3 -2
- package/dist/adapters/image/models.js +2 -2
- package/dist/adapters/image/openai.js +2 -1
- package/dist/adapters/image/replicate.js +3 -2
- package/dist/adapters/image/video.js +2 -1
- package/dist/adapters/key-probes.js +1 -1
- package/dist/adapters/llm/antigravity-models.js +1 -1
- package/dist/adapters/llm/antigravity.js +7 -6
- package/dist/adapters/llm/assistant-cli.js +1 -1
- package/dist/adapters/llm/claude-code-models.js +1 -1
- package/dist/adapters/llm/claude-code.js +3 -2
- package/dist/adapters/llm/codex-models.js +1 -1
- package/dist/adapters/llm/document-workspace.js +3 -2
- package/dist/adapters/llm/image-workspace.js +3 -2
- package/dist/adapters/llm/openrouter.js +2 -1
- package/dist/adapters/llm/run-cli.js +3 -2
- package/dist/adapters/tts/gemini.js +2 -1
- package/dist/adapters/tts/inworld-async.js +2 -1
- package/dist/adapters/tts/inworld.js +3 -2
- package/dist/adapters/tts/system.js +2 -1
- package/dist/catalog/registry.js +20 -13
- package/dist/catalog/schema.js +2 -1
- package/dist/catalog/validate.js +5 -2
- package/dist/edge/http/actions.js +7 -7
- package/dist/edge/http/app.js +5 -3
- package/dist/edge/http/assistant.js +3 -1
- package/dist/edge/http/backups.js +1 -1
- package/dist/edge/http/channel-memory.js +2 -2
- package/dist/edge/http/channels.js +1 -1
- package/dist/edge/http/checkpoints.js +7 -5
- package/dist/edge/http/design-templates.js +3 -3
- package/dist/edge/http/designs.js +1 -1
- package/dist/edge/http/document-themes.js +3 -3
- package/dist/edge/http/draft-problem.js +1 -1
- package/dist/edge/http/extension-auth.js +1 -1
- package/dist/edge/http/home.js +9 -1
- package/dist/edge/http/model-checks.js +4 -4
- package/dist/edge/http/onboarding.js +29 -12
- package/dist/edge/http/outputs.js +3 -2
- package/dist/edge/http/patch-notes.js +2 -2
- package/dist/edge/http/planning.js +1 -1
- package/dist/edge/http/project-create.js +1 -1
- package/dist/edge/http/project-templates.js +2 -2
- package/dist/edge/http/projects.js +22 -4
- package/dist/edge/http/prompts.js +2 -2
- package/dist/edge/http/pronunciation-check.js +133 -0
- package/dist/edge/http/pronunciations.js +26 -0
- package/dist/edge/http/providers.js +9 -10
- package/dist/edge/http/refusal.js +1 -1
- package/dist/edge/http/remote-access.js +4 -4
- package/dist/edge/http/remote-guard.js +2 -2
- package/dist/edge/http/reviews.js +1 -1
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +4 -4
- package/dist/edge/http/schedules.js +13 -11
- package/dist/edge/http/settings.js +6 -2
- package/dist/edge/http/social.js +5 -4
- package/dist/edge/http/staging.js +1 -1
- package/dist/edge/http/storage.js +12 -4
- package/dist/edge/http/studio.js +71 -19
- package/dist/edge/http/style-preview.js +2 -2
- package/dist/edge/http/template-studio.js +12 -9
- package/dist/edge/http/trash.js +1 -1
- package/dist/edge/http/youtube-edits.js +1 -1
- package/dist/edge/mcp/excluded-routes.js +4 -3
- package/dist/edge/mcp/instructions.js +3 -3
- package/dist/edge/mcp/library-tools.js +8 -5
- package/dist/edge/mcp/remake-tools.js +7 -4
- package/dist/edge/mcp/server.js +1 -1
- package/dist/edge/mcp/tools-channels.js +9 -9
- package/dist/edge/mcp/tools-designs.js +9 -9
- package/dist/edge/mcp/tools-library.js +31 -7
- package/dist/edge/mcp/tools-planning.js +18 -18
- package/dist/edge/mcp/tools-projects.js +10 -10
- package/dist/edge/mcp/tools-publishing.js +9 -9
- package/dist/edge/mcp/tools-runs.js +9 -9
- package/dist/edge/mcp/tools-schedules.js +6 -6
- package/dist/edge/mcp/tools-setup.js +20 -14
- package/dist/edge/mcp/tools-social.js +5 -4
- package/dist/edge/mcp/tools-storage.js +3 -3
- package/dist/edge/mcp/tools-template-studio.js +18 -18
- package/dist/edge/remote-access/model.js +1 -1
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
- package/dist/kernel/pipeline.js +12 -0
- package/dist/kernel/ports/settings-places.js +31 -0
- package/dist/kernel/ports/system-speech.js +31 -3
- package/dist/kernel/runner/attempt.js +11 -3
- package/dist/kernel/runner/providers.js +3 -2
- package/dist/kernel/runner/standalone.js +2 -2
- package/dist/main.js +5 -3
- package/dist/model-catalog.js +20 -0
- package/dist/patch-notes/4.0.0.md +13 -13
- package/dist/patch-notes/4.1.0.md +42 -0
- package/dist/patch-notes/4.2.0.md +28 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/admission/listing-facts.js +95 -0
- package/dist/slices/admission/repo.js +9 -6
- package/dist/slices/admission/rules.js +7 -7
- package/dist/slices/admission/schema.js +2 -0
- package/dist/slices/admission/start.js +7 -0
- package/dist/slices/admission/state-words.js +34 -5
- package/dist/slices/assistant/cost.js +1 -1
- package/dist/slices/assistant/gate.js +1 -0
- package/dist/slices/assistant/model.js +10 -3
- package/dist/slices/assistant/turn.js +5 -3
- package/dist/slices/autopilot/warnings.js +1 -1
- package/dist/slices/backups/service.js +5 -5
- package/dist/slices/channels/cast-images.js +1 -1
- package/dist/slices/channels/schema.js +1 -1
- package/dist/slices/channels/videos.js +1 -1
- package/dist/slices/control/providers.js +2 -2
- package/dist/slices/designs/budget.js +1 -1
- package/dist/slices/designs/calls.js +2 -2
- package/dist/slices/designs/check-context.js +4 -4
- package/dist/slices/designs/fields.js +4 -4
- package/dist/slices/designs/gate.js +7 -5
- package/dist/slices/designs/render-input.js +1 -1
- package/dist/slices/designs/requests.js +1 -1
- package/dist/slices/designs/runner.js +1 -1
- package/dist/slices/designs/sample.js +2 -1
- package/dist/slices/designs/service.js +1 -1
- package/dist/slices/designs/template-checks.js +2 -2
- package/dist/slices/designs/template-make.js +3 -3
- package/dist/slices/designs/templates.js +3 -3
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/model.js +7 -4
- package/dist/slices/document/render.js +2 -1
- package/dist/slices/document/theme-schema.js +1 -1
- package/dist/slices/document/theme.js +49 -3
- package/dist/slices/episodes/summarize.js +2 -2
- package/dist/slices/estimate/index.js +28 -22
- package/dist/slices/estimate/requests.js +4 -4
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/fonts/catalog.js +2 -1
- package/dist/slices/fonts/coverage.js +2 -1
- package/dist/slices/images/appearance.js +2 -1
- package/dist/slices/images/scenes.js +2 -1
- package/dist/slices/keywords/digest.js +2 -1
- package/dist/slices/keywords/rules.js +2 -2
- package/dist/slices/keywords/specs.js +5 -5
- package/dist/slices/keywords/values.js +1 -1
- package/dist/slices/library/transfer.js +3 -3
- package/dist/slices/loudness/loudnorm.js +2 -1
- package/dist/slices/loudness/model.js +3 -2
- package/dist/slices/loudness/sentence-level.js +2 -1
- package/dist/slices/model-checks/rules.js +13 -0
- package/dist/slices/model-upkeep/model.js +1 -1
- package/dist/slices/model-upkeep/switch.js +3 -3
- package/dist/slices/model-upkeep/usage.js +13 -2
- package/dist/slices/narration/breaths.js +2 -1
- package/dist/slices/narration/clean-pieces.js +2 -1
- package/dist/slices/narration/pauses-model.js +3 -2
- package/dist/slices/narration/pronunciation-check.js +226 -0
- package/dist/slices/narration/pronunciation.js +7 -2
- package/dist/slices/narration/pronunciations-library.js +94 -0
- package/dist/slices/notifications/subject.js +2 -2
- package/dist/slices/onboarding/copy.js +19 -2
- package/dist/slices/onboarding/first-run.js +13 -38
- package/dist/slices/onboarding/install.js +2 -1
- package/dist/slices/onboarding/model.js +26 -21
- package/dist/slices/onboarding/preferred-model.js +14 -0
- package/dist/slices/onboarding/quick-short.js +103 -64
- package/dist/slices/onboarding/sample.js +4 -1
- package/dist/slices/onboarding/state.js +7 -0
- package/dist/slices/onboarding/template.js +6 -5
- package/dist/slices/outputs/add.js +3 -2
- package/dist/slices/outputs/service.js +2 -2
- package/dist/slices/patch-notes/library.js +1 -1
- package/dist/slices/play-drafts/readiness.js +3 -3
- package/dist/slices/play-drafts/review-inputs.js +1 -1
- package/dist/slices/play-drafts/service.js +17 -7
- package/dist/slices/project-templates/next-chapter.js +2 -1
- package/dist/slices/project-templates/provider-defaults.js +22 -0
- package/dist/slices/project-templates/setup-sections.js +26 -0
- package/dist/slices/project-templates/setup.js +6 -1
- package/dist/slices/rebuild/preview-details.js +5 -5
- package/dist/slices/rebuild/recipe-preparation.js +1 -1
- package/dist/slices/rebuild/recipe-save.js +2 -2
- package/dist/slices/rebuild/recipe-text.js +16 -4
- package/dist/slices/rebuild/recipe-validation.js +5 -4
- package/dist/slices/rebuild/recipe-voices.js +3 -2
- package/dist/slices/rebuild/recipe-work.js +4 -4
- package/dist/slices/rebuild/recovery.js +6 -6
- package/dist/slices/rebuild/remake-outdated.js +5 -5
- package/dist/slices/rebuild/review-redo.js +1 -1
- package/dist/slices/rebuild/runtime-animate.js +2 -2
- package/dist/slices/rebuild/runtime-article.js +4 -3
- package/dist/slices/rebuild/runtime-document.js +5 -4
- package/dist/slices/rebuild/runtime-export-bed.js +2 -1
- package/dist/slices/rebuild/runtime-export-edit.js +2 -1
- package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
- package/dist/slices/rebuild/runtime-export-short.js +1 -1
- package/dist/slices/rebuild/runtime-export.js +4 -3
- package/dist/slices/rebuild/runtime-image.js +4 -3
- package/dist/slices/rebuild/runtime-local.js +3 -2
- package/dist/slices/rebuild/runtime-narration-text.js +3 -2
- package/dist/slices/rebuild/runtime-provider.js +7 -6
- package/dist/slices/rebuild/runtime-review.js +6 -5
- package/dist/slices/rebuild/runtime-shorts.js +6 -5
- package/dist/slices/rebuild/runtime-store.js +17 -2
- package/dist/slices/rebuild/runtime-subtitles.js +13 -12
- package/dist/slices/rebuild/runtime-voices.js +2 -1
- package/dist/slices/rebuild/runtime-youtube.js +3 -2
- package/dist/slices/rebuild/service-readiness.js +11 -3
- package/dist/slices/research/synthesis.js +3 -2
- package/dist/slices/reviews/external-model.js +1 -1
- package/dist/slices/reviews/holds.js +5 -4
- package/dist/slices/reviews/model.js +8 -5
- package/dist/slices/reviews/rules.js +3 -3
- package/dist/slices/reviews/verdict.js +2 -1
- package/dist/slices/revisions/image-plan.js +1 -1
- package/dist/slices/revisions/mutation-assets.js +2 -1
- package/dist/slices/revisions/mutations.js +3 -2
- package/dist/slices/schedules/agenda.js +7 -5
- package/dist/slices/schedules/design-check.js +4 -4
- package/dist/slices/schedules/design-run.js +3 -3
- package/dist/slices/schedules/repo.js +6 -5
- package/dist/slices/schedules/schema.js +3 -0
- package/dist/slices/schedules/topics.js +5 -5
- package/dist/slices/settings/first-run.js +6 -32
- package/dist/slices/settings/health.js +30 -15
- package/dist/slices/settings/key-test.js +1 -1
- package/dist/slices/settings/model.js +4 -2
- package/dist/slices/settings/playback.js +2 -2
- package/dist/slices/shorts/keep.js +2 -1
- package/dist/slices/shorts/pick.js +4 -3
- package/dist/slices/shorts/prompts.js +2 -1
- package/dist/slices/shorts/render.js +3 -2
- package/dist/slices/social/media.js +5 -4
- package/dist/slices/social/pack.js +1 -1
- package/dist/slices/social/post-prompt.js +2 -1
- package/dist/slices/social/reshape.js +2 -2
- package/dist/slices/social/serve.js +1 -1
- package/dist/slices/social/service.js +3 -2
- package/dist/slices/social/state.js +1 -1
- package/dist/slices/storage/backup-import.js +2 -2
- package/dist/slices/storage/portable.js +1 -1
- package/dist/slices/storage/trim.js +23 -13
- package/dist/slices/studio/calendar.js +3 -1
- package/dist/slices/studio/disclosure.js +4 -4
- package/dist/slices/studio/model.js +1 -1
- package/dist/slices/studio/pack.js +9 -8
- package/dist/slices/studio/plan.js +1 -1
- package/dist/slices/studio/settings.js +6 -6
- package/dist/slices/studio/videos.js +23 -4
- package/dist/slices/style-preview/render.js +3 -3
- package/dist/slices/style-preview/schema.js +1 -1
- package/dist/slices/subtitles/captions.js +1 -1
- package/dist/slices/template-studio/anatomy-view.js +46 -3
- package/dist/slices/template-studio/bakeoff.js +3 -3
- package/dist/slices/template-studio/budget.js +6 -3
- package/dist/slices/template-studio/cards.js +78 -0
- package/dist/slices/template-studio/checks.js +99 -50
- package/dist/slices/template-studio/for-all.js +5 -5
- package/dist/slices/template-studio/free-video.js +1 -1
- package/dist/slices/template-studio/free.js +5 -5
- package/dist/slices/template-studio/model.js +19 -15
- package/dist/slices/template-studio/pack-import.js +14 -14
- package/dist/slices/template-studio/pack.js +9 -7
- package/dist/slices/template-studio/paid.js +12 -5
- package/dist/slices/template-studio/quote.js +18 -11
- package/dist/slices/template-studio/ready.js +7 -7
- package/dist/slices/template-studio/runner.js +1 -1
- package/dist/slices/template-studio/save.js +12 -12
- package/dist/slices/template-studio/say-it-as.js +3 -3
- package/dist/slices/template-studio/schedule-gate.js +1 -1
- package/dist/slices/template-studio/schedule-price.js +3 -1
- package/dist/slices/template-studio/schedule-sample.js +5 -1
- package/dist/slices/template-studio/schema.js +25 -0
- package/dist/slices/template-studio/service.js +3 -1
- package/dist/slices/template-studio/status.js +26 -9
- package/dist/slices/template-studio/topic.js +3 -3
- package/dist/slices/thumbnail/by-llm.js +1 -1
- package/dist/slices/uploads/repo.js +37 -1
- package/dist/slices/uploads/rules.js +3 -2
- package/dist/slices/video/edit-list.js +2 -2
- package/dist/slices/video/edit-settings.js +1 -1
- package/dist/slices/video/encoding.js +14 -5
- package/dist/slices/video/ffmpeg.js +2 -2
- package/dist/slices/video/plan.js +2 -1
- package/dist/slices/video/slideshow.js +1 -1
- package/dist/slices/voices/attribution.js +3 -2
- package/dist/slices/voices/model.js +9 -8
- package/dist/slices/voices/script.js +2 -1
- package/dist/slices/youtube/answer.js +2 -1
- package/dist/slices/youtube/edits-repo.js +1 -1
- package/dist/tutorials/AI-CLIs.md +25 -25
- package/dist/tutorials/Agents-MCP.md +32 -32
- package/dist/tutorials/Assistant.md +5 -5
- package/dist/tutorials/Backups.md +7 -7
- package/dist/tutorials/Calendar.md +17 -25
- package/dist/tutorials/Cast-Library.md +19 -19
- package/dist/tutorials/Channels.md +40 -35
- package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
- package/dist/tutorials/Create-Article.md +114 -0
- package/dist/tutorials/Create-Extras.md +112 -0
- package/dist/tutorials/Create-Images.md +165 -0
- package/dist/tutorials/Create-Narration.md +178 -0
- package/dist/tutorials/Create-Overview.md +244 -0
- package/dist/tutorials/Create-Video-and-Style.md +144 -0
- package/dist/tutorials/Docker.md +3 -3
- package/dist/tutorials/Document-Themes.md +32 -32
- package/dist/tutorials/Editing-a-Project.md +95 -94
- package/dist/tutorials/FAQ.md +18 -18
- package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
- package/dist/tutorials/Home-and-Projects.md +101 -76
- package/dist/tutorials/Home.md +33 -33
- package/dist/tutorials/Install.md +13 -13
- package/dist/tutorials/Intros-and-Outros.md +33 -34
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
- package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
- package/dist/tutorials/Models.md +27 -27
- package/dist/tutorials/Multiple-Voices.md +41 -40
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +101 -19
- package/dist/tutorials/Notifications.md +21 -17
- package/dist/tutorials/Other-Languages.md +15 -15
- package/dist/tutorials/PDF-Documents.md +38 -29
- package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
- package/dist/tutorials/Project-Page.md +118 -120
- package/dist/tutorials/Prompts-and-Templates.md +84 -0
- package/dist/tutorials/Prompts.md +45 -45
- package/dist/tutorials/Providers-and-Keys.md +105 -98
- package/dist/tutorials/Publishing-to-YouTube.md +31 -31
- package/dist/tutorials/Recovery-and-Retries.md +31 -31
- package/dist/tutorials/Remote-Access.md +2 -2
- package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
- package/dist/tutorials/Schedules.md +27 -28
- package/dist/tutorials/Settings-Reference.md +183 -204
- package/dist/tutorials/Shorts.md +34 -33
- package/dist/tutorials/Start-at-Login.md +3 -3
- package/dist/tutorials/Studio-Extension.md +22 -24
- package/dist/tutorials/Template-Studio.md +45 -42
- package/dist/tutorials/Templates.md +50 -79
- package/dist/tutorials/Trash.md +4 -4
- package/dist/tutorials/Troubleshooting.md +44 -44
- package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
- package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
- package/dist/tutorials/Video-Editing.md +34 -34
- package/dist/tutorials/Where-Your-Files-Live.md +10 -10
- package/dist/tutorials/YouTube-Description.md +26 -26
- package/dist/tutorials/Your-First-Short.md +35 -35
- package/dist/tutorials/_Sidebar.md +15 -15
- package/dist/web/assets/index-BNftKQrA.css +1 -0
- package/dist/web/assets/index-CQASqwjT.js +460 -0
- package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
- package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
- package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/tutorials/Library-Overview.md +0 -76
- package/dist/tutorials/Play-Images.md +0 -165
- package/dist/tutorials/Play-Narration.md +0 -167
- package/dist/tutorials/Play-Outputs.md +0 -109
- package/dist/tutorials/Play-Overview.md +0 -227
- package/dist/tutorials/Play-Title-and-Article.md +0 -136
- package/dist/tutorials/Play-Video-and-Style.md +0 -143
- package/dist/web/assets/index-CkAI_2E7.css +0 -1
- package/dist/web/assets/index-DcD0cdfo.js +0 -334
- package/dist/web/assets/pdf-TODRwF31.js +0 -54
- package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
- package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fingerprint } from "../../kernel/runner/work.js";
|
|
3
|
+
import { actors } from "../agents/model.js";
|
|
4
|
+
import { deleteSetting, readJsonSetting, readSetting, writeSetting } from "../settings/repo.js";
|
|
5
|
+
import { libraryPronunciationsMax, parsePronunciationGlossary, } from "./pronunciation.js";
|
|
6
|
+
// Settings → Voices → Name pronunciations: one Pronunciation Glossary of your own, in the same
|
|
7
|
+
// `Term: /ipa/` lines an article's own glossary uses. Its entries come before the article's, so
|
|
8
|
+
// a name keeps the same pronunciation in every video however each article spells it out. A
|
|
9
|
+
// project takes a copy when it starts (and again when Edit project refreshes it), so a change
|
|
10
|
+
// here never alters a project's narration behind its back. Aliases on the Pronunciation page are
|
|
11
|
+
// the other half: they change the words, this changes the sounds.
|
|
12
|
+
export const libraryPronunciationsKey = "narration.pronunciations";
|
|
13
|
+
const savedKey = "narration.pronunciations.saved";
|
|
14
|
+
const savedRow = z
|
|
15
|
+
.object({ at: z.string().min(1).max(40), by: z.enum(actors) })
|
|
16
|
+
.strict()
|
|
17
|
+
.readonly();
|
|
18
|
+
export function readLibraryPronunciations(db) {
|
|
19
|
+
const saved = readJsonSetting(db, savedKey, savedRow, undefined);
|
|
20
|
+
return {
|
|
21
|
+
...describeLibraryPronunciations(readSetting(db, libraryPronunciationsKey) ?? ""),
|
|
22
|
+
...(saved === undefined ? {} : { savedAt: saved.at, savedBy: saved.by }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function describeLibraryPronunciations(text) {
|
|
26
|
+
const parsed = parsePronunciationGlossary(text);
|
|
27
|
+
return {
|
|
28
|
+
text,
|
|
29
|
+
terms: parsed.ok ? parsed.entries.length : 0,
|
|
30
|
+
version: versionOf(text),
|
|
31
|
+
...(parsed.ok && parsed.skipped !== undefined ? { skipped: parsed.skipped } : {}),
|
|
32
|
+
...(parsed.ok && parsed.unpronounced !== undefined
|
|
33
|
+
? { unpronounced: parsed.unpronounced }
|
|
34
|
+
: {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Short enough to pass around and read in a refusal, long enough never to collide.
|
|
38
|
+
export function versionOf(text) {
|
|
39
|
+
return fingerprint({ pronunciations: text.trim() }).slice(0, 16);
|
|
40
|
+
}
|
|
41
|
+
// `version` is the list the save was made from. It is required once a list exists: a save
|
|
42
|
+
// without it, or from an older one, is refused rather than replacing what somebody else wrote
|
|
43
|
+
// in another window or through an agent.
|
|
44
|
+
export function saveLibraryPronunciations(deps, text, version, by = "person") {
|
|
45
|
+
if (typeof text !== "string")
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
reason: "refused",
|
|
49
|
+
detail: "Write the list as Term: /ipa/ lines, one name per line.",
|
|
50
|
+
};
|
|
51
|
+
if (text.length > libraryPronunciationsMax)
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
reason: "refused",
|
|
55
|
+
detail: `Keep the list under ${String(libraryPronunciationsMax)} characters; it is ${String(text.length)} now. Remove the names you no longer narrate, then Save.`,
|
|
56
|
+
};
|
|
57
|
+
const current = readLibraryPronunciations(deps.db);
|
|
58
|
+
if (current.text !== "" && version !== current.version)
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
reason: "conflict",
|
|
62
|
+
detail: conflictDetail(current, version),
|
|
63
|
+
current,
|
|
64
|
+
};
|
|
65
|
+
const trimmed = text.trim();
|
|
66
|
+
if (trimmed === "")
|
|
67
|
+
deleteSetting(deps.db, libraryPronunciationsKey);
|
|
68
|
+
else
|
|
69
|
+
writeSetting(deps.db, libraryPronunciationsKey, trimmed);
|
|
70
|
+
writeSetting(deps.db, savedKey, JSON.stringify({ at: deps.clock.now().toISOString(), by }));
|
|
71
|
+
return { ok: true, list: readLibraryPronunciations(deps.db) };
|
|
72
|
+
}
|
|
73
|
+
// Said to whoever sent the save: the person at the page, or an agent reading a refusal. It
|
|
74
|
+
// names what the list is now and the one step that keeps their own work.
|
|
75
|
+
function conflictDetail(current, version) {
|
|
76
|
+
const who = current.savedBy === "agent"
|
|
77
|
+
? " by an agent"
|
|
78
|
+
: current.savedBy === "assistant"
|
|
79
|
+
? " by the assistant"
|
|
80
|
+
: "";
|
|
81
|
+
const when = current.savedAt === undefined ? "" : ` at ${current.savedAt}`;
|
|
82
|
+
const names = `${String(current.terms)} ${current.terms === 1 ? "name" : "names"}`;
|
|
83
|
+
return version === undefined
|
|
84
|
+
? `The list has ${names} already, saved${who}${when}. Read it first and send your save with the version it gives, so this one does not replace what is there.`
|
|
85
|
+
: `The list changed since this save was prepared: it has ${names} now, saved${who}${when}. Read it again and save with the version it gives; on the page, Save mine anyway keeps what is on screen.`;
|
|
86
|
+
}
|
|
87
|
+
// The entries a project's copy pronounces, under the project's own language and rules. An
|
|
88
|
+
// unparseable list is never fatal: its bad rows are skipped, and the editor is where they are
|
|
89
|
+
// named (the run's notices number the rows of the article's glossary, not of this list).
|
|
90
|
+
export function libraryGlossary(text, language, rules) {
|
|
91
|
+
if (text === undefined || text.trim() === "")
|
|
92
|
+
return { ok: true, entries: [] };
|
|
93
|
+
return parsePronunciationGlossary(text, language, rules);
|
|
94
|
+
}
|
|
@@ -4,7 +4,7 @@ import { stepFailedWhileWaiting } from "../admission/state-words.js";
|
|
|
4
4
|
import { lastStarter } from "../agents/repo.js";
|
|
5
5
|
import { projectHolds } from "../reviews/holds.js";
|
|
6
6
|
import { isTemplateSample } from "../template-studio/repo.js";
|
|
7
|
-
import {
|
|
7
|
+
import { settledProjects } from "../uploads/repo.js";
|
|
8
8
|
import { settledStatus } from "../uploads/rules.js";
|
|
9
9
|
import { atItsDay, failuresOf } from "./rules.js";
|
|
10
10
|
// What a notification says about a project, read at the moment it goes out, by the rule the
|
|
@@ -14,7 +14,7 @@ export function noticeSubject(db, projectId) {
|
|
|
14
14
|
if (project === undefined || isTemplateSample(db, projectId))
|
|
15
15
|
return undefined;
|
|
16
16
|
const stages = stagesOf(db, projectId);
|
|
17
|
-
const settled =
|
|
17
|
+
const settled = settledProjects(db, projectId);
|
|
18
18
|
const holds = projectHolds(db, { projectId, standings: new Map([[projectId, stages]]), settled });
|
|
19
19
|
const status = settledStatus(derive(stages, project.paused), settled.has(projectId));
|
|
20
20
|
const failures = failuresOf(stages);
|
|
@@ -12,7 +12,7 @@ import { projectDir } from "../storage/layout.js";
|
|
|
12
12
|
// ceiling: one pass per layer of the step graph (text, narration, timing, picks, prompts,
|
|
13
13
|
// images, renders); far more than any plan has.
|
|
14
14
|
const rebindPasses = 40;
|
|
15
|
-
export function copyProject(deps, sourceId, title) {
|
|
15
|
+
export function copyProject(deps, sourceId, title, { renamed = false } = {}) {
|
|
16
16
|
const source = projectDir(deps.paths, sourceId);
|
|
17
17
|
const tables = projectTables.map((table) => [table, projectRows(deps.db, table, sourceId)]);
|
|
18
18
|
const ids = new Map([[sourceId, deps.ids.next()]]);
|
|
@@ -34,6 +34,9 @@ export function copyProject(deps, sourceId, title) {
|
|
|
34
34
|
const projectId = ids.get(sourceId) ?? sourceId;
|
|
35
35
|
const target = projectDir(deps.paths, projectId);
|
|
36
36
|
const at = deps.clock.now().toISOString();
|
|
37
|
+
const named = (row) => renamed && typeof row.config === "string"
|
|
38
|
+
? { ...row, config: renameConfig(row.config, title) }
|
|
39
|
+
: row;
|
|
37
40
|
try {
|
|
38
41
|
for (const file of listFiles(source)) {
|
|
39
42
|
if (file === projectMarker)
|
|
@@ -52,7 +55,11 @@ export function copyProject(deps, sourceId, title) {
|
|
|
52
55
|
column,
|
|
53
56
|
typeof value === "string" ? remap(value) : value,
|
|
54
57
|
]));
|
|
55
|
-
insert(deps.db, table, table === "projects"
|
|
58
|
+
insert(deps.db, table, table === "projects"
|
|
59
|
+
? { ...named(copied), title, created_at: at, updated_at: at }
|
|
60
|
+
: table === "project_revisions"
|
|
61
|
+
? named(copied)
|
|
62
|
+
: copied);
|
|
56
63
|
}
|
|
57
64
|
rebind(deps, projectId);
|
|
58
65
|
});
|
|
@@ -63,6 +70,16 @@ export function copyProject(deps, sourceId, title) {
|
|
|
63
70
|
}
|
|
64
71
|
return { projectId };
|
|
65
72
|
}
|
|
73
|
+
// A stored config (JSON) under a new title, keeping what it was made about.
|
|
74
|
+
function renameConfig(config, title) {
|
|
75
|
+
const parsed = JSON.parse(config);
|
|
76
|
+
if (typeof parsed !== "object" || parsed === null || !("title" in parsed))
|
|
77
|
+
return config;
|
|
78
|
+
const before = parsed;
|
|
79
|
+
if (typeof before.title !== "string" || before.title === title)
|
|
80
|
+
return config;
|
|
81
|
+
return JSON.stringify({ ...before, title, subjectTitle: before.subjectTitle ?? before.title });
|
|
82
|
+
}
|
|
66
83
|
// Re-stamps the copy's finished steps with the fingerprints its own ids give them.
|
|
67
84
|
function rebind(deps, projectId) {
|
|
68
85
|
const revisionId = currentRevisionId(deps.db, projectId);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { readinessIsUsable } from "../../kernel/ports/model.js";
|
|
2
1
|
import { templateById } from "../project-templates/repo.js";
|
|
2
|
+
import { listVoices } from "../settings/repo.js";
|
|
3
3
|
import { notTemplateSample } from "../template-studio/repo.js";
|
|
4
|
-
import { starterPacks } from "./packs.js";
|
|
4
|
+
import { starterPacks, starterSet } from "./packs.js";
|
|
5
|
+
import { shortReadiness } from "./quick-short.js";
|
|
5
6
|
import { sampleProjectIds } from "./sample.js";
|
|
6
7
|
import { firstRunDismissed, readPackRecords } from "./state.js";
|
|
7
8
|
// The first-run screen shows on a fresh install and never again once it is skipped or a real
|
|
8
9
|
// project (anything but the samples and the template studio's hidden projects) exists. Reading it writes nothing: a real project that
|
|
9
10
|
// exists before the screen was recorded as done is reported as `settle`, and the client
|
|
10
11
|
// records it with POST /dismiss, so deleting every project later does not bring it back.
|
|
11
|
-
export function firstRunView(db, statuses
|
|
12
|
+
export function firstRunView(db, statuses,
|
|
13
|
+
// How to install the computer's voice here, for the checklist's voice line.
|
|
14
|
+
install) {
|
|
12
15
|
const samples = sampleProjectIds(db);
|
|
13
16
|
const real = db
|
|
14
17
|
.prepare(`SELECT 1 FROM projects WHERE id IS NOT ? AND id IS NOT ? AND id IS NOT ? AND ${notTemplateSample()} LIMIT 1`)
|
|
@@ -18,10 +21,15 @@ export function firstRunView(db, statuses) {
|
|
|
18
21
|
return {
|
|
19
22
|
show: !real && !dismissed,
|
|
20
23
|
settle: real && !dismissed,
|
|
21
|
-
voice
|
|
24
|
+
// Every starter pack suggests the same voice, so the starter set's stands for all of them.
|
|
25
|
+
needs: shortReadiness({
|
|
26
|
+
statuses,
|
|
27
|
+
voices: listVoices(db),
|
|
28
|
+
suggested: starterSet.voice,
|
|
29
|
+
install,
|
|
30
|
+
}),
|
|
22
31
|
sampleProjectId: samples.library,
|
|
23
32
|
samples,
|
|
24
|
-
clis: detectedClis(statuses),
|
|
25
33
|
// A pack counts as added only with its template: the first short adds a pack's prompts and
|
|
26
34
|
// voice but no template, and Add pack must still be able to add that template later (or
|
|
27
35
|
// again, after it was deleted).
|
|
@@ -38,36 +46,3 @@ export function firstRunView(db, statuses) {
|
|
|
38
46
|
}),
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
|
-
// Who would narrate a first short: a keyed voice provider when one has a key, else the
|
|
42
|
-
// computer's own voice when a speech program was found.
|
|
43
|
-
export function firstVoice(statuses) {
|
|
44
|
-
const keyed = statuses.find((row) => row.family === "tts" && row.readiness.kind === "keyed" && readinessIsUsable(row.readiness));
|
|
45
|
-
const system = statuses.find((row) => row.readiness.kind === "local")?.readiness;
|
|
46
|
-
const local = system?.kind === "local" ? system : undefined;
|
|
47
|
-
return {
|
|
48
|
-
keyed: keyed?.displayName ?? null,
|
|
49
|
-
system: {
|
|
50
|
-
available: local?.available === true,
|
|
51
|
-
engine: local?.engine ?? null,
|
|
52
|
-
issue: local?.issue ?? null,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// What this machine can make without a key: each agent CLI Slopify found, and whether it
|
|
57
|
-
// answers (installed, a supported version, signed in as far as the probe can tell).
|
|
58
|
-
export function detectedClis(statuses) {
|
|
59
|
-
return ["claude-code", "codex", "gemini"].map((id) => {
|
|
60
|
-
const status = statuses.find((row) => row.id === id);
|
|
61
|
-
const readiness = status?.readiness;
|
|
62
|
-
const cli = readiness?.kind === "cli" ? readiness : undefined;
|
|
63
|
-
return {
|
|
64
|
-
id,
|
|
65
|
-
name: status?.displayName ?? id,
|
|
66
|
-
installed: cli?.installed === true,
|
|
67
|
-
ready: cli?.installed === true && cli.issue === undefined,
|
|
68
|
-
version: cli?.version ?? null,
|
|
69
|
-
issue: cli?.issue ?? null,
|
|
70
|
-
draws: id === "codex",
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
}
|
|
@@ -5,6 +5,7 @@ import { nameMax } from "../library/model.js";
|
|
|
5
5
|
import { insertPrompt, promptById, promptByName } from "../library/repo.js";
|
|
6
6
|
import { templateById, templateSummaries } from "../project-templates/repo.js";
|
|
7
7
|
import { createTemplate } from "../project-templates/service.js";
|
|
8
|
+
import { readProviderDefaults } from "../settings/first-run.js";
|
|
8
9
|
import { insertVoice, listVoices } from "../settings/repo.js";
|
|
9
10
|
import { packById } from "./packs.js";
|
|
10
11
|
import { readPackRecords, writePackRecord } from "./state.js";
|
|
@@ -46,7 +47,7 @@ export function installPack(deps, packId, options = { template: true }) {
|
|
|
46
47
|
const created = createTemplate(deps, {
|
|
47
48
|
id,
|
|
48
49
|
name: freeTemplateName(deps, `${pack.name} starter`),
|
|
49
|
-
document: packTemplate(pack, names, voice.voiceId),
|
|
50
|
+
document: packTemplate(pack, names, voice.voiceId, readProviderDefaults(deps.db)),
|
|
50
51
|
});
|
|
51
52
|
if (!created.ok)
|
|
52
53
|
throw new Error(internalError(`the ${pack.name} pack's template was refused: ${created.reason}`));
|
|
@@ -10,6 +10,25 @@ export const sampleProjectsSchema = z.object({
|
|
|
10
10
|
audiobook: z.string().nullable(),
|
|
11
11
|
podcast: z.string().nullable(),
|
|
12
12
|
});
|
|
13
|
+
// What a 60-second short needs that nothing on this computer does yet, with what fixes it. The
|
|
14
|
+
// first-run checklist and the refusal of "Make a 60-second short" say the same gaps.
|
|
15
|
+
export const shortGapSchema = z.object({
|
|
16
|
+
need: z.enum(["text", "voice", "images"]),
|
|
17
|
+
// What is missing and what fixes it, in a sentence or two.
|
|
18
|
+
message: z.string(),
|
|
19
|
+
// A key for that part, or (the voice) a voice saved for the voice service that has a key.
|
|
20
|
+
fix: z.enum(["key", "saved-voice"]),
|
|
21
|
+
// The voice only: how to install the computer's own free voice from a terminal, where that
|
|
22
|
+
// can be done.
|
|
23
|
+
install: z.object({ text: z.string(), commands: z.array(z.string()) }).optional(),
|
|
24
|
+
});
|
|
25
|
+
// Who does one part of a first short, by name ("Claude Code CLI", "OpenAI", "eSpeak NG"), and
|
|
26
|
+
// how: with the person's own sign-in to a tool on this computer, a saved key, or (the voice)
|
|
27
|
+
// the computer's own speech. Null while nothing can.
|
|
28
|
+
const shortPartSchema = z.object({
|
|
29
|
+
by: z.string().nullable(),
|
|
30
|
+
how: z.enum(["sign-in", "key", "computer"]).nullable(),
|
|
31
|
+
});
|
|
13
32
|
export const firstRunViewSchema = z
|
|
14
33
|
.object({
|
|
15
34
|
show: z.boolean(),
|
|
@@ -17,31 +36,17 @@ export const firstRunViewSchema = z
|
|
|
17
36
|
// with POST /dismiss, so deleting every project later does not bring the screen back.
|
|
18
37
|
// Reading this view writes nothing.
|
|
19
38
|
settle: z.boolean(),
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
engine: z.string().nullable(),
|
|
28
|
-
// Why none can be used, with the fix.
|
|
29
|
-
issue: z.string().nullable(),
|
|
30
|
-
}),
|
|
39
|
+
// The three things a short needs (the script, a voice and the images), each with who would
|
|
40
|
+
// do it, and the gaps, as "Make a 60-second short" would refuse them.
|
|
41
|
+
needs: z.object({
|
|
42
|
+
text: shortPartSchema,
|
|
43
|
+
voice: shortPartSchema,
|
|
44
|
+
images: shortPartSchema,
|
|
45
|
+
gaps: z.array(shortGapSchema),
|
|
31
46
|
}),
|
|
32
47
|
// The Library of Alexandria's project, as before the demos; `samples` has all three.
|
|
33
48
|
sampleProjectId: z.string().nullable(),
|
|
34
49
|
samples: sampleProjectsSchema,
|
|
35
|
-
clis: z.array(z.object({
|
|
36
|
-
id: z.enum(["claude-code", "codex", "gemini"]),
|
|
37
|
-
name: z.string(),
|
|
38
|
-
installed: z.boolean(),
|
|
39
|
-
ready: z.boolean(),
|
|
40
|
-
version: z.string().nullable(),
|
|
41
|
-
issue: z.string().nullable(),
|
|
42
|
-
// Codex draws the images too.
|
|
43
|
-
draws: z.boolean(),
|
|
44
|
-
})),
|
|
45
50
|
packs: z.array(z.object({
|
|
46
51
|
id: z.string(),
|
|
47
52
|
name: z.string(),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The model a service starts with when Slopify picks it for the person (a first short, the
|
|
2
|
+
// writer and voice Create fills in): a fast, capable default rather than the first listed,
|
|
3
|
+
// which can be the slowest or the smallest. One rule, so a service names the same model
|
|
4
|
+
// wherever it was picked for the person.
|
|
5
|
+
const preferred = {
|
|
6
|
+
"claude-code": /sonnet/i,
|
|
7
|
+
"openai-tts": /^gpt-4o-mini-tts$/,
|
|
8
|
+
};
|
|
9
|
+
export function isPreferredModel(provider, model) {
|
|
10
|
+
return preferred[provider]?.test(model) === true;
|
|
11
|
+
}
|
|
12
|
+
export function preferredModel(provider, models) {
|
|
13
|
+
return models.find((model) => isPreferredModel(provider, model.id)) ?? models[0];
|
|
14
|
+
}
|
|
@@ -2,18 +2,82 @@ import { readinessIsUsable } from "../../kernel/ports/model.js";
|
|
|
2
2
|
import { titleMax } from "../admission/rules.js";
|
|
3
3
|
import { defaultSentencePauseSeconds } from "../narration/pauses-model.js";
|
|
4
4
|
import { systemVoiceProvider } from "../settings/model.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { preferredModel } from "./preferred-model.js";
|
|
6
|
+
// Who is asked first for each part: the CLIs before a key for the text, Codex before a key for
|
|
7
|
+
// the images.
|
|
8
|
+
const textOrder = ["claude-code", "codex", "gemini", "openrouter"];
|
|
9
|
+
const imageOrder = ["codex-image", "openai-image", "google-image", "fal", "replicate"];
|
|
7
10
|
const keyedVoices = ["openai-tts", "elevenlabs", "cartesia", "inworld"];
|
|
8
|
-
// The first model of these providers is the one a first short uses, unless one of these is
|
|
9
|
-
// listed: a fast, capable default rather than the slowest or the smallest.
|
|
10
|
-
const preferred = {
|
|
11
|
-
"claude-code": /sonnet/i,
|
|
12
|
-
"openai-tts": /^gpt-4o-mini-tts$/,
|
|
13
|
-
};
|
|
14
11
|
function ready(status) {
|
|
15
12
|
return status !== undefined && readinessIsUsable(status.readiness);
|
|
16
13
|
}
|
|
14
|
+
const statusOf = (statuses) => (id) => statuses.find((row) => row.id === id);
|
|
15
|
+
// The keyed voice a first short narrates with: a saved voice of a voice service with a key,
|
|
16
|
+
// else the voice the pack suggests when its service has a key.
|
|
17
|
+
function keyedVoice(statuses, voices, suggested) {
|
|
18
|
+
const status = statusOf(statuses);
|
|
19
|
+
const saved = voices.find((voice) => voice.provider !== systemVoiceProvider && ready(status(voice.provider)));
|
|
20
|
+
if (saved !== undefined)
|
|
21
|
+
return { provider: saved.provider, voiceId: saved.voiceId };
|
|
22
|
+
return ready(status(suggested.provider))
|
|
23
|
+
? { provider: suggested.provider, voiceId: suggested.voiceId }
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
// One gap per part nothing can do, in plain words with what fixes it. The voice names a voice
|
|
27
|
+
// service that has a key but no saved voice, since a key alone is not the fix then.
|
|
28
|
+
function gapOf(need, statuses, install) {
|
|
29
|
+
if (need === "text")
|
|
30
|
+
return {
|
|
31
|
+
need,
|
|
32
|
+
fix: "key",
|
|
33
|
+
message: "Nothing can write the article yet. Sign in to Claude Code, Codex or Antigravity on this computer, or add an OpenRouter key.",
|
|
34
|
+
};
|
|
35
|
+
if (need === "images")
|
|
36
|
+
return {
|
|
37
|
+
need,
|
|
38
|
+
fix: "key",
|
|
39
|
+
message: "Nothing can make the images yet. Add an image key (OpenAI, Google, fal.ai or Replicate), or sign in to Codex on this computer.",
|
|
40
|
+
};
|
|
41
|
+
const status = statusOf(statuses);
|
|
42
|
+
const unsaved = keyedVoices.map(status).find((row) => ready(row));
|
|
43
|
+
if (unsaved !== undefined)
|
|
44
|
+
return {
|
|
45
|
+
need,
|
|
46
|
+
fix: "saved-voice",
|
|
47
|
+
message: `${unsaved.displayName} has a key but no voice saved yet. Save one in Settings → Voices, or add an OpenAI key, which works at once.`,
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
need,
|
|
51
|
+
fix: "key",
|
|
52
|
+
message: "Nothing can read the article aloud yet. Add a voice key: an OpenAI key works at once with the voices Slopify sets up.",
|
|
53
|
+
...(install === undefined ? {} : { install: { ...install, commands: [...install.commands] } }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// What a first short would use on this computer and what it lacks, without asking any provider
|
|
57
|
+
// for its models: the first-run checklist, which must agree with the refusal below.
|
|
58
|
+
export function shortReadiness(input) {
|
|
59
|
+
const status = statusOf(input.statuses);
|
|
60
|
+
const part = (row) => row === undefined
|
|
61
|
+
? { by: null, how: null }
|
|
62
|
+
: { by: row.displayName, how: row.readiness.kind === "cli" ? "sign-in" : "key" };
|
|
63
|
+
const text = part(textOrder.map(status).find((row) => ready(row)));
|
|
64
|
+
const images = part(imageOrder.map(status).find((row) => ready(row)));
|
|
65
|
+
const keyed = keyedVoice(input.statuses, input.voices, input.suggested);
|
|
66
|
+
const system = status(systemVoiceProvider)?.readiness;
|
|
67
|
+
const voice = keyed !== undefined
|
|
68
|
+
? part(status(keyed.provider))
|
|
69
|
+
: system?.kind === "local" && system.available
|
|
70
|
+
? { by: system.engine ?? "System voice", how: "computer" }
|
|
71
|
+
: { by: null, how: null };
|
|
72
|
+
const gaps = [
|
|
73
|
+
["text", text],
|
|
74
|
+
["images", images],
|
|
75
|
+
["voice", voice],
|
|
76
|
+
]
|
|
77
|
+
.filter(([, done]) => done.by === null)
|
|
78
|
+
.map(([need]) => gapOf(need, input.statuses, input.install));
|
|
79
|
+
return { text, voice, images, gaps };
|
|
80
|
+
}
|
|
17
81
|
async function firstModel(modelsFor, provider, family) {
|
|
18
82
|
let models;
|
|
19
83
|
try {
|
|
@@ -22,58 +86,37 @@ async function firstModel(modelsFor, provider, family) {
|
|
|
22
86
|
catch {
|
|
23
87
|
return undefined;
|
|
24
88
|
}
|
|
25
|
-
|
|
26
|
-
return ((liked === undefined ? undefined : models.find((model) => liked.test(model.id)))?.id ??
|
|
27
|
-
models[0]?.id);
|
|
89
|
+
return preferredModel(provider, models)?.id;
|
|
28
90
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
for (const id of [...textClis, "openrouter"]) {
|
|
91
|
+
// The first ready provider in `order` that lists a model.
|
|
92
|
+
async function firstReady(input, order, family) {
|
|
93
|
+
const status = statusOf(input.statuses);
|
|
94
|
+
for (const id of order) {
|
|
34
95
|
if (!ready(status(id)))
|
|
35
96
|
continue;
|
|
36
|
-
const model = await firstModel(input.modelsFor, id,
|
|
37
|
-
if (model !== undefined)
|
|
38
|
-
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (llm === undefined)
|
|
43
|
-
gaps.push({
|
|
44
|
-
need: "text",
|
|
45
|
-
message: "No text model is ready. Install Claude Code, Codex or Antigravity CLI and sign in to it (Settings → Providers shows what Slopify found), or add an OpenRouter key in Settings → Providers.",
|
|
46
|
-
});
|
|
47
|
-
let images;
|
|
48
|
-
for (const id of ["codex-image", ...keyedImages]) {
|
|
49
|
-
if (!ready(status(id)))
|
|
50
|
-
continue;
|
|
51
|
-
const model = await firstModel(input.modelsFor, id, "image");
|
|
52
|
-
if (model !== undefined) {
|
|
53
|
-
images = { provider: id, model };
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
97
|
+
const model = await firstModel(input.modelsFor, id, family);
|
|
98
|
+
if (model !== undefined)
|
|
99
|
+
return { provider: id, model };
|
|
56
100
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
export async function planShortProviders(input) {
|
|
104
|
+
const status = statusOf(input.statuses);
|
|
105
|
+
const llm = await firstReady(input, textOrder, "llm");
|
|
106
|
+
const images = await firstReady(input, imageOrder, "image");
|
|
62
107
|
// A keyed voice first (a saved one, else the pack's suggestion), then the computer's own: a
|
|
63
108
|
// saved system voice, else the one found. Keys are the better voice, never a requirement.
|
|
64
109
|
let audio;
|
|
65
|
-
const keyed = (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(ready(status(input.suggested.provider)) ? input.suggested.provider : undefined);
|
|
69
|
-
if (provider !== undefined) {
|
|
70
|
-
const model = await firstModel(input.modelsFor, provider, "tts");
|
|
110
|
+
const keyed = keyedVoice(input.statuses, input.voices, input.suggested);
|
|
111
|
+
if (keyed !== undefined) {
|
|
112
|
+
const model = await firstModel(input.modelsFor, keyed.provider, "tts");
|
|
71
113
|
if (model !== undefined)
|
|
72
|
-
audio = { provider, model, voice:
|
|
114
|
+
audio = { provider: keyed.provider, model, voice: keyed.voiceId };
|
|
73
115
|
}
|
|
74
116
|
let systemVoice = false;
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
if (audio === undefined &&
|
|
118
|
+
ready(status(systemVoiceProvider)) &&
|
|
119
|
+
input.systemVoice !== undefined) {
|
|
77
120
|
const savedSystem = input.voices.find((voice) => voice.provider === systemVoiceProvider);
|
|
78
121
|
audio = {
|
|
79
122
|
provider: systemVoiceProvider,
|
|
@@ -82,20 +125,16 @@ export async function planShortProviders(input) {
|
|
|
82
125
|
};
|
|
83
126
|
systemVoice = true;
|
|
84
127
|
}
|
|
85
|
-
if (audio === undefined)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return llm === undefined || images === undefined || audio === undefined
|
|
97
|
-
? { ok: false, gaps }
|
|
98
|
-
: { ok: true, providers: { llm, audio, images }, systemVoice };
|
|
128
|
+
if (llm === undefined || images === undefined || audio === undefined)
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
gaps: [
|
|
132
|
+
...(llm === undefined ? [gapOf("text", input.statuses, input.install)] : []),
|
|
133
|
+
...(images === undefined ? [gapOf("images", input.statuses, input.install)] : []),
|
|
134
|
+
...(audio === undefined ? [gapOf("voice", input.statuses, input.install)] : []),
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
return { ok: true, providers: { llm, audio, images }, systemVoice };
|
|
99
138
|
}
|
|
100
139
|
// The short-mode draft for a topic, using the pack's (or starter set's) script and scene
|
|
101
140
|
// prompts under the names they have in the library.
|
|
@@ -127,5 +127,8 @@ export function copySample(deps, projectId) {
|
|
|
127
127
|
status: 404,
|
|
128
128
|
detail: "That sample project is no longer here. Bring it back with Settings → Backup & storage → Restore samples, then press Make my own copy.",
|
|
129
129
|
};
|
|
130
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
...copyProject(deps, projectId, `${title} (my copy)`, { renamed: true }),
|
|
133
|
+
};
|
|
131
134
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { readJsonSetting, readSetting, writeSetting } from "../settings/repo.js";
|
|
3
|
+
import { sampleIds } from "./model.js";
|
|
3
4
|
// What the first five minutes remember, in the settings table: whether the first-run screen
|
|
4
5
|
// was dismissed, which project is the bundled sample, and which library rows each installed
|
|
5
6
|
// starter pack became. None of these keys is in a portable backup's settings, so another
|
|
@@ -41,6 +42,12 @@ const sampleKeyOf = (id) => (id === "library" ? sampleKey : `${sampleKey}.${id}`
|
|
|
41
42
|
export function readSampleRecord(db, id = "library") {
|
|
42
43
|
return readJson(db, sampleKeyOf(id), sampleRecord);
|
|
43
44
|
}
|
|
45
|
+
// The bundled samples' projects, by id, for a slice that must leave them as they are (Disk
|
|
46
|
+
// space's clean-ups). A deleted sample's record stays until Restore samples, so an id may name
|
|
47
|
+
// no project any more.
|
|
48
|
+
export function bundledSampleProjects(db) {
|
|
49
|
+
return new Set(sampleIds.flatMap((id) => readSampleRecord(db, id)?.projectId ?? []));
|
|
50
|
+
}
|
|
44
51
|
export function writeSampleRecord(db, record, id = "library") {
|
|
45
52
|
writeSetting(db, sampleKeyOf(id), JSON.stringify(record));
|
|
46
53
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { defaultEdgeSilenceSeconds } from "../admission/rules.js";
|
|
2
2
|
import { playDraftDocumentSchema } from "../play-drafts/schema.js";
|
|
3
3
|
// A pack's Play template: its prompts ticked, its voice picked, its style set, and the text and
|
|
4
|
-
// image providers
|
|
5
|
-
// differs per machine.
|
|
6
|
-
export function packTemplate(pack, names, voiceId) {
|
|
4
|
+
// image providers this machine's first launch found (Claude Code, Codex), since which of them
|
|
5
|
+
// is installed or keyed differs per machine. One it did not find is left for the user to pick.
|
|
6
|
+
export function packTemplate(pack, names, voiceId, providers = {}) {
|
|
7
7
|
const { style } = pack;
|
|
8
8
|
const name = (key) => names[key] ?? "";
|
|
9
|
+
const chosen = (choice) => choice === undefined ? { provider: "", model: "" } : { ...choice };
|
|
9
10
|
return playDraftDocumentSchema.parse({
|
|
10
11
|
schemaVersion: 1,
|
|
11
12
|
form: {
|
|
@@ -21,7 +22,7 @@ export function packTemplate(pack, names, voiceId) {
|
|
|
21
22
|
video: "generate",
|
|
22
23
|
document: "off",
|
|
23
24
|
},
|
|
24
|
-
llm:
|
|
25
|
+
llm: chosen(providers.llm),
|
|
25
26
|
audio: {
|
|
26
27
|
provider: pack.voice.provider,
|
|
27
28
|
model: pack.voice.model,
|
|
@@ -30,7 +31,7 @@ export function packTemplate(pack, names, voiceId) {
|
|
|
30
31
|
shareGlossary: true,
|
|
31
32
|
describeFigures: true,
|
|
32
33
|
},
|
|
33
|
-
images:
|
|
34
|
+
images: chosen(providers.images),
|
|
34
35
|
articlePrompt: name("article"),
|
|
35
36
|
narrationPrompt: "",
|
|
36
37
|
youtubeDescription: name("description") !== "",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stageKinds } from "../../kernel/pipeline.js";
|
|
2
|
+
import { editSettings } from "../../kernel/ports/settings-places.js";
|
|
2
3
|
import { sourceOf } from "../admission/model.js";
|
|
3
4
|
import { defaultVoicesSettings } from "../voices/model.js";
|
|
4
5
|
// Another output made from a project that already exists: the same brief, sources and
|
|
@@ -53,7 +54,7 @@ export function planAddedOutput(config, kind) {
|
|
|
53
54
|
return {
|
|
54
55
|
ok: false,
|
|
55
56
|
reason: "narration-taken",
|
|
56
|
-
message: `This project already has narration, and a project has one: ${kind === "podcast" ? "a podcast" : "an audiobook"} would replace it. Keep it, or change its format under Speakers in
|
|
57
|
+
message: `This project already has narration, and a project has one: ${kind === "podcast" ? "a podcast" : "an audiobook"} would replace it. Keep it, or change its format under Speakers in ${editSettings.narration}.`,
|
|
57
58
|
};
|
|
58
59
|
const sources = switchedOn(config, kind);
|
|
59
60
|
const next = {
|
|
@@ -78,7 +79,7 @@ export function planAddedOutput(config, kind) {
|
|
|
78
79
|
reused: ["Brief: title, prompts and keywords", ...kept.map((stage) => stageResults[stage])],
|
|
79
80
|
created: createdWords(kind, stages),
|
|
80
81
|
textUse: kind === "podcast"
|
|
81
|
-
? "Adapted: the
|
|
82
|
+
? "Adapted: the AI model rewrites your article as a conversation between two hosts for the narration. The article stays as written; the conversation is kept beside it as the script."
|
|
82
83
|
: kind === "images"
|
|
83
84
|
? "Your text is not changed."
|
|
84
85
|
: "Your text is used as written: nothing is rewritten.",
|
|
@@ -117,9 +117,9 @@ function newProblems(before, after) {
|
|
|
117
117
|
.map((one) => ({ field: one.field, message: settingsHint(one) }));
|
|
118
118
|
}
|
|
119
119
|
function settingsHint(problem) {
|
|
120
|
-
return /Settings|Edit
|
|
120
|
+
return /Settings|Edit settings/.test(problem.message)
|
|
121
121
|
? problem.message
|
|
122
|
-
: `${problem.message} Set it in
|
|
122
|
+
: `${problem.message} Set it in Edit settings, save, then add the output again.`;
|
|
123
123
|
}
|
|
124
124
|
function catalogueOf(deps) {
|
|
125
125
|
return (deps.catalogue?.read() ?? {
|