@gentbajko/slopify 2.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +3 -0
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +44 -5
- package/dist/adapters/host-cli/transport.js +7 -5
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +1 -1
- package/dist/adapters/image/codex.js +87 -22
- package/dist/adapters/image/fal.js +13 -5
- package/dist/adapters/image/google.js +11 -8
- package/dist/adapters/image/openai.js +14 -7
- package/dist/adapters/image/reference.js +34 -0
- package/dist/adapters/image/replicate.js +7 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +12 -7
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +13 -13
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +58 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +29 -5
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +84 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +9 -1
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +122 -14
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +17 -8
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +142 -7
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +272 -16
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +79 -1
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +41 -5
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/folder.js +14 -6
- package/dist/slices/batch/index.js +7 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +109 -28
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +7 -1
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +21 -3
- package/dist/slices/notifications/rules.js +22 -1
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +125 -6
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +2 -0
- package/dist/slices/play-drafts/review-inputs.js +20 -3
- package/dist/slices/play-drafts/schema.js +89 -0
- package/dist/slices/play-drafts/service.js +4 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/project-templates/from-project.js +50 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +18 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +46 -3
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +8 -4
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +35 -0
- package/dist/slices/rebuild/recipe-legacy.js +9 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +15 -1
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +23 -3
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +14 -2
- package/dist/slices/rebuild/recipe-visual.js +96 -33
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +5 -0
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +21 -2
- package/dist/slices/rebuild/runtime-local.js +148 -24
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +27 -0
- package/dist/slices/rebuild/runtime-provider.js +126 -18
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +44 -17
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +9 -2
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +5 -2
- package/dist/slices/revisions/adopt.js +3 -1
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +10 -0
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +11 -1
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +43 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +6 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +14 -4
- package/dist/slices/storage/model.js +11 -0
- package/dist/slices/storage/portable.js +92 -12
- package/dist/slices/storage/reconcile.js +14 -1
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-BLF2i8c2.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-CktpxlJd.js +0 -171
- package/dist/web/assets/index-DB-vOnGU.css +0 -1
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import { referenceKey, thumbnailVariant } from "../admission/model.js";
|
|
2
3
|
import { plainText } from "../article/plain.js";
|
|
3
4
|
import { splitEndMatter } from "../article/split.js";
|
|
5
|
+
import { spokenPassage } from "../narration/describe.js";
|
|
4
6
|
import { observeNarration } from "../narration/live.js";
|
|
5
7
|
import { validatePreparation } from "../narration/preparation.js";
|
|
6
8
|
import { prepareRequests } from "../narration/steering.js";
|
|
7
9
|
import { chaptersFrom } from "../research/planner.js";
|
|
8
10
|
import { sourcedAnswer } from "../research/synthesis.js";
|
|
11
|
+
import { imageReviewers } from "../reviews/model.js";
|
|
9
12
|
import { discardPreparedAssets, writeAsset } from "../storage/assets.js";
|
|
10
13
|
import { outputPath } from "../storage/layout.js";
|
|
11
14
|
import { probeDurationMs } from "../video/ffmpeg.js";
|
|
15
|
+
import { parseAttribution } from "../voices/attribution.js";
|
|
16
|
+
import { parseScript } from "../voices/script.js";
|
|
12
17
|
import { executeArticleRequests } from "./runtime-article.js";
|
|
13
18
|
import { imageCall } from "./runtime-image.js";
|
|
14
19
|
import { frozenInstructions } from "./runtime-instructions.js";
|
|
20
|
+
import { articlePicture } from "./runtime-pictures.js";
|
|
15
21
|
import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
|
|
16
22
|
import { preparedResult, preparedTexts, publishResult } from "./runtime-publication.js";
|
|
23
|
+
import { clearSoftening, softenedPrompt, softeningRequested, softenMessages } from "./soften.js";
|
|
17
24
|
export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
18
25
|
if (deps.db
|
|
19
26
|
.prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
|
|
@@ -22,6 +29,8 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
22
29
|
const input = piece.input;
|
|
23
30
|
const wrapped = providers.forPiece(piece.id);
|
|
24
31
|
if (input.kind === "llm") {
|
|
32
|
+
// A described figure's own picture, when the project has it and the model can look.
|
|
33
|
+
const picture = describedPicture(deps, context, input);
|
|
25
34
|
const answer = piece.key === "article:body"
|
|
26
35
|
? await executeArticleRequests(deps, context, providers, piece)
|
|
27
36
|
: await wrapped.llm({
|
|
@@ -29,7 +38,8 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
29
38
|
model: input.model,
|
|
30
39
|
...(input.thinking === null ? {} : { thinking: input.thinking }),
|
|
31
40
|
thinkingConfig: input.thinkingConfig,
|
|
32
|
-
messages: input.messages,
|
|
41
|
+
messages: picture === undefined ? input.messages : withPicture(input.messages),
|
|
42
|
+
...(picture === undefined ? {} : { images: [picture] }),
|
|
33
43
|
documents: input.documents,
|
|
34
44
|
webSearch: input.webSearch,
|
|
35
45
|
previewLabel: piece.key,
|
|
@@ -54,7 +64,17 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
54
64
|
return "done";
|
|
55
65
|
}
|
|
56
66
|
if (input.kind === "tts") {
|
|
57
|
-
const spoken = await wrapped.tts({
|
|
67
|
+
const spoken = await wrapped.tts({
|
|
68
|
+
provider: input.provider,
|
|
69
|
+
model: input.model,
|
|
70
|
+
voiceId: input.voice,
|
|
71
|
+
text: input.text,
|
|
72
|
+
...(input.dialogue === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: {
|
|
75
|
+
dialogue: input.dialogue.map((line) => ({ voiceId: line.voice, text: line.text })),
|
|
76
|
+
}),
|
|
77
|
+
}, observeNarration(deps.audioPreviews, context.work.projectId, piece.id, narrationLabel(deps, context, piece), { revisionId: context.work.revisionId, workId: context.work.workId, workPieceId: piece.id }));
|
|
58
78
|
if (!spoken.ok)
|
|
59
79
|
return "held";
|
|
60
80
|
const asset = writeAsset(deps, context.work.projectId, "narration.mp3", spoken.value.bytes);
|
|
@@ -77,6 +97,15 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
77
97
|
voice: input.voice,
|
|
78
98
|
...(durationMs === undefined ? {} : { durationMs }),
|
|
79
99
|
}, asset);
|
|
100
|
+
context.emit({
|
|
101
|
+
type: "narration.piece",
|
|
102
|
+
projectId: context.work.projectId,
|
|
103
|
+
key: piece.key,
|
|
104
|
+
durationMs: durationMs ?? null,
|
|
105
|
+
revisionId: context.work.revisionId,
|
|
106
|
+
workId: context.work.workId,
|
|
107
|
+
workPieceId: piece.id,
|
|
108
|
+
});
|
|
80
109
|
deps.count?.("stage.completed", {
|
|
81
110
|
stage: "audio",
|
|
82
111
|
segment: input.segment,
|
|
@@ -95,48 +124,91 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
95
124
|
const index = piece.key.startsWith("image:")
|
|
96
125
|
? (view?.revision.content.imageOrder.indexOf(piece.key.slice(6)) ?? 0) + 1
|
|
97
126
|
: undefined;
|
|
98
|
-
|
|
99
|
-
|
|
127
|
+
// Which thumbnail this is (1-3), or undefined for any other image.
|
|
128
|
+
const variant = thumbnailVariant(piece.key);
|
|
129
|
+
const label = variant !== undefined
|
|
130
|
+
? variant === 1
|
|
131
|
+
? "Thumbnail"
|
|
132
|
+
: `Thumbnail ${String(variant)}`
|
|
100
133
|
: piece.key === referenceKey
|
|
101
134
|
? "Establishing image"
|
|
102
135
|
: index === undefined
|
|
103
136
|
? "Image"
|
|
104
|
-
: `Image ${String(index)}
|
|
137
|
+
: `Image ${String(index)}`;
|
|
138
|
+
const softened = await softenIfAsked(deps, context, wrapped, piece, input.prompt, label);
|
|
139
|
+
if (softened === "held")
|
|
140
|
+
return "held";
|
|
141
|
+
const prompt = softened ?? input.prompt;
|
|
142
|
+
const image = await wrapped.image(imageCall(deps, context.work.projectId, { ...input, prompt }, label));
|
|
105
143
|
if (!image.ok)
|
|
106
144
|
return "held";
|
|
107
145
|
const asset = writeAsset(deps, context.work.projectId, image.value.mime === "image/jpeg" ? "image.jpg" : "image.png", image.value.bytes);
|
|
108
|
-
const output = preparedResult(deps, context, piece, piece.key === "
|
|
109
|
-
|
|
110
|
-
: piece.key === referenceKey
|
|
111
|
-
? "reference"
|
|
112
|
-
: "image", asset, null, {
|
|
113
|
-
prompt: input.prompt,
|
|
146
|
+
const output = preparedResult(deps, context, piece, variant !== undefined ? "thumbnail" : piece.key === referenceKey ? "reference" : "image", asset, null, {
|
|
147
|
+
prompt,
|
|
114
148
|
provider: input.provider,
|
|
115
149
|
model: input.model,
|
|
116
150
|
...(index === undefined ? {} : { index }),
|
|
151
|
+
// The second and third thumbnails carry their number; the first keeps the meta a
|
|
152
|
+
// thumbnail always had.
|
|
153
|
+
...(variant === undefined || variant === 1 ? {} : { index: variant }),
|
|
117
154
|
});
|
|
118
|
-
await publishResult(deps, context, piece, [output], { prompt
|
|
155
|
+
await publishResult(deps, context, piece, [output], { prompt }, asset);
|
|
156
|
+
if (softened !== undefined)
|
|
157
|
+
clearSoftening(deps.db, context.work.projectId, [piece.key]);
|
|
119
158
|
deps.count?.("stage.completed", {
|
|
120
159
|
stage: context.work.kind,
|
|
121
160
|
provider: input.provider,
|
|
122
161
|
model: input.model,
|
|
123
|
-
...(
|
|
162
|
+
...(variant !== undefined ? { thumbnails: 1 } : { images: 1 }),
|
|
124
163
|
});
|
|
125
164
|
return "done";
|
|
126
165
|
}
|
|
127
|
-
throw new Error("Slopify hit an internal error (this step has no provider request to run). Use
|
|
166
|
+
throw new Error("Slopify hit an internal error (this step has no provider request to run). Use Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
167
|
+
}
|
|
168
|
+
// Soften and retry (`soften.ts`): the project's AI model rewords a refused image prompt
|
|
169
|
+
// before the image is drawn again. Undefined when nobody asked, or the project has no model.
|
|
170
|
+
async function softenIfAsked(deps, context, providers, piece, prompt, label) {
|
|
171
|
+
if (!softeningRequested(deps.db, context.work.projectId, piece.key))
|
|
172
|
+
return undefined;
|
|
173
|
+
const llm = executionView(deps, context.work.projectId, context.work.revisionId)?.revision.config
|
|
174
|
+
.llm;
|
|
175
|
+
if (llm === undefined)
|
|
176
|
+
return undefined;
|
|
177
|
+
const answer = await providers.llm({
|
|
178
|
+
provider: llm.provider,
|
|
179
|
+
model: llm.model,
|
|
180
|
+
...(llm.thinking === undefined ? {} : { thinking: llm.thinking }),
|
|
181
|
+
messages: softenMessages(prompt),
|
|
182
|
+
previewLabel: `${label}: softened prompt`,
|
|
183
|
+
check: (value) => softenedPrompt(value.text) === ""
|
|
184
|
+
? "The AI model sent back an empty prompt while softening it. Use Soften and retry again, or reword the prompt yourself in Edit project → Images."
|
|
185
|
+
: undefined,
|
|
186
|
+
});
|
|
187
|
+
return answer.ok ? softenedPrompt(answer.value.text) : "held";
|
|
128
188
|
}
|
|
129
189
|
function checkAnswer(piece, answer) {
|
|
130
190
|
if (answer.text.trim() === "")
|
|
131
|
-
return "The AI model sent back an empty answer. Use
|
|
191
|
+
return "The AI model sent back an empty answer. Use Try again; if it keeps happening, choose another model in the Providers section of Edit project.";
|
|
192
|
+
if (piece.input.kind === "llm" && piece.input.describe !== undefined)
|
|
193
|
+
return spokenPassage(answer.text) === ""
|
|
194
|
+
? "The AI model's description had no words a narrator could say. Use Try again; if it keeps happening, choose another model in the Providers section of Edit project, or turn off Describe tables and figures there."
|
|
195
|
+
: undefined;
|
|
132
196
|
if (piece.key === "research:planner")
|
|
133
197
|
return chaptersFrom(answer.text).length === 0
|
|
134
|
-
? "The AI model's research plan listed no chapters, so research could not go on. Use
|
|
198
|
+
? "The AI model's research plan listed no chapters, so research could not go on. Use Try again; if it keeps happening, choose another model in the Providers section of Edit project."
|
|
135
199
|
: undefined;
|
|
136
200
|
if (piece.key === "research:notes" || piece.key.startsWith("research:chapter:"))
|
|
137
201
|
return sourcedAnswer(piece.key, answer.text);
|
|
202
|
+
// A speaker split that loses or adds words is asked for again rather than narrated.
|
|
203
|
+
if (piece.input.kind === "llm" && piece.input.script?.attribute === true) {
|
|
204
|
+
const checked = parseAttribution(answer.text, attributionSource(piece.input.messages), piece.input.script.speakers);
|
|
205
|
+
return checked.ok ? undefined : checked.reason;
|
|
206
|
+
}
|
|
138
207
|
return undefined;
|
|
139
208
|
}
|
|
209
|
+
function attributionSource(messages) {
|
|
210
|
+
return messages.at(-1)?.content ?? "";
|
|
211
|
+
}
|
|
140
212
|
async function publishText(deps, context, piece, answer) {
|
|
141
213
|
if (piece.input.kind === "llm" && piece.input.preparation !== undefined) {
|
|
142
214
|
const checked = validatePreparation(answer.text, piece.input.preparation.source);
|
|
@@ -150,8 +222,20 @@ async function publishText(deps, context, piece, answer) {
|
|
|
150
222
|
});
|
|
151
223
|
return;
|
|
152
224
|
}
|
|
225
|
+
if (piece.input.kind === "llm" && piece.input.describe !== undefined) {
|
|
226
|
+
// The passage the narration says, as it says it; nothing to download.
|
|
227
|
+
await publishResult(deps, context, piece, [], { text: spokenPassage(answer.text) });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
153
230
|
if (piece.key === "article:body") {
|
|
154
231
|
const parts = splitEndMatter(answer.text);
|
|
232
|
+
// A script is read before it is kept: one the narration could not speak fails here, with
|
|
233
|
+
// the line to fix, rather than at the narration.
|
|
234
|
+
if (piece.input.kind === "llm" && piece.input.script !== undefined) {
|
|
235
|
+
const checked = parseScript(parts.body, piece.input.script.speakers);
|
|
236
|
+
if (!checked.ok)
|
|
237
|
+
throw new Error(`The text model's script can't be read: ${checked.reason} Try again to have it written again, or fix it in Edit project → Article.`);
|
|
238
|
+
}
|
|
155
239
|
await publishResult(deps, context, piece, preparedTexts(deps, context, piece, [
|
|
156
240
|
["instructions", "instructions.md", frozenInstructions(deps, context, piece)],
|
|
157
241
|
["article_md", "article.md", answer.text],
|
|
@@ -161,6 +245,10 @@ async function publishText(deps, context, piece, answer) {
|
|
|
161
245
|
]), { text: answer.text });
|
|
162
246
|
return;
|
|
163
247
|
}
|
|
248
|
+
if (piece.key === "script:attribute") {
|
|
249
|
+
await publishResult(deps, context, piece, preparedTexts(deps, context, piece, [["script_md", "script.md", answer.text]]), { text: answer.text });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
164
252
|
if (piece.key === "research:notes") {
|
|
165
253
|
await publishResult(deps, context, piece, preparedTexts(deps, context, piece, [
|
|
166
254
|
["notes", "notes.md", answer.text],
|
|
@@ -184,7 +272,7 @@ async function publishText(deps, context, piece, answer) {
|
|
|
184
272
|
: [];
|
|
185
273
|
const title = titles[Number(piece.key.split(":").at(-1)) - 1];
|
|
186
274
|
if (typeof title !== "string")
|
|
187
|
-
throw new Error("Slopify hit an internal error (a research chapter has no title in the research plan). Use
|
|
275
|
+
throw new Error("Slopify hit an internal error (a research chapter has no title in the research plan). Use Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
188
276
|
const asset = writeAsset(deps, context.work.projectId, `research-${piece.key.split(":").at(-1)}.md`, Buffer.from(answer.text));
|
|
189
277
|
await publishResult(deps, context, piece, [], { title, notes: answer.text }, asset);
|
|
190
278
|
return;
|
|
@@ -222,3 +310,23 @@ function checkPreparation(deps, context, piece, answer) {
|
|
|
222
310
|
const prepared = prepareRequests(source, checked.cues, model?.tts.maxCharacters ?? 4000);
|
|
223
311
|
return prepared.ok ? undefined : prepared.reason;
|
|
224
312
|
}
|
|
313
|
+
// A described figure's own picture (`runtime-pictures.ts`), sent only to a text provider that
|
|
314
|
+
// looks at pictures (`reviews/model.ts`). Otherwise the figure is described from its caption
|
|
315
|
+
// and alt text alone.
|
|
316
|
+
function describedPicture(deps, context, input) {
|
|
317
|
+
const named = input.describe?.image;
|
|
318
|
+
if (named === undefined || !imageReviewers.includes(input.provider))
|
|
319
|
+
return undefined;
|
|
320
|
+
const view = executionView(deps, context.work.projectId, context.work.revisionId);
|
|
321
|
+
const path = view === undefined ? undefined : articlePicture(deps, view, named);
|
|
322
|
+
return path === undefined ? undefined : { path, name: basename(path) };
|
|
323
|
+
}
|
|
324
|
+
function withPicture(messages) {
|
|
325
|
+
const last = messages.findLastIndex((message) => message.role === "user");
|
|
326
|
+
return messages.map((message, index) => index === last
|
|
327
|
+
? {
|
|
328
|
+
...message,
|
|
329
|
+
content: `${message.content}\n\nThe picture itself is attached: look at it before you answer.`,
|
|
330
|
+
}
|
|
331
|
+
: message);
|
|
332
|
+
}
|
|
@@ -12,7 +12,11 @@ export function preparedResult(deps, context, piece, role, asset, durationMs = n
|
|
|
12
12
|
slot: role === "image" ||
|
|
13
13
|
role === "short_image" ||
|
|
14
14
|
role === "short_video" ||
|
|
15
|
-
role === "animated_image"
|
|
15
|
+
role === "animated_image" ||
|
|
16
|
+
// Level the volume: one levelled narration per segment.
|
|
17
|
+
role === "audio_levelled" ||
|
|
18
|
+
// The second and third thumbnails; the first keeps the slot it always had.
|
|
19
|
+
(role === "thumbnail" && piece.key !== "thumbnail:image")
|
|
16
20
|
? piece.key
|
|
17
21
|
: `${context.work.kind}:${role}`,
|
|
18
22
|
workKey: piece.key,
|
|
@@ -35,7 +39,7 @@ export function preparedResult(deps, context, piece, role, asset, durationMs = n
|
|
|
35
39
|
export function preparedText(deps, context, piece, role, filename, text) {
|
|
36
40
|
const output = preparedTexts(deps, context, piece, [[role, filename, text]])[0];
|
|
37
41
|
if (output === undefined)
|
|
38
|
-
throw new Error("Slopify hit an internal error (a text file could not be prepared).
|
|
42
|
+
throw new Error("Slopify hit an internal error (a text file could not be prepared). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
39
43
|
return output;
|
|
40
44
|
}
|
|
41
45
|
export function preparedTexts(deps, context, piece, texts) {
|
|
@@ -93,13 +97,17 @@ export async function publishResult(deps, context, piece, outputs, payload, asse
|
|
|
93
97
|
function pieceKind(piece) {
|
|
94
98
|
if (piece.input.kind === "llm" && piece.input.preparation !== undefined)
|
|
95
99
|
return "prompt_written";
|
|
100
|
+
if (piece.input.kind === "llm" && piece.input.describe !== undefined)
|
|
101
|
+
return "prompt_written";
|
|
96
102
|
if (piece.input.kind === "tts")
|
|
97
103
|
return piece.input.segment === "body" ? "chunk" : "segment";
|
|
98
104
|
if (piece.input.kind === "provided" && /^audio:body:.+:\d+$/.test(piece.key))
|
|
99
105
|
return "chunk";
|
|
100
106
|
if (piece.input.kind === "provided" && /^audio:(intro|outro):\d+$/.test(piece.key))
|
|
101
107
|
return "segment";
|
|
102
|
-
if (piece.key.startsWith("image:") ||
|
|
108
|
+
if (piece.key.startsWith("image:") ||
|
|
109
|
+
piece.key.startsWith("animate:") ||
|
|
110
|
+
piece.key.startsWith("figure:card:"))
|
|
103
111
|
return "image";
|
|
104
112
|
if (piece.key.startsWith("research:chapter:"))
|
|
105
113
|
return "chapter";
|
|
@@ -123,6 +131,13 @@ function pieceIndex(deps, context, piece) {
|
|
|
123
131
|
return 1;
|
|
124
132
|
if (piece.key === "entry:outro:text")
|
|
125
133
|
return 2;
|
|
134
|
+
// The cards take places of their own, past the animated images'.
|
|
135
|
+
const card = /^figure:card:(\d+)$/.exec(piece.key);
|
|
136
|
+
if (card !== null)
|
|
137
|
+
return 800000 + Number(card[1]);
|
|
138
|
+
// The narration's descriptions take places of their own, past the preparation steps'.
|
|
139
|
+
if (piece.input.kind === "llm" && piece.input.describe !== undefined)
|
|
140
|
+
return 500000 + piece.input.describe.index;
|
|
126
141
|
// The shorts' steps take places of their own, past every other step of the Video stage:
|
|
127
142
|
// the pick, then per short its prompts, its images and its render.
|
|
128
143
|
const short = /^shorts:(?:(\d+):(prompts|image:(\d+)|render)|pick)$/.exec(piece.key);
|
|
@@ -152,6 +167,7 @@ function pieceIndex(deps, context, piece) {
|
|
|
152
167
|
"narration:files:body",
|
|
153
168
|
"narration:files:intro",
|
|
154
169
|
"narration:files:outro",
|
|
170
|
+
"voices:files",
|
|
155
171
|
];
|
|
156
172
|
const localIndex = localOrder.indexOf(piece.key);
|
|
157
173
|
if (localIndex >= 0)
|
|
@@ -190,10 +206,10 @@ export function retainPartialArticle(deps, context, piece, text) {
|
|
|
190
206
|
if (authority === undefined)
|
|
191
207
|
return;
|
|
192
208
|
if (authority.key !== piece.key || authority.fingerprint !== piece.fingerprint)
|
|
193
|
-
throw new Error("Slopify hit an internal error (a partly written article doesn't match its saved step).
|
|
209
|
+
throw new Error("Slopify hit an internal error (a partly written article doesn't match its saved step). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
194
210
|
const revision = revisionById(deps.db, context.work.projectId, context.work.revisionId);
|
|
195
211
|
if (revision === undefined)
|
|
196
|
-
throw new Error("Slopify hit an internal error (the project version for a partly written article is missing).
|
|
212
|
+
throw new Error("Slopify hit an internal error (the project version for a partly written article is missing). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
197
213
|
insertAsset(deps.db, result.asset);
|
|
198
214
|
insertManifestOutput(deps.db, revision, {
|
|
199
215
|
slot: `article:partial:${piece.id}`,
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { thinkingModes } from "../../kernel/ports/llm.js";
|
|
4
|
+
import { plainText } from "../article/plain.js";
|
|
5
|
+
import { defaultReviewPrompts, narrationItemKey, reviewStages, } from "../reviews/model.js";
|
|
6
|
+
import { nextAttempt, reviewOutcome } from "../reviews/outcome.js";
|
|
7
|
+
import { latestVerdict, saveVerdict } from "../reviews/repo.js";
|
|
8
|
+
import { parseVerdict, reviewMessages } from "../reviews/verdict.js";
|
|
9
|
+
import { pickedShortsOf } from "../shorts/clips.js";
|
|
10
|
+
import { outputPath } from "../storage/layout.js";
|
|
11
|
+
import { exportSnapshot } from "./runtime-export-inputs.js";
|
|
12
|
+
import { savedCatalogue } from "./runtime-plan.js";
|
|
13
|
+
import { publishResult } from "./runtime-publication.js";
|
|
14
|
+
import { wordsSchema } from "./runtime-subtitles.js";
|
|
15
|
+
// What `recipe-reviews.ts` saves in the step's values, by position.
|
|
16
|
+
const valuesSchema = z.tuple([
|
|
17
|
+
z.enum(reviewStages),
|
|
18
|
+
z.string(),
|
|
19
|
+
z.array(z.tuple([z.string(), z.unknown()])),
|
|
20
|
+
z.string(),
|
|
21
|
+
z.string(),
|
|
22
|
+
z.string().nullable(),
|
|
23
|
+
z.string(),
|
|
24
|
+
z.enum(["flag", "redo"]),
|
|
25
|
+
z.number().int().nonnegative(),
|
|
26
|
+
]);
|
|
27
|
+
// ceiling: enough of a long article or research for a reviewer to judge it, and well inside
|
|
28
|
+
// every supported model's context.
|
|
29
|
+
const textMax = 60_000;
|
|
30
|
+
// Asks the reviewer about one finished item and saves its verdict. A malformed answer is a
|
|
31
|
+
// failed attempt the provider wrapper asks again; a verdict that sends the item back is
|
|
32
|
+
// saved as a pending redo, which `review-redo.ts` starts once this step has finished, and
|
|
33
|
+
// until then the item's dependents are held (`reviewHold`).
|
|
34
|
+
export async function executeReviewRecipe(deps, context, providers, piece) {
|
|
35
|
+
if (deps.db
|
|
36
|
+
.prepare("SELECT state FROM revision_work_pieces WHERE id=? AND work_id=?")
|
|
37
|
+
.get(piece.id, context.work.workId)?.state === "done")
|
|
38
|
+
return "done";
|
|
39
|
+
if (!context.maySubmit(piece.id))
|
|
40
|
+
return "held";
|
|
41
|
+
context.signal.throwIfAborted();
|
|
42
|
+
const input = piece.input;
|
|
43
|
+
const values = input.kind === "local" && input.operation === "review-v1"
|
|
44
|
+
? valuesSchema.safeParse(input.values)
|
|
45
|
+
: undefined;
|
|
46
|
+
if (values === undefined || !values.success)
|
|
47
|
+
throw new Error("Slopify hit an internal error (a review step was set up wrongly). Try again; if it happens again, use Download diagnostics in Settings and report it.");
|
|
48
|
+
const [stage, itemKey, , provider, model, thinking, prompt, mode, retries] = values.data;
|
|
49
|
+
const { view, plan } = exportSnapshot(deps, context, piece);
|
|
50
|
+
const material = reviewMaterial(deps, context, view, plan.recipes, stage, itemKey, prompt);
|
|
51
|
+
const previous = latestVerdict(deps.db, context.work.projectId, itemKey);
|
|
52
|
+
const attempt = nextAttempt(previous, material.itemFingerprint);
|
|
53
|
+
const row = deps.db
|
|
54
|
+
.prepare("SELECT recipe_context FROM revision_work WHERE id=?")
|
|
55
|
+
.get(context.work.workId);
|
|
56
|
+
const catalogued = savedCatalogue(row?.recipe_context).llm.find((one) => one.provider === provider && one.id === model);
|
|
57
|
+
const level = thinking === null ? undefined : z.enum(thinkingModes).parse(thinking);
|
|
58
|
+
if (!context.maySubmit(piece.id))
|
|
59
|
+
return "held";
|
|
60
|
+
const answer = await providers.forPiece(piece.id).llm({
|
|
61
|
+
provider,
|
|
62
|
+
model,
|
|
63
|
+
...(level === undefined ? {} : { thinking: level }),
|
|
64
|
+
thinkingConfig: level === undefined ? null : (catalogued?.llm.thinking?.[level] ?? null),
|
|
65
|
+
messages: reviewMessages(material.request),
|
|
66
|
+
webSearch: false,
|
|
67
|
+
...(material.images.length === 0 ? {} : { images: material.images }),
|
|
68
|
+
previewLabel: piece.key,
|
|
69
|
+
check: (value) => {
|
|
70
|
+
const parsed = parseVerdict(value.text);
|
|
71
|
+
return parsed.ok ? undefined : parsed.reason;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (!answer.ok)
|
|
75
|
+
return "held";
|
|
76
|
+
const verdict = parseVerdict(answer.value.text);
|
|
77
|
+
if (!verdict.ok)
|
|
78
|
+
throw new Error(verdict.reason);
|
|
79
|
+
const outcome = reviewOutcome({ passed: verdict.value.passed, mode, attempt, retries });
|
|
80
|
+
const saved = saveVerdict(deps.db, {
|
|
81
|
+
id: deps.ids.next(),
|
|
82
|
+
projectId: context.work.projectId,
|
|
83
|
+
revisionId: context.work.revisionId,
|
|
84
|
+
itemKey,
|
|
85
|
+
stage,
|
|
86
|
+
itemFingerprint: material.itemFingerprint,
|
|
87
|
+
reviewFingerprint: piece.fingerprint,
|
|
88
|
+
passed: verdict.value.passed,
|
|
89
|
+
reasons: verdict.value.reasons,
|
|
90
|
+
outcome,
|
|
91
|
+
attempt,
|
|
92
|
+
action: null,
|
|
93
|
+
actionAt: null,
|
|
94
|
+
redoState: outcome === "redo" ? "pending" : null,
|
|
95
|
+
redoError: null,
|
|
96
|
+
createdAt: deps.clock.now().toISOString(),
|
|
97
|
+
});
|
|
98
|
+
await publishResult(deps, context, piece, [], {
|
|
99
|
+
verdictId: saved.id,
|
|
100
|
+
passed: saved.passed,
|
|
101
|
+
reasons: saved.reasons,
|
|
102
|
+
outcome: saved.outcome,
|
|
103
|
+
attempt: saved.attempt,
|
|
104
|
+
});
|
|
105
|
+
deps.count?.("stage.completed", {
|
|
106
|
+
provider,
|
|
107
|
+
model,
|
|
108
|
+
tokensIn: answer.value.usage?.inputTokens ?? 0,
|
|
109
|
+
tokensOut: answer.value.usage?.outputTokens ?? 0,
|
|
110
|
+
});
|
|
111
|
+
return "done";
|
|
112
|
+
}
|
|
113
|
+
function reviewMaterial(deps, context, view, recipes, stage, itemKey, prompt) {
|
|
114
|
+
const config = view.revision.config;
|
|
115
|
+
const output = (workKey, role) => view.outputs.find((row) => row.workKey === workKey &&
|
|
116
|
+
row.selected &&
|
|
117
|
+
row.available &&
|
|
118
|
+
row.state === "ready" &&
|
|
119
|
+
(role === undefined || row.output.role === role));
|
|
120
|
+
const path = (row) => outputPath(deps.paths, context.work.projectId, row.output.path);
|
|
121
|
+
const text = (row) => clip(readFileSync(path(row), "utf8"));
|
|
122
|
+
const missing = (what, fix) => new Error(`The ${what} this review looks at is missing, so it could not be reviewed. ${fix}, then use Try again.`);
|
|
123
|
+
const request = (sections, images = []) => ({
|
|
124
|
+
stage,
|
|
125
|
+
instruction: prompt.trim() === "" ? defaultReviewPrompts[stage] : prompt,
|
|
126
|
+
sections,
|
|
127
|
+
images,
|
|
128
|
+
});
|
|
129
|
+
switch (stage) {
|
|
130
|
+
case "article": {
|
|
131
|
+
const article = output(itemKey, "article_md");
|
|
132
|
+
if (article === undefined)
|
|
133
|
+
throw missing("article", "Use More → Write the article again in the Article section");
|
|
134
|
+
const notes = output("research:notes", "notes");
|
|
135
|
+
const research = notes !== undefined ? text(notes) : clip(config.provided.research?.trim() ?? "");
|
|
136
|
+
return {
|
|
137
|
+
request: request([
|
|
138
|
+
{
|
|
139
|
+
label: "Article prompt the article was written from",
|
|
140
|
+
text: clip(config.rendered.article ?? ""),
|
|
141
|
+
},
|
|
142
|
+
...(research === "" ? [] : [{ label: "Research notes", text: research }]),
|
|
143
|
+
{ label: "Article", text: text(article) },
|
|
144
|
+
]),
|
|
145
|
+
images: [],
|
|
146
|
+
itemFingerprint: article.fingerprint,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
case "images":
|
|
150
|
+
case "thumbnail": {
|
|
151
|
+
const image = output(itemKey);
|
|
152
|
+
if (image === undefined)
|
|
153
|
+
throw missing(stage === "images" ? "image" : "thumbnail", stage === "images"
|
|
154
|
+
? "Use More → make it again in its section on Images"
|
|
155
|
+
: "Use More → make it again in its section on Thumbnail");
|
|
156
|
+
const recipe = recipes.find((one) => one.key === itemKey);
|
|
157
|
+
const brief = recipe?.input.kind === "image" ? recipe.input.prompt : "";
|
|
158
|
+
const reference = stage === "images" ? output("reference:image") : undefined;
|
|
159
|
+
return {
|
|
160
|
+
request: request([
|
|
161
|
+
...(stage === "thumbnail" ? [{ label: "Video title", text: config.title }] : []),
|
|
162
|
+
...(brief === "" ? [] : [{ label: "Brief the image was drawn from", text: brief }]),
|
|
163
|
+
], [
|
|
164
|
+
stage === "images" ? "the image to review" : "the thumbnail to review",
|
|
165
|
+
...(reference === undefined ? [] : ["the establishing image it must match"]),
|
|
166
|
+
]),
|
|
167
|
+
images: [
|
|
168
|
+
{
|
|
169
|
+
path: path(image),
|
|
170
|
+
name: stage === "images" ? "the image to review" : "the thumbnail to review",
|
|
171
|
+
},
|
|
172
|
+
...(reference === undefined
|
|
173
|
+
? []
|
|
174
|
+
: [{ path: path(reference), name: "the establishing image it must match" }]),
|
|
175
|
+
],
|
|
176
|
+
itemFingerprint: image.fingerprint,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
case "narration": {
|
|
180
|
+
const timing = output("subtitles:timing", "subtitle_words");
|
|
181
|
+
if (timing === undefined || itemKey !== narrationItemKey)
|
|
182
|
+
throw missing("narration's word timing", "Use More → Render the video again in the Video section");
|
|
183
|
+
const { words, omissions } = wordsSchema.parse(JSON.parse(readFileSync(path(timing), "utf8")));
|
|
184
|
+
const unsure = words.filter((word) => (word.confidence ?? 1) < 0.3).length;
|
|
185
|
+
return {
|
|
186
|
+
request: request([
|
|
187
|
+
{
|
|
188
|
+
label: "Text the narration was read from",
|
|
189
|
+
text: clip(plainText(view.articleMarkdown ?? "")),
|
|
190
|
+
},
|
|
191
|
+
{ label: "What the timing heard", text: clip(words.map((word) => word.text).join(" ")) },
|
|
192
|
+
{
|
|
193
|
+
label: "Stretches of text the timing could not hear",
|
|
194
|
+
text: omissions.length === 0
|
|
195
|
+
? "None."
|
|
196
|
+
: omissions.map((gap) => `At ${gap.start.toFixed(1)} s: ${gap.text}`).join("\n"),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
label: "Words heard with low confidence",
|
|
200
|
+
text: `${String(unsure)} of ${String(words.length)}.`,
|
|
201
|
+
},
|
|
202
|
+
]),
|
|
203
|
+
images: [],
|
|
204
|
+
itemFingerprint: timing.fingerprint,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
case "shorts": {
|
|
208
|
+
const render = output(`${itemKey}:render`);
|
|
209
|
+
if (render === undefined)
|
|
210
|
+
throw missing("short", "Use Make this short again in Edit project → Shorts");
|
|
211
|
+
const number = Number(itemKey.slice("shorts:".length));
|
|
212
|
+
const pick = view.pieces.find((row) => row.key === "shorts:pick" && row.selected && row.piece.state === "done");
|
|
213
|
+
const title = pickedShortsOf(pick?.piece.payload)?.shorts.find((clip) => clip.number === number)?.title ??
|
|
214
|
+
"";
|
|
215
|
+
const stills = view.outputs
|
|
216
|
+
.filter((row) => row.workKey.startsWith(`${itemKey}:image:`) &&
|
|
217
|
+
row.selected &&
|
|
218
|
+
row.available &&
|
|
219
|
+
row.state === "ready")
|
|
220
|
+
.slice(0, 6);
|
|
221
|
+
if (stills.length === 0)
|
|
222
|
+
throw missing("short's images", "Use Make this short again in Edit project → Shorts");
|
|
223
|
+
const names = stills.map((_, index) => `image ${String(index + 1)} of the short`);
|
|
224
|
+
return {
|
|
225
|
+
request: request([{ label: "Title of the short", text: title }], names),
|
|
226
|
+
images: stills.map((row, index) => ({
|
|
227
|
+
path: path(row),
|
|
228
|
+
name: names[index] ?? "an image of the short",
|
|
229
|
+
})),
|
|
230
|
+
itemFingerprint: render.fingerprint,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function clip(value) {
|
|
236
|
+
return value.length <= textMax ? value : `${value.slice(0, textMax)}\n[... cut for length]`;
|
|
237
|
+
}
|
|
@@ -4,8 +4,10 @@ import { executeExportRecipe } from "./runtime-export.js";
|
|
|
4
4
|
import { executeLocalRecipe } from "./runtime-local.js";
|
|
5
5
|
import { pieceLabel } from "./runtime-piece-label.js";
|
|
6
6
|
import { executeProviderRecipe } from "./runtime-provider.js";
|
|
7
|
+
import { executeReviewRecipe } from "./runtime-review.js";
|
|
7
8
|
import { executeShortsRecipe } from "./runtime-shorts.js";
|
|
8
9
|
import { executeSubtitleRecipe } from "./runtime-subtitles.js";
|
|
10
|
+
import { executeVoicesRecipe } from "./runtime-voices.js";
|
|
9
11
|
import { executeYoutubeRecipe } from "./runtime-youtube.js";
|
|
10
12
|
import { workPieces } from "./work-records.js";
|
|
11
13
|
export async function runRevisionInvocation(deps, context, providers) {
|
|
@@ -22,17 +24,21 @@ export async function runRevisionInvocation(deps, context, providers) {
|
|
|
22
24
|
? await executeExportRecipe(deps, context, piece)
|
|
23
25
|
: piece.key.startsWith("document:")
|
|
24
26
|
? await executeDocumentRecipe(deps, context, piece)
|
|
25
|
-
: piece.key.startsWith("
|
|
26
|
-
? await
|
|
27
|
-
: piece.key.startsWith("
|
|
28
|
-
? await
|
|
29
|
-
: piece.key.startsWith("
|
|
30
|
-
? await
|
|
31
|
-
: piece.
|
|
32
|
-
|
|
33
|
-
piece.
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
: piece.key.startsWith("review:")
|
|
28
|
+
? await executeReviewRecipe(deps, context, providers, piece)
|
|
29
|
+
: piece.key.startsWith("voices:")
|
|
30
|
+
? await executeVoicesRecipe(deps, context, piece)
|
|
31
|
+
: piece.key.startsWith("youtube:")
|
|
32
|
+
? await executeYoutubeRecipe(deps, context, providers, piece)
|
|
33
|
+
: piece.key.startsWith("shorts:")
|
|
34
|
+
? await executeShortsRecipe(deps, context, providers, piece)
|
|
35
|
+
: piece.key.startsWith("animate:")
|
|
36
|
+
? await executeAnimateRecipe(deps, context, providers, piece)
|
|
37
|
+
: piece.input.kind === "llm" ||
|
|
38
|
+
piece.input.kind === "tts" ||
|
|
39
|
+
piece.input.kind === "image"
|
|
40
|
+
? await executeProviderRecipe(deps, context, providers, piece)
|
|
41
|
+
: await executeLocalRecipe(deps, context, piece);
|
|
36
42
|
if (outcome === "held")
|
|
37
43
|
return outcome;
|
|
38
44
|
}
|