@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
|
@@ -2,6 +2,7 @@ import { zValidator } from "@hono/zod-validator";
|
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { HTTPException } from "hono/http-exception";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { setupSectionNames } from "../../slices/project-templates/setup-sections.js";
|
|
5
6
|
import { calendarRange } from "../../slices/schedules/agenda.js";
|
|
6
7
|
import { rejectHeldTopics, restoreRejectedTopics } from "../../slices/schedules/held-bulk.js";
|
|
7
8
|
import { preparedTopics, prepareTopic } from "../../slices/schedules/prepare.js";
|
|
@@ -40,14 +41,14 @@ function refused(c, result) {
|
|
|
40
41
|
conflict: "This schedule changed while you were editing, possibly because a run just started. Reload the page to see the latest, then make your change again.",
|
|
41
42
|
"cancel-required": "This schedule is still active. Cancel it first, then delete it. Completed schedules can be deleted straight away.",
|
|
42
43
|
"missing-template": "The template you picked was deleted or changed. Choose a template again, then save.",
|
|
43
|
-
"unsupported-media":
|
|
44
|
+
"unsupported-media": `This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, so it cannot run on a schedule: a scheduled run has no one to attach the file again. Pick a template that generates these instead, or open this one in Edit template, remove the music under ${setupSectionNames.shorts} (or pick Rain, Fireplace or Wind as the Ambient sound under ${setupSectionNames.video}) and save it again.`,
|
|
44
45
|
"not-due": "This one-off time has already passed. Choose a time in the future.",
|
|
45
46
|
"spend-limit": "The estimated cost is above this schedule's spend limit, or some prices are unknown. Raise the spend limit or choose models with known prices.",
|
|
46
|
-
readiness: "A provider this template needs is not ready, for example a missing API key, model or voice. Check Settings →
|
|
47
|
+
readiness: "A provider this template needs is not ready, for example a missing API key, model or voice. Check Settings → Keys & services, then try again.",
|
|
47
48
|
"queue-full": `A schedule's topic queue holds at most ${String(queueMax)} topics. Remove some under Schedules → Edit, then try again.`,
|
|
48
49
|
"topic-not-found": "This topic was already approved, turned down or used. Reload the page to see the topics waiting now.",
|
|
49
50
|
busy: "Slopify is already generating topics for this schedule. Wait for it to finish, then try again.",
|
|
50
|
-
"not-ready": "This schedule's template has no version
|
|
51
|
+
"not-ready": "This schedule's template has no version approved for schedules. Open it from Templates with Edit template, then choose More → Approve for schedules.",
|
|
51
52
|
"invalid-topics": "Some topics name keywords this template does not use. Check each topic under Schedules → Edit and save again.",
|
|
52
53
|
};
|
|
53
54
|
return refusal(c, result.reason, {
|
|
@@ -165,7 +166,8 @@ function routes(deps) {
|
|
|
165
166
|
})
|
|
166
167
|
// Which queued topics are prepared ahead, and their projects.
|
|
167
168
|
.get("/:id/prepared", zValidator("param", id, onInvalid), (c) => c.json({ prepared: preparedTopics(service(), c.req.valid("param").id) }))
|
|
168
|
-
//
|
|
169
|
+
// Make ahead one queued topic: everything but the video now, which its scheduled day puts
|
|
170
|
+
// together.
|
|
169
171
|
.post("/:id/topics/prepare", zValidator("param", id, onInvalid), zValidator("json", prepareTopicBody, onInvalid), async (c) => {
|
|
170
172
|
const result = await prepareTopic(service(), {
|
|
171
173
|
scheduleId: c.req.valid("param").id,
|
|
@@ -175,19 +177,19 @@ function routes(deps) {
|
|
|
175
177
|
return c.json({ projectId: result.projectId, title: result.title }, 201);
|
|
176
178
|
const detail = {
|
|
177
179
|
"not-found": "This topic is no longer in the schedule's queue. Reload the page to see the queue as it is now.",
|
|
178
|
-
"already-prepared": "This topic is already
|
|
179
|
-
paused: "Its schedule is paused, so it makes nothing, ahead or on its day. Resume it under Calendar → Schedules, then press
|
|
180
|
-
"missing-template": "This schedule's template was deleted, so there is nothing to
|
|
181
|
-
"spend-limit": "
|
|
182
|
-
design: "A
|
|
180
|
+
"already-prepared": "This topic is already made ahead: its project is in Projects under the same title.",
|
|
181
|
+
paused: "Its schedule is paused, so it makes nothing, ahead or on its day. Resume it under Calendar → Schedules, then press Make ahead again.",
|
|
182
|
+
"missing-template": "This schedule's template was deleted, so there is nothing to make it from. Choose another template under Edit.",
|
|
183
|
+
"spend-limit": "Making it ahead would go over this schedule's spend limit. Raise the limit under Edit, or make it from Create instead.",
|
|
184
|
+
design: "A poster schedule makes each poster on its day, so there is nothing to make ahead. To have one now, open its poster template (Templates → Poster templates) and press Make a poster; to have it sooner on a schedule, give its row an earlier date in the Schedule section.",
|
|
183
185
|
};
|
|
184
186
|
return problem(c, {
|
|
185
187
|
status: result.reason === "not-found" ? 404 : 409,
|
|
186
188
|
detail: (result.reason === "not-ready" ? result.detail : undefined) ??
|
|
187
189
|
detail[result.reason] ??
|
|
188
190
|
(result.detail === undefined
|
|
189
|
-
? `This topic couldn't be
|
|
190
|
-
: `This topic couldn't be
|
|
191
|
+
? `This topic couldn't be made ahead (${result.reason}). Open Create with the schedule's template to see what the setup needs, then try again.`
|
|
192
|
+
: `This topic couldn't be made ahead: ${result.detail} Fix it in the schedule's template (Templates → Edit template), then try again.`),
|
|
191
193
|
extensions: { reason: result.reason },
|
|
192
194
|
});
|
|
193
195
|
})
|
|
@@ -88,7 +88,11 @@ export function settingsRoutes(deps) {
|
|
|
88
88
|
return c.json({
|
|
89
89
|
nvidia: status.available
|
|
90
90
|
? { available: true }
|
|
91
|
-
: {
|
|
91
|
+
: {
|
|
92
|
+
available: false,
|
|
93
|
+
reason: status.reason,
|
|
94
|
+
...(status.detail === undefined ? {} : { detail: status.detail }),
|
|
95
|
+
},
|
|
92
96
|
});
|
|
93
97
|
})
|
|
94
98
|
// The named links `{{Name}}` placeholders in YouTube descriptions fill from
|
|
@@ -100,7 +104,7 @@ export function settingsRoutes(deps) {
|
|
|
100
104
|
if (refused !== undefined)
|
|
101
105
|
return problem(c, {
|
|
102
106
|
status: 400,
|
|
103
|
-
detail: `The channel links weren't saved: ${refused} Then press Save in Settings →
|
|
107
|
+
detail: `The channel links weren't saved: ${refused} Then press Save in Settings → YouTube Studio and posting.`,
|
|
104
108
|
});
|
|
105
109
|
writeChannelLinks(deps.db, links);
|
|
106
110
|
return c.json({ links: readChannelLinks(deps.db) });
|
package/dist/edge/http/social.js
CHANGED
|
@@ -5,6 +5,7 @@ import { projectById } from "../../slices/admission/repo.js";
|
|
|
5
5
|
import { webhookBody } from "../../slices/notifications/address.js";
|
|
6
6
|
import { sendFailureText } from "../../slices/notifications/send.js";
|
|
7
7
|
import { readNotificationUrl } from "../../slices/notifications/settings.js";
|
|
8
|
+
import { setupSectionNames } from "../../slices/project-templates/setup-sections.js";
|
|
8
9
|
import { waitsForRelease } from "../../slices/social/due.js";
|
|
9
10
|
import { postingExtension, writeExtensionSites } from "../../slices/social/extension.js";
|
|
10
11
|
import { platformLabels } from "../../slices/social/model.js";
|
|
@@ -50,7 +51,7 @@ export function socialRoutes(deps) {
|
|
|
50
51
|
: {
|
|
51
52
|
ok: false,
|
|
52
53
|
sentence: drawn?.message ??
|
|
53
|
-
"The design couldn't be drawn. Open it in
|
|
54
|
+
"The design couldn't be drawn. Open it in Designs to check it, then close Copy for YouTube Studio and open it again.",
|
|
54
55
|
};
|
|
55
56
|
},
|
|
56
57
|
}),
|
|
@@ -110,7 +111,7 @@ export function socialRoutes(deps) {
|
|
|
110
111
|
config?.text !== "prompt")
|
|
111
112
|
return problem(c, {
|
|
112
113
|
status: 409,
|
|
113
|
-
detail: `A post written by a post prompt is a paid step, so it is picked in the template, in
|
|
114
|
+
detail: `A post written by a post prompt is a paid step, so it is picked in the template, in Edit template → ${setupSectionNames.youtube}, where Save shows its cost. For this project, pick a free text source in Copy for YouTube Studio → ${platformLabels[platform]} → Settings.`,
|
|
114
115
|
});
|
|
115
116
|
writeChoice(deps.db, { projectId, platform }, choice, deps.clock.now());
|
|
116
117
|
changed(projectId);
|
|
@@ -204,7 +205,7 @@ export function socialRoutes(deps) {
|
|
|
204
205
|
if (next === undefined)
|
|
205
206
|
return problem(c, {
|
|
206
207
|
status: 404,
|
|
207
|
-
detail: "Nothing is waiting to be filled on this site. In Slopify, open the project, press
|
|
208
|
+
detail: "Nothing is waiting to be filled on this site. In Slopify, open the project, press Copy for YouTube Studio, then Fill in the site's post.",
|
|
208
209
|
});
|
|
209
210
|
changed(next.pack.projectId);
|
|
210
211
|
return c.json(next);
|
|
@@ -277,6 +278,6 @@ function unknownProject(c) {
|
|
|
277
278
|
function unknownPost(c) {
|
|
278
279
|
return problem(c, {
|
|
279
280
|
status: 404,
|
|
280
|
-
detail: "That project no longer posts to this site. In Slopify, open the project's
|
|
281
|
+
detail: "That project no longer posts to this site. In Slopify, open the project's Copy for YouTube Studio and press Fill again.",
|
|
281
282
|
});
|
|
282
283
|
}
|
|
@@ -47,7 +47,7 @@ export function stagingRoutes(deps) {
|
|
|
47
47
|
return problem(c, {
|
|
48
48
|
status: result.reason === "in-use" ? 409 : 404,
|
|
49
49
|
detail: result.reason === "in-use"
|
|
50
|
-
? "This file is still attached to a draft
|
|
50
|
+
? "This file is still attached to a draft on Create. Remove it from the draft first."
|
|
51
51
|
: "This uploaded file no longer exists; it may have been removed already. Reload the page.",
|
|
52
52
|
});
|
|
53
53
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { zValidator } from "@hono/zod-validator";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { projectExists } from "../../slices/admission/repo.js";
|
|
4
|
+
import { isSampleProject } from "../../slices/onboarding/sample.js";
|
|
4
5
|
import { BackupBusyError, planBackup, streamBackup, } from "../../slices/storage/backup-export.js";
|
|
5
6
|
import { BackupImportRefused, importBackup } from "../../slices/storage/backup-import.js";
|
|
6
7
|
import { importPortable, portableMaxArchiveBytes, storageUsage, } from "../../slices/storage/portable.js";
|
|
@@ -19,14 +20,21 @@ export function storageRoutes(deps) {
|
|
|
19
20
|
appVersion: deps.version,
|
|
20
21
|
hasInflight: deps.runner.hasInflight,
|
|
21
22
|
});
|
|
22
|
-
// A template's studio sample keeps its files for the studio's bake-offs and resamples
|
|
23
|
+
// A template's studio sample keeps its files for the studio's bake-offs and resamples, and a
|
|
24
|
+
// bundled sample is read-only.
|
|
23
25
|
const studioSample = (id) => {
|
|
26
|
+
if (isSampleProject(deps.db, id))
|
|
27
|
+
return {
|
|
28
|
+
status: 409,
|
|
29
|
+
detail: "This is a sample project, which is read-only and keeps all its files. To free its space, delete it in Projects; Restore samples in Settings → Backup & storage brings it back.",
|
|
30
|
+
extensions: { reason: "sample-read-only" },
|
|
31
|
+
};
|
|
24
32
|
const owner = sampleOwner(deps.db, id);
|
|
25
33
|
return owner === undefined
|
|
26
34
|
? undefined
|
|
27
35
|
: {
|
|
28
36
|
status: 409,
|
|
29
|
-
detail: `This is the
|
|
37
|
+
detail: `This is the sample project of the template ${owner.name}, kept with its files so Edit template can make its samples again without paying twice. Change it in Edit template instead: Templates → ${owner.name} → Edit template.`,
|
|
30
38
|
extensions: { reason: "template-sample" },
|
|
31
39
|
};
|
|
32
40
|
};
|
|
@@ -55,11 +63,11 @@ export function storageRoutes(deps) {
|
|
|
55
63
|
return result.reason === "no-project"
|
|
56
64
|
? problem(c, {
|
|
57
65
|
status: 404,
|
|
58
|
-
detail: "This project no longer exists. Reload Settings →
|
|
66
|
+
detail: "This project no longer exists. Reload Settings → Backup & storage to see the current list.",
|
|
59
67
|
})
|
|
60
68
|
: problem(c, {
|
|
61
69
|
status: 409,
|
|
62
|
-
detail: "Only a finished project can drop its working files, and this one is running, waiting or has unfinished steps. Let it finish (or cancel it on its project page), then drop the working files again from the project page or Settings →
|
|
70
|
+
detail: "Only a finished project can drop its working files, and this one is running, waiting or has unfinished steps. Let it finish (or cancel it on its project page), then drop the working files again from the project page or Settings → Backup & storage.",
|
|
63
71
|
});
|
|
64
72
|
})
|
|
65
73
|
// Delete old versions: one project's History files (`slices/storage/trim.ts`).
|
package/dist/edge/http/studio.js
CHANGED
|
@@ -8,6 +8,7 @@ import { transact } from "../../kernel/db/tx.js";
|
|
|
8
8
|
import { derive } from "../../kernel/runner/graph.js";
|
|
9
9
|
import { listProjects, projectById, stagesOf } from "../../slices/admission/repo.js";
|
|
10
10
|
import { channelById, projectChannels } from "../../slices/channels/repo.js";
|
|
11
|
+
import { isSampleProject } from "../../slices/onboarding/sample.js";
|
|
11
12
|
import { scheduleById, scheduleRunOfProject } from "../../slices/schedules/repo.js";
|
|
12
13
|
import { readSetting, writeSetting } from "../../slices/settings/repo.js";
|
|
13
14
|
import { findDownload } from "../../slices/storage/downloads.js";
|
|
@@ -130,6 +131,16 @@ export function studioRoutes(deps) {
|
|
|
130
131
|
});
|
|
131
132
|
};
|
|
132
133
|
const { allowOrigin, refused, allowed: extAllowed } = extensionAuth(deps.db);
|
|
134
|
+
// The bundled samples are read-only and never go to YouTube, so nothing here writes a
|
|
135
|
+
// release, a link, a pick or a fill for one, as the project routes refuse its writes
|
|
136
|
+
// (`app.ts`). Reading them stays open: the sample's page shows what a finished video offers.
|
|
137
|
+
const sampleRefused = (c, ...projectIds) => projectIds.some((one) => isSampleProject(deps.db, one))
|
|
138
|
+
? problem(c, {
|
|
139
|
+
status: 409,
|
|
140
|
+
detail: "This is a sample project, which is read-only: it isn't on YouTube and can't be. Press Make my own copy at the top of its page to post your own.",
|
|
141
|
+
extensions: { reason: "sample-read-only" },
|
|
142
|
+
})
|
|
143
|
+
: undefined;
|
|
133
144
|
// A project's place in the plan: its series, and the schedule and run day that made it.
|
|
134
145
|
const whoOf = (projectId, series) => {
|
|
135
146
|
const run = scheduleRunOfProject(deps.db, projectId);
|
|
@@ -145,7 +156,10 @@ export function studioRoutes(deps) {
|
|
|
145
156
|
// A project ready to upload gets its release times from the posting plan when its upload is
|
|
146
157
|
// prepared (`releases.ts`).
|
|
147
158
|
// A long video already on YouTube keeps when it went up; its shorts still get times.
|
|
159
|
+
// A sample never gets a time: it isn't going to YouTube.
|
|
148
160
|
const planned = (projectId) => {
|
|
161
|
+
if (isSampleProject(deps.db, projectId))
|
|
162
|
+
return;
|
|
149
163
|
const long = videoOf(deps.db, projectId, null);
|
|
150
164
|
const out = long?.uploadState === "done" ? long.recordedAt : undefined;
|
|
151
165
|
const result = uploadPack(deps, projectId);
|
|
@@ -167,12 +181,13 @@ export function studioRoutes(deps) {
|
|
|
167
181
|
.toSorted(),
|
|
168
182
|
});
|
|
169
183
|
const slotChoices = (projectId, series) => freeSlots(deps.db, readPlan(deps.db), whoOf(projectId, series), deps.clock.now(), 9);
|
|
170
|
-
// Finished projects not marked uploaded: the popup's list and the calendar's candidates.
|
|
184
|
+
// Finished projects not marked uploaded: the popup's list and the calendar's candidates. The
|
|
185
|
+
// samples are never among them.
|
|
171
186
|
const finishedProjects = () => {
|
|
172
187
|
const uploads = uploadedProjects(deps.db);
|
|
173
188
|
const aside = setAsideProjects(deps.db);
|
|
174
189
|
return listProjects(deps.db).filter((project) => {
|
|
175
|
-
if (uploads.has(project.id))
|
|
190
|
+
if (uploads.has(project.id) || isSampleProject(deps.db, project.id))
|
|
176
191
|
return false;
|
|
177
192
|
const state = derive(stagesOf(deps.db, project.id), project.paused === true);
|
|
178
193
|
return (state === "done" || state === "partial" || (state === "pending" && aside.has(project.id)));
|
|
@@ -246,6 +261,9 @@ export function studioRoutes(deps) {
|
|
|
246
261
|
return unknownProject(c);
|
|
247
262
|
const leadMs = readLeadHours(deps.db) * 3_600_000;
|
|
248
263
|
const releases = releasesOf(deps.db, projectId);
|
|
264
|
+
// The long video is on YouTube once its link is known or the project is marked
|
|
265
|
+
// uploaded (one fact, `videos.ts`); a short only by its own link.
|
|
266
|
+
const uploaded = uploadedProjects(deps.db).has(projectId);
|
|
249
267
|
return c.json({
|
|
250
268
|
leadHours: readLeadHours(deps.db),
|
|
251
269
|
// The zone its times are read in: its line's schedule's, else the plan's.
|
|
@@ -260,7 +278,8 @@ export function studioRoutes(deps) {
|
|
|
260
278
|
title: item.title,
|
|
261
279
|
at: at === "" ? null : at,
|
|
262
280
|
uploadBy: at === "" ? null : new Date(Date.parse(at) - leadMs).toISOString(),
|
|
263
|
-
onYoutube: videoOf(deps.db, projectId, item.short ?? null)?.uploadState === "done"
|
|
281
|
+
onYoutube: videoOf(deps.db, projectId, item.short ?? null)?.uploadState === "done" ||
|
|
282
|
+
(item.kind === "video" && uploaded),
|
|
264
283
|
};
|
|
265
284
|
}),
|
|
266
285
|
free: slotChoices(projectId, pack.pack.series),
|
|
@@ -273,6 +292,9 @@ export function studioRoutes(deps) {
|
|
|
273
292
|
return denied;
|
|
274
293
|
const { projectId } = c.req.valid("param");
|
|
275
294
|
const other = c.req.valid("json").with;
|
|
295
|
+
const sample = sampleRefused(c, projectId, other);
|
|
296
|
+
if (sample !== undefined)
|
|
297
|
+
return sample;
|
|
276
298
|
const swapped = transact(deps.db, () => swapReleases(deps.db, projectId, other, deps.clock.now()));
|
|
277
299
|
if (!swapped)
|
|
278
300
|
return problem(c, {
|
|
@@ -296,6 +318,9 @@ export function studioRoutes(deps) {
|
|
|
296
318
|
if (denied !== undefined)
|
|
297
319
|
return denied;
|
|
298
320
|
const { projectId } = c.req.valid("param");
|
|
321
|
+
const sample = sampleRefused(c, projectId);
|
|
322
|
+
if (sample !== undefined)
|
|
323
|
+
return sample;
|
|
299
324
|
if (uploadPack(deps, projectId).ok === false)
|
|
300
325
|
return unknownProject(c);
|
|
301
326
|
const { short, at, line } = c.req.valid("json");
|
|
@@ -311,7 +336,7 @@ export function studioRoutes(deps) {
|
|
|
311
336
|
if (channelId !== undefined && channelById(deps.db, channelId) === undefined)
|
|
312
337
|
return problem(c, {
|
|
313
338
|
status: 404,
|
|
314
|
-
detail: "The playlists weren't saved: that channel no longer exists. Reload Settings → YouTube Studio and pick the channel again.",
|
|
339
|
+
detail: "The playlists weren't saved: that channel no longer exists. Reload Settings → YouTube Studio and posting and pick the channel again.",
|
|
315
340
|
});
|
|
316
341
|
const invalid = studioPlaylistsProblem(raw);
|
|
317
342
|
if (invalid !== undefined)
|
|
@@ -339,12 +364,15 @@ export function studioRoutes(deps) {
|
|
|
339
364
|
return unknownProject(c);
|
|
340
365
|
return c.json({ ...result.pack, slotChoices: slotChoices(projectId, result.pack.series) });
|
|
341
366
|
})
|
|
342
|
-
//
|
|
367
|
+
// Copy for YouTube Studio's Release picker: another free time of the plan, or none.
|
|
343
368
|
.put("/packs/:projectId/slot", zValidator("param", projectParam, onInvalid), zValidator("json", slotBody, onInvalid), (c) => {
|
|
344
369
|
const denied = samePage(c);
|
|
345
370
|
if (denied !== undefined)
|
|
346
371
|
return denied;
|
|
347
372
|
const { projectId } = c.req.valid("param");
|
|
373
|
+
const sample = sampleRefused(c, projectId);
|
|
374
|
+
if (sample !== undefined)
|
|
375
|
+
return sample;
|
|
348
376
|
const wanted = c.req.valid("json").slot;
|
|
349
377
|
const before = uploadPack(deps, projectId);
|
|
350
378
|
if (!before.ok)
|
|
@@ -356,7 +384,7 @@ export function studioRoutes(deps) {
|
|
|
356
384
|
if (!free)
|
|
357
385
|
return problem(c, {
|
|
358
386
|
status: 409,
|
|
359
|
-
detail: "That
|
|
387
|
+
detail: "That time is taken or past. Close Copy for YouTube Studio, open it again and choose one of the times it lists.",
|
|
360
388
|
});
|
|
361
389
|
setRelease(deps.db, projectId, 0, wanted.longAt, wanted.row, deps.clock.now());
|
|
362
390
|
}
|
|
@@ -366,37 +394,47 @@ export function studioRoutes(deps) {
|
|
|
366
394
|
? c.json({ ...result.pack, slotChoices: slotChoices(projectId, result.pack.series) })
|
|
367
395
|
: unknownProject(c);
|
|
368
396
|
})
|
|
369
|
-
//
|
|
397
|
+
// Copy for YouTube Studio's AI use tick: the project's uploaded clips are real footage.
|
|
370
398
|
.put("/packs/:projectId/real-footage", zValidator("param", projectParam, onInvalid), zValidator("json", realFootageBody, onInvalid), (c) => {
|
|
371
399
|
const denied = samePage(c);
|
|
372
400
|
if (denied !== undefined)
|
|
373
401
|
return denied;
|
|
374
402
|
const { projectId } = c.req.valid("param");
|
|
403
|
+
const sample = sampleRefused(c, projectId);
|
|
404
|
+
if (sample !== undefined)
|
|
405
|
+
return sample;
|
|
375
406
|
if (!uploadPack(deps, projectId).ok)
|
|
376
407
|
return unknownProject(c);
|
|
377
408
|
saveRealFootage(deps.db, projectId, c.req.valid("json").realFootage);
|
|
378
409
|
const result = uploadPack(deps, projectId);
|
|
379
410
|
return result.ok ? c.json(result.pack) : unknownProject(c);
|
|
380
411
|
})
|
|
381
|
-
//
|
|
412
|
+
// Copy for YouTube Studio's pick: which title and thumbnail the upload carries; the A/B test tries
|
|
382
413
|
// the others once the video is public.
|
|
383
414
|
.put("/packs/:projectId/pick", zValidator("param", projectParam, onInvalid), zValidator("json", pickBody, onInvalid), (c) => {
|
|
384
415
|
const denied = samePage(c);
|
|
385
416
|
if (denied !== undefined)
|
|
386
417
|
return denied;
|
|
387
418
|
const { projectId } = c.req.valid("param");
|
|
419
|
+
const sample = sampleRefused(c, projectId);
|
|
420
|
+
if (sample !== undefined)
|
|
421
|
+
return sample;
|
|
388
422
|
if (!uploadPack(deps, projectId).ok)
|
|
389
423
|
return unknownProject(c);
|
|
390
424
|
writeUploadPick(deps.db, projectId, c.req.valid("json"));
|
|
391
425
|
const result = uploadPack(deps, projectId);
|
|
392
426
|
return result.ok ? c.json(result.pack) : unknownProject(c);
|
|
393
427
|
})
|
|
394
|
-
//
|
|
428
|
+
// Copy for YouTube Studio's playlist ticks: which of the channel's playlists this project
|
|
429
|
+
// goes into.
|
|
395
430
|
.put("/packs/:projectId/playlists", zValidator("param", projectParam, onInvalid), zValidator("json", projectPlaylistsBody, onInvalid), (c) => {
|
|
396
431
|
const denied = samePage(c);
|
|
397
432
|
if (denied !== undefined)
|
|
398
433
|
return denied;
|
|
399
434
|
const { projectId } = c.req.valid("param");
|
|
435
|
+
const sample = sampleRefused(c, projectId);
|
|
436
|
+
if (sample !== undefined)
|
|
437
|
+
return sample;
|
|
400
438
|
if (!uploadPack(deps, projectId).ok)
|
|
401
439
|
return unknownProject(c);
|
|
402
440
|
saveProjectPlaylists(deps.db, projectId, c.req.valid("json").playlists);
|
|
@@ -408,6 +446,9 @@ export function studioRoutes(deps) {
|
|
|
408
446
|
if (denied !== undefined)
|
|
409
447
|
return denied;
|
|
410
448
|
const { projectId } = c.req.valid("param");
|
|
449
|
+
const sample = sampleRefused(c, projectId);
|
|
450
|
+
if (sample !== undefined)
|
|
451
|
+
return sample;
|
|
411
452
|
const { short } = c.req.valid("json");
|
|
412
453
|
const result = uploadPack(deps, projectId);
|
|
413
454
|
if (!result.ok)
|
|
@@ -415,7 +456,7 @@ export function studioRoutes(deps) {
|
|
|
415
456
|
if (packItem(result.pack, short) === undefined)
|
|
416
457
|
return problem(c, {
|
|
417
458
|
status: 404,
|
|
418
|
-
detail: `This project has no short ${String(short)}. Reload the project page and press
|
|
459
|
+
detail: `This project has no short ${String(short)}. Reload the project page and press Copy for YouTube Studio again.`,
|
|
419
460
|
});
|
|
420
461
|
const queue = enqueueFill(deps.db, projectId, short ?? null, deps.clock.now());
|
|
421
462
|
return c.json({ chosen: { projectId, short: short ?? null }, queue: queueView(queue) });
|
|
@@ -426,15 +467,19 @@ export function studioRoutes(deps) {
|
|
|
426
467
|
// The YouTube videos a project's uploads became, and their A/B tests.
|
|
427
468
|
.get("/videos/:projectId", zValidator("param", projectParam, onInvalid), (c) => c.json({ videos: projectVideos(deps.db, c.req.valid("param").projectId) }))
|
|
428
469
|
// A video's link pasted in Slopify, for an upload made without the extension; an empty
|
|
429
|
-
// link forgets it.
|
|
470
|
+
// link forgets it. The long video's link marks the project uploaded, and forgetting it
|
|
471
|
+
// takes the mark off (`videos.ts`).
|
|
430
472
|
.put("/videos/:projectId", zValidator("param", projectParam, onInvalid), zValidator("json", videoLinkBody, onInvalid), (c) => {
|
|
431
473
|
const wrong = samePage(c);
|
|
432
474
|
if (wrong !== undefined)
|
|
433
475
|
return wrong;
|
|
434
476
|
const { projectId } = c.req.valid("param");
|
|
477
|
+
const sample = sampleRefused(c, projectId);
|
|
478
|
+
if (sample !== undefined)
|
|
479
|
+
return sample;
|
|
435
480
|
const { short, link } = c.req.valid("json");
|
|
436
481
|
if (link.trim() === "") {
|
|
437
|
-
forgetVideo(deps.db, projectId, short ?? null);
|
|
482
|
+
forgetVideo(deps.db, projectId, short ?? null, deps.clock.now().toISOString());
|
|
438
483
|
return c.json({ videos: projectVideos(deps.db, projectId) });
|
|
439
484
|
}
|
|
440
485
|
const videoId = videoIdOf(link);
|
|
@@ -461,7 +506,7 @@ export function studioRoutes(deps) {
|
|
|
461
506
|
queue: queueView(removeFill(deps.db, projectId, short ?? null, deps.clock.now())),
|
|
462
507
|
});
|
|
463
508
|
})
|
|
464
|
-
// The extension's own zips, for Settings' and
|
|
509
|
+
// The extension's own zips, for Settings' and Copy for YouTube Studio's Download.
|
|
465
510
|
.get("/extension/:file", zValidator("param", extensionParam, onInvalid), (c) => {
|
|
466
511
|
const { file } = c.req.valid("param");
|
|
467
512
|
const name = extensionFiles[file] ?? "";
|
|
@@ -496,7 +541,7 @@ export function studioRoutes(deps) {
|
|
|
496
541
|
? refused(c)
|
|
497
542
|
: problem(c, {
|
|
498
543
|
status: 403,
|
|
499
|
-
detail: "Only the Slopify Studio browser extension can pair with Slopify. Install it (see Settings → YouTube Studio) and pair from its options page.",
|
|
544
|
+
detail: "Only the Slopify Studio browser extension can pair with Slopify. Install it (see Settings → YouTube Studio and posting) and pair from its options page.",
|
|
500
545
|
});
|
|
501
546
|
// The origin is paired now; the response carries its headers.
|
|
502
547
|
allowOrigin(c, false);
|
|
@@ -523,8 +568,8 @@ export function studioRoutes(deps) {
|
|
|
523
568
|
return problem(c, {
|
|
524
569
|
status: 404,
|
|
525
570
|
detail: gone > 0
|
|
526
|
-
? "The project chosen to fill in is gone. In Slopify, open the project again, press
|
|
527
|
-
: "Nothing is waiting to be filled in. In Slopify, open the project, press
|
|
571
|
+
? "The project chosen to fill in is gone. In Slopify, open the project again, press Copy for YouTube Studio, then Fill in YouTube Studio."
|
|
572
|
+
: "Nothing is waiting to be filled in. In Slopify, open the project, press Copy for YouTube Studio, then Fill in YouTube Studio.",
|
|
528
573
|
});
|
|
529
574
|
})
|
|
530
575
|
// The extension filled an item: it leaves the queue, so the next upload dialog takes the
|
|
@@ -595,6 +640,9 @@ export function studioRoutes(deps) {
|
|
|
595
640
|
if (!extAllowed(c))
|
|
596
641
|
return refused(c);
|
|
597
642
|
const { projectId, short } = c.req.valid("json");
|
|
643
|
+
const sample = sampleRefused(c, projectId);
|
|
644
|
+
if (sample !== undefined)
|
|
645
|
+
return sample;
|
|
598
646
|
const result = uploadPack(deps, projectId);
|
|
599
647
|
if (!result.ok || packItem(result.pack, short ?? undefined) === undefined)
|
|
600
648
|
return problem(c, {
|
|
@@ -612,6 +660,9 @@ export function studioRoutes(deps) {
|
|
|
612
660
|
if (!extAllowed(c))
|
|
613
661
|
return refused(c);
|
|
614
662
|
const { projectId } = c.req.valid("json");
|
|
663
|
+
const sample = sampleRefused(c, projectId);
|
|
664
|
+
if (sample !== undefined)
|
|
665
|
+
return sample;
|
|
615
666
|
planned(projectId);
|
|
616
667
|
const result = uploadPack(deps, projectId);
|
|
617
668
|
if (!result.ok)
|
|
@@ -715,7 +766,7 @@ export function studioRoutes(deps) {
|
|
|
715
766
|
for (const { projectId, short, videoId } of body.videos) {
|
|
716
767
|
if (videoOf(deps.db, projectId, short ?? null)?.videoId !== videoId)
|
|
717
768
|
continue;
|
|
718
|
-
forgetVideo(deps.db, projectId, short ?? null);
|
|
769
|
+
forgetVideo(deps.db, projectId, short ?? null, deps.clock.now().toISOString());
|
|
719
770
|
deps.hub.emit(projectId, { type: "project.updated", projectId });
|
|
720
771
|
forgotten++;
|
|
721
772
|
}
|
|
@@ -798,8 +849,9 @@ export function studioRoutes(deps) {
|
|
|
798
849
|
// (a short's related video, the long video's end screen and captions, unless they went in
|
|
799
850
|
// during the upload) wait for the extension, the pinned comment for the video to be public.
|
|
800
851
|
function confirmVideoUpload(deps, projectId, short, videoId, captionsAdded = false) {
|
|
801
|
-
|
|
802
|
-
|
|
852
|
+
const at = deps.clock.now().toISOString();
|
|
853
|
+
if (!confirmUpload(deps.db, projectId, short, videoId, at))
|
|
854
|
+
recordVideo(deps.db, projectId, short, videoId, at);
|
|
803
855
|
const confirmedPack = uploadPack(deps, projectId);
|
|
804
856
|
const confirmedItem = confirmedPack.ok
|
|
805
857
|
? packItem(confirmedPack.pack, short ?? undefined)
|
|
@@ -41,7 +41,7 @@ export function stylePreviewRoutes(deps) {
|
|
|
41
41
|
if (saved === undefined)
|
|
42
42
|
return problem(c, {
|
|
43
43
|
status: 404,
|
|
44
|
-
detail: "This style preview is no longer saved; Slopify keeps only the most recent ones. Press
|
|
44
|
+
detail: "This style preview is no longer saved; Slopify keeps only the most recent ones. Press Make the preview again to make it.",
|
|
45
45
|
});
|
|
46
46
|
return fileResponse(c, saved.path, saved.bytes, {
|
|
47
47
|
"content-type": "video/mp4",
|
|
@@ -53,6 +53,6 @@ export function stylePreviewRoutes(deps) {
|
|
|
53
53
|
function unavailable(c) {
|
|
54
54
|
return problem(c, {
|
|
55
55
|
status: 503,
|
|
56
|
-
detail: "The style preview isn't available because this Slopify started without its video renderer. Restart Slopify, then press
|
|
56
|
+
detail: "The style preview isn't available because this Slopify started without its video renderer. Restart Slopify, then press Make the preview again.",
|
|
57
57
|
});
|
|
58
58
|
}
|
|
@@ -10,6 +10,7 @@ import { providerStatuses } from "../../slices/settings/readiness.js";
|
|
|
10
10
|
import { getAnatomy } from "../../slices/template-studio/anatomy-view.js";
|
|
11
11
|
import { bakeoffQuoteSchema, bakeoffStartSchema, bakeoffView, discardBakeoff, quoteBakeoff, startBakeoff, } from "../../slices/template-studio/bakeoff.js";
|
|
12
12
|
import { budgetRoom, readStudioSettings, studioSettingsSchema, systemTimeZone, writeStudioSettings, } from "../../slices/template-studio/budget.js";
|
|
13
|
+
import { templateCards } from "../../slices/template-studio/cards.js";
|
|
13
14
|
import { makeSamplesForAll, quoteSamplesForAll, samplesForAllSchema, } from "../../slices/template-studio/for-all.js";
|
|
14
15
|
import { createFreeSamples } from "../../slices/template-studio/free.js";
|
|
15
16
|
import { enqueueJob } from "../../slices/template-studio/jobs.js";
|
|
@@ -46,7 +47,7 @@ const quoteBody = z
|
|
|
46
47
|
})
|
|
47
48
|
.strict()
|
|
48
49
|
.refine((body) => [body.document, body.topic, body.fullScript].filter((one) => one !== undefined).length === 1, { message: "Send one of document, topic or fullScript." });
|
|
49
|
-
const unpriced = "This Slopify can't price or make paid samples because
|
|
50
|
+
const unpriced = "This Slopify can't price or make paid samples because Create's review isn't available here. Restart Slopify; if it happens again, use Download diagnostics in Settings and report it.";
|
|
50
51
|
// A refusal that carries the new quote (a higher price, unknown prices, past the budget), so the
|
|
51
52
|
// page can show it on the button at once.
|
|
52
53
|
function refusedWithQuote(c, result) {
|
|
@@ -60,7 +61,7 @@ function refusedWithQuote(c, result) {
|
|
|
60
61
|
},
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
|
-
const missingTemplate = "This template no longer exists; it may have been deleted. Go to
|
|
64
|
+
const missingTemplate = "This template no longer exists; it may have been deleted. Go to Templates to see your templates.";
|
|
64
65
|
function service(deps) {
|
|
65
66
|
return (deps.templateStudio ?? {
|
|
66
67
|
free: createFreeSamples({
|
|
@@ -110,13 +111,15 @@ export function templateStudioRoutes(deps) {
|
|
|
110
111
|
"x-content-type-options": "nosniff",
|
|
111
112
|
});
|
|
112
113
|
return (new Hono()
|
|
113
|
-
//
|
|
114
|
+
// Templates: what each template makes, its still and its facts, for picking one.
|
|
115
|
+
.get("/cards", (c) => c.json({ cards: templateCards({ db: deps.db, paths: deps.paths, log: deps.log }) }))
|
|
116
|
+
// Settings → Production defaults: the samples' daily budgets, and what was spent today.
|
|
114
117
|
.get("/settings", (c) => c.json(settingsView()))
|
|
115
118
|
.put("/settings", zValidator("json", studioSettingsSchema, onInvalid), (c) => {
|
|
116
119
|
writeStudioSettings(deps.db, c.req.valid("json"));
|
|
117
120
|
return c.json(settingsView());
|
|
118
121
|
})
|
|
119
|
-
// Settings →
|
|
122
|
+
// Settings → Production defaults → Make samples for all: the total first, then each sample
|
|
120
123
|
// that fits today's budget queued at its price, the rest waiting on the budget.
|
|
121
124
|
.post("/samples-for-all/quote", async (c) => {
|
|
122
125
|
if (review === undefined)
|
|
@@ -157,12 +160,12 @@ export function templateStudioRoutes(deps) {
|
|
|
157
160
|
return problem(c, {
|
|
158
161
|
status: 413,
|
|
159
162
|
title: "Content Too Large",
|
|
160
|
-
detail: `This
|
|
163
|
+
detail: `This template file is larger than ${String(packLimits.uploadBytes / 1024 / 1024)} MB, more than an exported template ever is. Export it again from the template's Edit template page, or ask whoever made it to.`,
|
|
161
164
|
});
|
|
162
165
|
if (bytes === undefined || bytes.byteLength === 0)
|
|
163
166
|
return problem(c, {
|
|
164
167
|
status: 400,
|
|
165
|
-
detail: "No
|
|
168
|
+
detail: "No file arrived with the upload. Choose the template's .zip file again.",
|
|
166
169
|
});
|
|
167
170
|
const result = importPack(review, new Uint8Array(bytes));
|
|
168
171
|
if (!result.ok)
|
|
@@ -330,7 +333,7 @@ export function templateStudioRoutes(deps) {
|
|
|
330
333
|
const template = templateById(deps.db, id);
|
|
331
334
|
if (template === undefined)
|
|
332
335
|
return gone(c, missingTemplate);
|
|
333
|
-
const notMade = "This loudness sample isn't made yet. Open
|
|
336
|
+
const notMade = "This loudness sample isn't made yet. Open Edit template and press Make sample.";
|
|
334
337
|
if (file === "before.mp3") {
|
|
335
338
|
const source = studio.loudnessSource;
|
|
336
339
|
const size = source === undefined ? undefined : statSync(source, { throwIfNoEntry: false });
|
|
@@ -422,12 +425,12 @@ export function templateStudioRoutes(deps) {
|
|
|
422
425
|
}), onInvalid), (c) => {
|
|
423
426
|
const { id, file } = c.req.valid("param");
|
|
424
427
|
const found = packSampleFileOf(deps.paths, id, file);
|
|
425
|
-
return media(c, found, found?.type ?? "application/octet-stream", "This template has no such sample from
|
|
428
|
+
return media(c, found, found?.type ?? "application/octet-stream", "This template has no such sample from the file it came in. Open Edit template to see the samples it has.");
|
|
426
429
|
})
|
|
427
430
|
.get("/:id/document.pdf", zValidator("param", param, onInvalid), (c) => {
|
|
428
431
|
const template = templateById(deps.db, c.req.valid("param").id);
|
|
429
432
|
if (template === undefined)
|
|
430
433
|
return gone(c, missingTemplate);
|
|
431
|
-
return media(c, studio.documentFile?.(studio.free.documentHashOf(template.document)), "application/pdf", "This PDF sample isn't made yet. Open
|
|
434
|
+
return media(c, studio.documentFile?.(studio.free.documentHashOf(template.document)), "application/pdf", "This PDF sample isn't made yet. Open Edit template and press Make sample.");
|
|
432
435
|
}));
|
|
433
436
|
}
|
package/dist/edge/http/trash.js
CHANGED
|
@@ -32,7 +32,7 @@ const status = {
|
|
|
32
32
|
"template-gone": 409,
|
|
33
33
|
};
|
|
34
34
|
const details = {
|
|
35
|
-
"not-found": "This item is no longer in the trash: it was restored or removed for good.
|
|
35
|
+
"not-found": "This item is no longer in the trash: it was restored or removed for good.",
|
|
36
36
|
running: "This project is still running. Use Cancel run on the project page first, then try again.",
|
|
37
37
|
files: "Some of this project's files could not be removed. Close any program using files in the project folder, then press Delete now again.",
|
|
38
38
|
"template-in-trash": "This schedule's template is in the trash too. Restore the template first (it is listed here), then restore the schedule.",
|
|
@@ -79,7 +79,7 @@ export function youtubeEditRoutes(deps) {
|
|
|
79
79
|
if (refused !== undefined)
|
|
80
80
|
return problem(c, {
|
|
81
81
|
status: 400,
|
|
82
|
-
detail: `This project's links weren't saved: ${refused} Then press Save
|
|
82
|
+
detail: `This project's links weren't saved: ${refused} Then press Save beside Previous episode, under YouTube links in the project's YouTube section.`,
|
|
83
83
|
});
|
|
84
84
|
const saved = transact(deps.db, () => {
|
|
85
85
|
const next = { ...readDescriptionEdits(deps.db, id), links };
|
|
@@ -10,11 +10,11 @@ const listening = "sound and pictures for the person watching the page";
|
|
|
10
10
|
const personal = "the person's own tutorial, notices and reading state";
|
|
11
11
|
const retired = "retired: answers revision-required; edits go through save_revision";
|
|
12
12
|
const extension = "only the paired Slopify Studio extension calls these";
|
|
13
|
-
const youtube = "acts on YouTube: agents stop at Ready to upload, and uploading is the person's (the extension's queue is filled only from
|
|
13
|
+
const youtube = "acts on YouTube: agents stop at Ready to upload, and uploading is the person's (the extension's queue is filled only from Copy for YouTube Studio)";
|
|
14
14
|
const posting = "acts on X, LinkedIn, Instagram, TikTok, Reddit or Bluesky: agents stop at Ready to post, and filling a site's form and posting are the person's";
|
|
15
15
|
const cleanup = "every project's cleanup is the person's: Delete all old versions and Clean up everything are in Settings → Backup & storage";
|
|
16
16
|
const assistantChat = "the in-app assistant's chat is the person's own: its asks are answered only by a press in the page, never by an agent";
|
|
17
|
-
const notStarted = "not a page action: projects start from
|
|
17
|
+
const notStarted = "not a page action: projects start from drafts on Create (start_draft) after their review and estimate";
|
|
18
18
|
export const excludedRoutes = {
|
|
19
19
|
"GET /api/assistant": assistantChat,
|
|
20
20
|
"POST /api/assistant/sessions": assistantChat,
|
|
@@ -48,6 +48,7 @@ export const excludedRoutes = {
|
|
|
48
48
|
"GET /api/projects/:projectId/audio-preview/:previewId": listening,
|
|
49
49
|
"GET /api/projects/:projectId/narration/peaks": listening,
|
|
50
50
|
"POST /api/auditions": `${listening}; its price is quote_voice_sample`,
|
|
51
|
+
"GET /api/template-studio/cards": "the Templates page's cards, a still and one line of facts per template for picking by eye; agents list templates with list_templates and read one with get_template_studio",
|
|
51
52
|
"GET /api/template-studio/:id/loudness/:file": `${listening}; get_template_studio gives both levels`,
|
|
52
53
|
"GET /api/template-studio/:id/document.pdf": `${listening}; get_template_studio says whether it is made`,
|
|
53
54
|
"GET /api/template-studio/:id/pack": "a file download to share, like a project's files: agents read the template with get_template_studio and get_template",
|
|
@@ -76,8 +77,8 @@ export const excludedRoutes = {
|
|
|
76
77
|
"GET /api/tutorials/search": personal,
|
|
77
78
|
"GET /api/tutorials/:page": personal,
|
|
78
79
|
"GET /api/providers/first-run": personal,
|
|
79
|
-
"POST /api/providers/first-run/dismiss": personal,
|
|
80
80
|
"POST /api/onboarding/dismiss": personal,
|
|
81
|
+
"POST /api/onboarding/check": personal,
|
|
81
82
|
"GET /api/settings/autostart": hostSetup,
|
|
82
83
|
"PUT /api/settings/autostart": hostSetup,
|
|
83
84
|
"PUT /api/settings/remote-access": remoteAccess,
|