@gentbajko/slopify 2.0.2 → 2.1.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.
Files changed (224) hide show
  1. package/SUBTITLES.md +1 -1
  2. package/dist/adapters/alignment/protocol.js +11 -1
  3. package/dist/adapters/alignment/runner.js +4 -1
  4. package/dist/adapters/alignment/threads.js +11 -0
  5. package/dist/adapters/alignment/worker.js +66 -9
  6. package/dist/adapters/explain.js +93 -0
  7. package/dist/adapters/ffmpeg.js +3 -3
  8. package/dist/adapters/host-cli/index.js +34 -3
  9. package/dist/adapters/host-cli/transport.js +4 -4
  10. package/dist/adapters/image/bytes.js +2 -2
  11. package/dist/adapters/image/codex-output.js +12 -12
  12. package/dist/adapters/image/codex.js +24 -8
  13. package/dist/adapters/image/fal.js +12 -6
  14. package/dist/adapters/image/google.js +18 -8
  15. package/dist/adapters/image/models.js +8 -2
  16. package/dist/adapters/image/openai.js +15 -6
  17. package/dist/adapters/image/replicate.js +16 -7
  18. package/dist/adapters/llm/claude-code-models.js +1 -1
  19. package/dist/adapters/llm/claude-code.js +16 -7
  20. package/dist/adapters/llm/cli-login-error.js +2 -1
  21. package/dist/adapters/llm/codex-models.js +1 -1
  22. package/dist/adapters/llm/codex.js +5 -6
  23. package/dist/adapters/llm/document-workspace.js +2 -2
  24. package/dist/adapters/llm/gemini-models.js +1 -1
  25. package/dist/adapters/llm/gemini-workspace.js +1 -1
  26. package/dist/adapters/llm/gemini.js +15 -11
  27. package/dist/adapters/llm/openrouter.js +21 -9
  28. package/dist/adapters/llm/run-cli.js +14 -11
  29. package/dist/adapters/tts/cartesia.js +10 -3
  30. package/dist/adapters/tts/elevenlabs.js +18 -6
  31. package/dist/adapters/tts/inworld-async.js +47 -16
  32. package/dist/adapters/tts/inworld.js +25 -15
  33. package/dist/adapters/tts/openai.js +18 -6
  34. package/dist/assets/document/SOURCE.txt +7 -0
  35. package/dist/assets/document/background.jpg +0 -0
  36. package/dist/assets/document/fonts/Cinzel-Black.ttf +0 -0
  37. package/dist/assets/document/fonts/Cinzel-Bold.ttf +0 -0
  38. package/dist/assets/document/fonts/Cinzel-Medium.ttf +0 -0
  39. package/dist/assets/document/fonts/Cinzel-Regular.ttf +0 -0
  40. package/dist/assets/document/fonts/OFL.txt +93 -0
  41. package/dist/assets/document/fonts/SOURCE.txt +11 -0
  42. package/dist/catalog/registry.js +13 -10
  43. package/dist/catalog/store.js +1 -1
  44. package/dist/catalog/validate.js +16 -6
  45. package/dist/edge/cli.js +33 -7
  46. package/dist/edge/docker-launch.js +6 -6
  47. package/dist/edge/docker-projects/engine.js +34 -13
  48. package/dist/edge/docker-projects/install.js +8 -8
  49. package/dist/edge/docker-projects/state.js +11 -7
  50. package/dist/edge/docker-projects/tree.js +6 -6
  51. package/dist/edge/docker.js +13 -13
  52. package/dist/edge/host-cli.js +14 -0
  53. package/dist/edge/http/actions.js +27 -25
  54. package/dist/edge/http/app.js +3 -3
  55. package/dist/edge/http/audio-preview.js +1 -1
  56. package/dist/edge/http/checkpoints.js +5 -1
  57. package/dist/edge/http/diagnostics.js +3 -1
  58. package/dist/edge/http/draft-files.js +2 -2
  59. package/dist/edge/http/draft-problem.js +11 -1
  60. package/dist/edge/http/files.js +4 -4
  61. package/dist/edge/http/folder-location-schema.js +5 -8
  62. package/dist/edge/http/folder-location.js +13 -8
  63. package/dist/edge/http/fonts.js +22 -10
  64. package/dist/edge/http/host-cli.js +61 -14
  65. package/dist/edge/http/multipart.js +7 -5
  66. package/dist/edge/http/open-folder.js +2 -2
  67. package/dist/edge/http/planning.js +4 -4
  68. package/dist/edge/http/problem.js +2 -2
  69. package/dist/edge/http/project-templates.js +7 -3
  70. package/dist/edge/http/projects.js +8 -8
  71. package/dist/edge/http/prompts.js +9 -4
  72. package/dist/edge/http/providers.js +15 -7
  73. package/dist/edge/http/revision-files.js +8 -4
  74. package/dist/edge/http/revisions.js +30 -4
  75. package/dist/edge/http/schedules.js +9 -9
  76. package/dist/edge/http/settings.js +15 -9
  77. package/dist/edge/http/staging.js +5 -5
  78. package/dist/edge/http/storage.js +4 -4
  79. package/dist/edge/http/subtitles.js +1 -1
  80. package/dist/edge/http/tutorial.js +2 -2
  81. package/dist/edge/http/update.js +3 -3
  82. package/dist/host-cli/install.js +28 -6
  83. package/dist/host-cli/open-folder.js +95 -0
  84. package/dist/host-cli/paths.js +6 -6
  85. package/dist/host-cli/runtime.js +2 -0
  86. package/dist/host-cli/server.js +1 -1
  87. package/dist/host-cli/service.js +11 -11
  88. package/dist/host-cli/status.js +7 -3
  89. package/dist/kernel/config/index.js +6 -4
  90. package/dist/kernel/db/migrate.js +27 -2
  91. package/dist/kernel/db/migrations/0013-schedule-topic-queue.sql +4 -0
  92. package/dist/kernel/db/migrations/0014-document-stage.sql +44 -0
  93. package/dist/kernel/db/migrations/0015-description-prompts.sql +13 -0
  94. package/dist/kernel/lock.js +4 -3
  95. package/dist/kernel/pipeline.js +11 -1
  96. package/dist/kernel/ports/host-cli.js +14 -0
  97. package/dist/kernel/ports/subtitles.js +15 -1
  98. package/dist/kernel/runner/attempt.js +26 -2
  99. package/dist/kernel/runner/graph.js +6 -1
  100. package/dist/main.js +8 -4
  101. package/dist/slices/admission/model.js +7 -1
  102. package/dist/slices/admission/rules.js +98 -22
  103. package/dist/slices/admission/schema.js +16 -3
  104. package/dist/slices/admission/start.js +3 -2
  105. package/dist/slices/article/continuation.js +4 -2
  106. package/dist/slices/article/plain.js +2 -2
  107. package/dist/slices/article/provided-entries.js +3 -3
  108. package/dist/slices/article/run.js +2 -2
  109. package/dist/slices/article/segments.js +5 -3
  110. package/dist/slices/control/providers.js +20 -7
  111. package/dist/slices/document/blocks.js +129 -0
  112. package/dist/slices/document/flow.js +117 -0
  113. package/dist/slices/document/fonts.js +54 -0
  114. package/dist/slices/document/model.js +11 -0
  115. package/dist/slices/document/pages.js +198 -0
  116. package/dist/slices/document/render.js +242 -0
  117. package/dist/slices/document/sources.js +35 -0
  118. package/dist/slices/document/theme.js +158 -0
  119. package/dist/slices/document/writer.js +66 -0
  120. package/dist/slices/estimate/index.js +14 -2
  121. package/dist/slices/fonts/catalog.js +1 -1
  122. package/dist/slices/fonts/upload.js +1 -1
  123. package/dist/slices/images/run.js +5 -5
  124. package/dist/slices/library/lint.js +13 -7
  125. package/dist/slices/library/model.js +1 -1
  126. package/dist/slices/library/slots.js +6 -3
  127. package/dist/slices/narration/concat.js +1 -1
  128. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  129. package/dist/slices/narration/pronunciation.js +2 -2
  130. package/dist/slices/narration/run.js +7 -7
  131. package/dist/slices/narration/steering.js +3 -3
  132. package/dist/slices/play-drafts/convert.js +35 -5
  133. package/dist/slices/play-drafts/readiness.js +15 -8
  134. package/dist/slices/play-drafts/review-inputs.js +7 -4
  135. package/dist/slices/play-drafts/review.js +3 -3
  136. package/dist/slices/play-drafts/schema.js +22 -5
  137. package/dist/slices/play-drafts/service.js +3 -1
  138. package/dist/slices/play-drafts/uploads.js +1 -1
  139. package/dist/slices/project-templates/from-project.js +10 -0
  140. package/dist/slices/project-templates/setup.js +1 -0
  141. package/dist/slices/rebuild/admission-repo.js +2 -2
  142. package/dist/slices/rebuild/preview-details.js +11 -1
  143. package/dist/slices/rebuild/recipe-audio.js +3 -0
  144. package/dist/slices/rebuild/recipe-build.js +6 -1
  145. package/dist/slices/rebuild/recipe-document.js +36 -0
  146. package/dist/slices/rebuild/recipe-exports.js +14 -2
  147. package/dist/slices/rebuild/recipe-fixture.js +4 -0
  148. package/dist/slices/rebuild/recipe-legacy.js +6 -1
  149. package/dist/slices/rebuild/recipe-model.js +2 -0
  150. package/dist/slices/rebuild/recipe-narration-text.js +1 -1
  151. package/dist/slices/rebuild/recipe-provider-choice.js +3 -0
  152. package/dist/slices/rebuild/recipe-save.js +15 -3
  153. package/dist/slices/rebuild/recipe-validation.js +15 -2
  154. package/dist/slices/rebuild/recipe-visual.js +11 -2
  155. package/dist/slices/rebuild/recipe-work.js +15 -0
  156. package/dist/slices/rebuild/recipe-youtube.js +33 -0
  157. package/dist/slices/rebuild/recovery-selection.js +2 -0
  158. package/dist/slices/rebuild/recovery.js +12 -7
  159. package/dist/slices/rebuild/repo.js +3 -3
  160. package/dist/slices/rebuild/runtime-actions.js +2 -0
  161. package/dist/slices/rebuild/runtime-admission.js +4 -4
  162. package/dist/slices/rebuild/runtime-article.js +5 -5
  163. package/dist/slices/rebuild/runtime-checkpoints.js +4 -4
  164. package/dist/slices/rebuild/runtime-document.js +85 -0
  165. package/dist/slices/rebuild/runtime-export-inputs.js +7 -6
  166. package/dist/slices/rebuild/runtime-export.js +39 -23
  167. package/dist/slices/rebuild/runtime-local.js +6 -6
  168. package/dist/slices/rebuild/runtime-narration-text.js +46 -7
  169. package/dist/slices/rebuild/runtime-piece-label.js +66 -0
  170. package/dist/slices/rebuild/runtime-provider.js +6 -4
  171. package/dist/slices/rebuild/runtime-publication.js +4 -3
  172. package/dist/slices/rebuild/runtime-run.js +22 -4
  173. package/dist/slices/rebuild/runtime-store.js +7 -2
  174. package/dist/slices/rebuild/runtime-subtitles.js +82 -16
  175. package/dist/slices/rebuild/runtime-youtube.js +84 -0
  176. package/dist/slices/rebuild/transition-repo.js +8 -6
  177. package/dist/slices/reruns/cascade.js +6 -4
  178. package/dist/slices/reruns/index.js +1 -1
  179. package/dist/slices/research/run.js +9 -7
  180. package/dist/slices/research/synthesis.js +4 -2
  181. package/dist/slices/revisions/mutation-assets.js +26 -8
  182. package/dist/slices/revisions/mutation-cues.js +4 -2
  183. package/dist/slices/revisions/projection.js +2 -1
  184. package/dist/slices/revisions/rules.js +18 -9
  185. package/dist/slices/revisions/schema.js +1 -1
  186. package/dist/slices/schedules/repo.js +55 -11
  187. package/dist/slices/schedules/scheduler.js +25 -8
  188. package/dist/slices/schedules/schema.js +19 -4
  189. package/dist/slices/schedules/service.js +2 -0
  190. package/dist/slices/settings/cli-paths.js +7 -4
  191. package/dist/slices/settings/models.js +4 -4
  192. package/dist/slices/settings/playback.js +2 -2
  193. package/dist/slices/storage/assets.js +5 -5
  194. package/dist/slices/storage/downloads.js +1 -0
  195. package/dist/slices/storage/layout.js +7 -1
  196. package/dist/slices/storage/model.js +3 -0
  197. package/dist/slices/subtitles/captions.js +1 -1
  198. package/dist/slices/subtitles/prepare.js +7 -6
  199. package/dist/slices/subtitles/transcript.js +1 -1
  200. package/dist/slices/telemetry/model.js +1 -1
  201. package/dist/slices/thumbnail/run.js +8 -8
  202. package/dist/slices/video/audio-export.js +11 -3
  203. package/dist/slices/video/audio-inputs.js +2 -2
  204. package/dist/slices/video/edit-list.js +73 -0
  205. package/dist/slices/video/ffmpeg.js +129 -61
  206. package/dist/slices/video/motion.js +51 -0
  207. package/dist/slices/video/plan.js +62 -28
  208. package/dist/slices/video/reuse-audio.js +2 -0
  209. package/dist/slices/video/run.js +23 -12
  210. package/dist/slices/video/slideshow.js +48 -0
  211. package/dist/slices/video/write-export.js +17 -25
  212. package/dist/slices/youtube/answer.js +199 -0
  213. package/dist/slices/youtube/model.js +27 -0
  214. package/dist/slices/youtube/timestamps.js +24 -0
  215. package/dist/slices/youtube/transcript.js +47 -0
  216. package/dist/updater/plan.js +1 -1
  217. package/dist/updater/service.js +4 -3
  218. package/dist/updater/worker.js +6 -4
  219. package/dist/web/assets/index-B6NbhpjD.css +1 -0
  220. package/dist/web/assets/index-ComF5gwP.js +142 -0
  221. package/dist/web/index.html +2 -2
  222. package/package.json +4 -2
  223. package/dist/web/assets/index-CZNiHm8P.css +0 -1
  224. package/dist/web/assets/index-DPgGLX5m.js +0 -137
@@ -5,13 +5,13 @@ import { z } from "zod";
5
5
  import { installHostPackage } from "../host-cli/install.js";
6
6
  import { ensureBridgeToken, hasCode, prepareHostPaths } from "../host-cli/paths.js";
7
7
  import { ensureHostService, privateRead, privateWrite } from "../host-cli/service.js";
8
- import { resolveHostCommand } from "../host-cli/status.js";
8
+ import { hostCommandName, resolveHostCommand } from "../host-cli/status.js";
9
9
  import { hostCliProtocol, hostLlmIds } from "../kernel/ports/host-cli.js";
10
10
  export function assertManagedDockerHost(platform, uid, gid) {
11
11
  if (platform !== "linux")
12
- throw new Error("The managed Docker launcher requires Linux. Native Slopify and plain API-only Docker remain available on other hosts.");
12
+ throw new Error("The --docker launcher only works on Linux. On this system, run Slopify without Docker (npx @gentbajko/slopify), or start the Slopify Docker image yourself with docker run.");
13
13
  if (uid === undefined || gid === undefined || uid === 0)
14
- throw new Error("Run the managed Docker launcher as your logged-in user, not with sudo; grant that account access to Docker.");
14
+ throw new Error("Don't run the --docker launcher with sudo or as root. Run it as your normal user, and if Docker then says permission denied, give your user access: sudo usermod -aG docker $USER, then log out and back in.");
15
15
  }
16
16
  export async function prepareDockerHostCli(options) {
17
17
  if (options.disabled)
@@ -24,16 +24,16 @@ export async function prepareDockerHostCli(options) {
24
24
  }
25
25
  catch (error) {
26
26
  if (!hasCode(error, "ENOENT"))
27
- throw new Error("Host CLI detection failed. No container was changed.");
27
+ throw new Error(`Could not check whether ${hostCommandName(id)} is installed on this machine (${error instanceof Error ? error.message : String(error)}). Nothing was changed. Fix the problem, or start with --host-cli=off to use API keys only.`);
28
28
  }
29
29
  }
30
30
  if (!installed)
31
31
  return {};
32
32
  if (process.platform !== "linux")
33
- throw new Error("Managed host CLI setup requires Linux/systemd. Use --host-cli=off for API-only Docker.");
33
+ throw new Error("Using this machine's AI CLIs from Docker needs Linux with systemd. Start with --host-cli=off to use API keys only.");
34
34
  const uid = process.getuid?.();
35
35
  if (uid === undefined || uid === 0)
36
- throw new Error("Run the Docker launcher as your logged-in user, not root; or use --host-cli=off.");
36
+ throw new Error("Don't run the --docker launcher with sudo or as root; run it as your normal user. Or start with --host-cli=off to use API keys only.");
37
37
  const receiptPath = join(options.root, "consent.json");
38
38
  const existing = await privateRead(receiptPath, uid);
39
39
  const receipt = z.object({ version: z.literal(1), automaticStartup: z.literal(true) }).strict();
@@ -43,15 +43,15 @@ export async function prepareDockerHostCli(options) {
43
43
  approved ||= receipt.safeParse(JSON.parse(existing)).success;
44
44
  }
45
45
  catch {
46
- throw new Error("Invalid host helper consent receipt.");
46
+ throw new Error(`The saved answer to the host CLI question (${receiptPath}) is damaged. Delete that file and run the launcher again to be asked again.`);
47
47
  }
48
48
  }
49
49
  if (!approved) {
50
50
  if (!options.interactive)
51
- throw new Error("Host CLI setup needs confirmation. Use --accept-host-cli to approve, or --host-cli=off for API-only Docker.");
51
+ throw new Error("Slopify needs your OK to use the Claude Code, Codex or Gemini CLI installed on this machine, but can't ask because this isn't an interactive terminal. Rerun with --accept-host-cli to allow it, or with --host-cli=off to use API keys only.");
52
52
  approved = await options.prompt("Allow Slopify to run your host Claude Code, Codex and Gemini CLIs using their existing logins? This installs a private helper and enables automatic startup. User lingering keeps your user services running after logout and at boot. Credentials stay on the host. [y/N] ");
53
53
  if (!approved)
54
- throw new Error("Host CLI setup declined. The container is unchanged. Use --host-cli=off for API-only Docker.");
54
+ throw new Error("You declined, so Slopify did not set up your host AI CLIs and nothing was changed. Run the launcher again and answer y to allow it, or add --host-cli=off to start with API keys only.");
55
55
  }
56
56
  const inspect = () => options.runner.exec("docker", [
57
57
  "image",
@@ -64,14 +64,14 @@ export async function prepareDockerHostCli(options) {
64
64
  if (image.code !== 0 || image.stdout.trim() !== String(hostCliProtocol)) {
65
65
  const pulled = await options.runner.exec("docker", ["pull", options.image], options.signal);
66
66
  if (pulled.code !== 0)
67
- throw new Error("Could not pull a compatible Docker image. No helper or container was changed.");
67
+ throw new Error(`Could not download the Slopify Docker image ${options.image}. Check your internet connection and that Docker is running (docker info), then try again. Nothing was changed.`);
68
68
  image = await inspect();
69
69
  if (image.code !== 0 || image.stdout.trim() !== String(hostCliProtocol))
70
- throw new Error("This Docker image does not support the host helper. Update the image before retrying.");
70
+ throw new Error(`The Docker image ${options.image} is too old to use the AI CLIs on this machine. Use the latest image (unset SLOPIFY_DOCKER_IMAGE, or point it to a current release) and try again, or start with --host-cli=off.`);
71
71
  }
72
72
  const supported = await options.runner.exec("systemctl", ["--user", "list-units", "--no-legend", "--no-pager", "slopify-cli-bridge.service"], options.signal);
73
73
  if (supported.code !== 0)
74
- throw new Error("The systemd user service is unavailable. Use a normal Slopify install or --host-cli=off for API-only Docker.");
74
+ throw new Error("Using this machine's AI CLIs from Docker needs systemd user services (systemctl --user), which aren't available here. Start with --host-cli=off to use API keys only, or run Slopify without Docker: npx @gentbajko/slopify.");
75
75
  const paths = await prepareHostPaths(options.root);
76
76
  const lock = join(paths.root, "setup.lock");
77
77
  const deadline = Date.now() + 30_000;
@@ -84,7 +84,7 @@ export async function prepareDockerHostCli(options) {
84
84
  if (!hasCode(error, "EEXIST"))
85
85
  throw error;
86
86
  if (Date.now() >= deadline)
87
- throw new Error("Another host helper setup holds setup.lock. Wait for it to finish; the container is unchanged.");
87
+ throw new Error(`Another Slopify launcher is setting up the host CLI helper right now. Wait for it to finish and try again. If no other launcher is running, delete the folder ${lock} and retry.`);
88
88
  await delay(100, undefined, { signal: options.signal });
89
89
  }
90
90
  }
@@ -1,15 +1,18 @@
1
1
  import { constants } from "node:fs";
2
2
  import { open } from "node:fs/promises";
3
+ import { homedir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import { parseArgs } from "node:util";
5
6
  import { nodeRunCli } from "../adapters/llm/run-cli.js";
6
7
  import { hostEnvironmentSchema } from "../host-cli/environment.js";
8
+ import { launchXdgOpen } from "../host-cli/open-folder.js";
7
9
  import { ensureBridgeToken, prepareHostPaths } from "../host-cli/paths.js";
8
10
  import { createHostRuntime } from "../host-cli/runtime.js";
9
11
  import { startHostServer } from "../host-cli/server.js";
10
12
  import { resolveHostCommand } from "../host-cli/status.js";
11
13
  import { readVersion } from "../kernel/version.js";
12
14
  import { nodeCliProbe } from "../slices/settings/cli-status.js";
15
+ import { dockerRoot } from "./docker-projects/state.js";
13
16
  const { values } = parseArgs({ options: { "state-dir": { type: "string" } }, strict: true });
14
17
  if (!values["state-dir"])
15
18
  throw new Error("A host helper state directory is required.");
@@ -32,6 +35,14 @@ try {
32
35
  finally {
33
36
  await file.close();
34
37
  }
38
+ // Without a usable data folder there are no launcher receipts, so Open folder stays unsupported.
39
+ let folderRoot;
40
+ try {
41
+ folderRoot = dockerRoot(process.env, process.env.HOME || homedir());
42
+ }
43
+ catch {
44
+ folderRoot = undefined;
45
+ }
35
46
  const server = await startHostServer({
36
47
  directory: paths.share,
37
48
  token: await ensureBridgeToken(paths.tokenFile),
@@ -42,6 +53,9 @@ const server = await startHostServer({
42
53
  env: process.env,
43
54
  now: Date.now,
44
55
  resolve: (id) => resolveHostCommand(id, process.env),
56
+ ...(process.getuid === undefined || folderRoot === undefined
57
+ ? {}
58
+ : { folders: { root: folderRoot, uid: process.getuid(), launch: launchXdgOpen } }),
45
59
  }),
46
60
  });
47
61
  process.on("SIGHUP", server.pauseAdmissions);
@@ -91,15 +91,15 @@ export function actionRoutes(deps) {
91
91
  "rebuild-required": 409,
92
92
  };
93
93
  const messages = {
94
- "no-project": "No project has that id.",
95
- running: "Pause the run and wait for its active calls to stop before changing providers.",
96
- "not-editable": "Providers can be changed only while a project is paused or failed.",
97
- "invalid-providers": "The provider choices need attention.",
98
- "catalog-unavailable": "The provider model catalog could not be loaded. Try again.",
99
- "revision-required": "Reload the project before using this control. A current revision and request ID are required.",
100
- conflict: "The project changed. Reload it before using this control.",
101
- "idempotency-conflict": "This request ID has already been used for a different action.",
102
- "rebuild-required": "Review the affected outputs and cost before rebuilding this revision.",
94
+ "no-project": "This project no longer exists. Go back to Projects to pick another.",
95
+ running: "This project is still running. Pause it and wait for the current step to stop before changing providers.",
96
+ "not-editable": "Providers can be changed only while the project is paused or has failed. Pause it first, then try again.",
97
+ "invalid-providers": "Some provider choices are not valid. Fix the highlighted fields and try again.",
98
+ "catalog-unavailable": "Slopify could not load the list of available models. Wait a moment and try again; if it keeps failing, check Settings → Models.",
99
+ "revision-required": "This page is out of date. Reload the page, then try again.",
100
+ conflict: "This project changed since the page loaded. Reload the page to see the latest, then try again.",
101
+ "idempotency-conflict": "This action was already sent with different details. Reload the page, then try again.",
102
+ "rebuild-required": "This change needs some outputs to be made again. In the Edit tab, use Rebuild affected outputs to see what will be regenerated and what it costs.",
103
103
  };
104
104
  return problem(c, {
105
105
  status: codes[result.reason],
@@ -117,7 +117,7 @@ export function actionRoutes(deps) {
117
117
  return problem(c, {
118
118
  status: 503,
119
119
  title: titleOf(503),
120
- detail: "Recovery is unavailable. Try again after restart.",
120
+ detail: "Resume and retry are not ready yet because Slopify is still starting. Wait a moment, reload the page and try again.",
121
121
  });
122
122
  const result = recoveryResultSchema.parse(await recoverProject(deps.rebuild, id, { ...input, action }));
123
123
  if (result.ok)
@@ -128,26 +128,28 @@ export function actionRoutes(deps) {
128
128
  ? 400
129
129
  : 409;
130
130
  const messages = {
131
- "no-project": "This project no longer exists.",
132
- "no-revision": "This revision no longer exists. Reload the project.",
133
- conflict: "The saved project changed. Reload it and try Resume.",
134
- "idempotency-conflict": "This request ID belongs to another action. Reload the project.",
135
- "invalid-edit": "This section needs attention in Edit project before rerunning.",
136
- "stale-preview": "The required work changed. Try Resume to check it again.",
137
- "invalid-selection": "This section has no generated work to rerun. Use Edit project to change its source.",
138
- "review-required": "Changed supplied content or manual captions need Edit project or optional Advanced rebuild review.",
139
- "cost-ack-required": "Use optional Advanced rebuild review for this request.",
140
- readiness: "Check the provider, model, voice or source files, then try Resume.",
141
- running: "Wait for this section to finish, or Pause the project before rerunning it.",
142
- "accepted-job": "This section cannot be rerun yet.",
143
- "control-changed": "A newer control action took precedence. Check the project before using Resume.",
131
+ "no-project": "This project no longer exists. Go back to Projects to pick another.",
132
+ "no-revision": "The project's saved settings changed since the page loaded. Reload the page, then try again.",
133
+ conflict: "This project changed since the page loaded. Reload the page to see the latest, then use Resume again.",
134
+ "idempotency-conflict": "This action was already sent with different details. Reload the page, then try again.",
135
+ "invalid-edit": "This section has settings that need fixing first. Open the Edit tab, choose Edit project and correct this section, then try again.",
136
+ "stale-preview": "What needs to be made changed since the page loaded. Reload the page, then use Resume again.",
137
+ "invalid-selection": "This section has nothing generated to re-run because its content was supplied by you. To change it, open the Edit tab and choose Edit project.",
138
+ "review-required": "Content you supplied or captions you edited by hand would be replaced. Change them in the Edit tab with Edit project, or use Rebuild affected outputs to confirm the replacement.",
139
+ "cost-ack-required": "This re-run needs you to review its cost first. In the Edit tab, use Rebuild affected outputs.",
140
+ readiness: "Slopify cannot run this yet. Check the provider, model, voice and any files you supplied, then use Resume.",
141
+ running: "This section is still running. Wait for it to finish, or Pause the project, before re-running it.",
142
+ "accepted-job": "This section has a finished provider job that Slopify has not collected yet.",
143
+ "control-changed": "Another action on this project (such as Pause or Cancel) happened at the same time. Reload the page to see its current state, then try again.",
144
144
  };
145
145
  const detail = [
146
146
  messages[result.reason],
147
147
  ...(result.fields ?? []).map((row) => row.message),
148
148
  ...(result.intentRevisionId === undefined
149
149
  ? []
150
- : ["The rerun revision was saved. Use Resume to continue it; do not rerun again."]),
150
+ : [
151
+ "Your re-run request was saved. Use Resume to continue it instead of re-running again.",
152
+ ]),
151
153
  ].join(" ");
152
154
  return problem(c, {
153
155
  status,
@@ -218,7 +220,7 @@ function revisionRequired(c) {
218
220
  return problem(c, {
219
221
  status: 409,
220
222
  title: titleOf(409),
221
- detail: "Open Edit project to save changes, then review the affected rebuild.",
223
+ detail: "This change is now made from the Edit tab. Choose Edit project, save your change, then use Rebuild affected outputs.",
222
224
  extensions: { reason: "revision-required" },
223
225
  });
224
226
  }
@@ -80,14 +80,14 @@ export function createApp(deps) {
80
80
  return problem(c, {
81
81
  status: 503,
82
82
  title: titleOf(503),
83
- detail: "Docker installation is being verified. Wait for the launcher to finish.",
83
+ detail: "Slopify is still finishing its Docker setup. Wait for the launcher to finish, then try again.",
84
84
  });
85
85
  const releaseRequest = deps.mutations?.begin();
86
86
  if (deps.mutations !== undefined && releaseRequest === undefined)
87
87
  return problem(c, {
88
88
  status: 503,
89
89
  title: titleOf(503),
90
- detail: "Slopify is shutting down. Wait for it to restart before making changes.",
90
+ detail: "Slopify is shutting down, so changes cannot be saved right now. Start it again, reload the page and try again.",
91
91
  });
92
92
  const updateRoute = [
93
93
  "/api/update",
@@ -101,7 +101,7 @@ export function createApp(deps) {
101
101
  return problem(c, {
102
102
  status: 409,
103
103
  title: titleOf(409),
104
- detail: "Slopify is updating. Wait for it to restart before making changes.",
104
+ detail: "Slopify is updating. Wait for it to restart, reload the page and try again.",
105
105
  });
106
106
  }
107
107
  try {
@@ -49,6 +49,6 @@ function missing(c) {
49
49
  return problem(c, {
50
50
  status: 404,
51
51
  title: titleOf(404),
52
- detail: "This live audio preview is no longer available. Finished narration is available in the audio outputs.",
52
+ detail: "This live audio preview has ended. Play the finished narration from the project's audio outputs instead.",
53
53
  });
54
54
  }
@@ -20,7 +20,11 @@ function refused(c, reason) {
20
20
  return problem(c, {
21
21
  status,
22
22
  title: titleOf(status),
23
- detail: "Reload the current checkpoint status before approving or changing this gate.",
23
+ detail: reason === "not-found"
24
+ ? "This checkpoint no longer exists; the project may have moved on or changed. Reload the page to see where it is now."
25
+ : reason === "invalid-input"
26
+ ? "This checkpoint answer could not be read. Reload the page and try again."
27
+ : "This checkpoint changed since the page loaded, for example it was already approved. Reload the page to see its current state, then try again.",
24
28
  extensions: { reason },
25
29
  });
26
30
  }
@@ -29,7 +29,9 @@ export function diagnosticsRoutes(deps) {
29
29
  ...(provider.cliPath === undefined ? {} : { cliPath: provider.cliPath }),
30
30
  })),
31
31
  projects: { total: typeof projectCount?.count === "number" ? projectCount.count : 0 },
32
- catalogue: deps.catalogue?.status() ?? { warning: "Catalogue unavailable." },
32
+ catalogue: deps.catalogue?.status() ?? {
33
+ warning: "The model list (models.yaml) is not loaded yet. Wait a moment and reload the page; if it stays like this, restart Slopify.",
34
+ },
33
35
  });
34
36
  });
35
37
  }
@@ -32,7 +32,7 @@ export function draftFileRoutes(service) {
32
32
  return problem(c, {
33
33
  status: 400,
34
34
  title: titleOf(400),
35
- detail: "The request carried no file part.",
35
+ detail: "No file arrived with the upload. Choose the file again and retry.",
36
36
  });
37
37
  return result.ok
38
38
  ? c.json(draftAttachmentSchema.parse(result.value))
@@ -57,7 +57,7 @@ export function draftFileRoutes(service) {
57
57
  return problem(c, {
58
58
  status: 404,
59
59
  title: titleOf(404),
60
- detail: "This draft attachment has no preview bytes.",
60
+ detail: "This attached file has not finished uploading, so it cannot be previewed yet. Wait a moment or attach it again.",
61
61
  });
62
62
  return c.body(Readable.toWeb(createReadStream(stagingPath(deps.paths, staged.path))), 200, {
63
63
  "content-type": contentType,
@@ -6,10 +6,20 @@ export function draftProblem(c, result) {
6
6
  ? 400
7
7
  : 409;
8
8
  const { ok: _ok, ...extensions } = result;
9
+ const details = {
10
+ "not-found": "This draft no longer exists; it may have been started or deleted. Reload the page to start a new one.",
11
+ "invalid-draft": "This saved draft could not be read. Reload the page and set the video up again in Play.",
12
+ conflict: "This draft changed in another tab or window. Reload the page to see the latest, then make your change again.",
13
+ "invalid-edit": "Some settings need fixing. Fix the highlighted fields, then try again.",
14
+ "pending-start": "This video is already starting. Wait a moment, then find it in Projects.",
15
+ "already-started": "This draft was already started. Find the video in Projects.",
16
+ "stale-review": "Something changed since you reviewed this video, such as a setting or a price. Choose Review and start again to see the latest.",
17
+ readiness: "Slopify is not ready to start this video yet. Fix the highlighted problems, then try again.",
18
+ };
9
19
  return problem(c, {
10
20
  status,
11
21
  title: titleOf(status),
12
- detail: "Review the saved draft and the listed fields.",
22
+ detail: details[result.reason],
13
23
  extensions,
14
24
  });
15
25
  }
@@ -58,10 +58,10 @@ function disposition(filename) {
58
58
  }
59
59
  function missing(c, reason) {
60
60
  const details = {
61
- "unknown-project": "No project has that id.",
62
- "unknown-asset": "This project has no such file.",
63
- "missing-file": "This file is recorded but is no longer on disk; re-run the stage.",
64
- "no-images": "This project has no images or thumbnail yet.",
61
+ "unknown-project": "This project no longer exists. Go back to Projects to pick another.",
62
+ "unknown-asset": "This project has no file by that name. Reload the page to see its current files.",
63
+ "missing-file": "This file was deleted or moved from the project folder. Use Re-run section on the project page to make it again.",
64
+ "no-images": "This project has no images or thumbnail yet. Wait for the Images step to finish, then try again.",
65
65
  };
66
66
  return problem(c, { status: 404, title: titleOf(404), detail: details[reason] });
67
67
  }
@@ -1,11 +1,8 @@
1
1
  import { z } from "zod";
2
- export const folderReplySchema = z.discriminatedUnion("opened", [
2
+ const dockerHost = { location: z.literal("docker-host"), path: z.string().min(1) };
3
+ // In Docker the host helper may open the folder itself; the host path is sent either way.
4
+ export const folderReplySchema = z.union([
3
5
  z.object({ opened: z.literal(true) }).strict(),
4
- z
5
- .object({
6
- opened: z.literal(false),
7
- location: z.literal("docker-host"),
8
- path: z.string().min(1),
9
- })
10
- .strict(),
6
+ z.object({ opened: z.literal(true), ...dockerHost }).strict(),
7
+ z.object({ opened: z.literal(false), ...dockerHost }).strict(),
11
8
  ]);
@@ -10,7 +10,7 @@ export async function replyForFolder(c, deps, projectId, file) {
10
10
  return problem(c, {
11
11
  status: 503,
12
12
  title: titleOf(503),
13
- detail: "This Docker installation has no verified host project folder. Downloads remain available. Rerun the managed Docker launcher to set up host files.",
13
+ detail: "Slopify cannot show where this file is on your computer because its Docker project folder was not set up. You can still use the download buttons, or run the Slopify Docker launcher again to set up the folder.",
14
14
  });
15
15
  try {
16
16
  const r = relative(deps.paths.projects, file);
@@ -27,11 +27,16 @@ export async function replyForFolder(c, deps, projectId, file) {
27
27
  if (s.isSymbolicLink() || (i === parts.length - 1 ? !s.isFile() : !s.isDirectory()))
28
28
  throw new Error("Unavailable output");
29
29
  }
30
- return c.json(folderReplySchema.parse({
31
- opened: false,
32
- location: "docker-host",
33
- path: join(host, dirname(r)),
34
- }));
30
+ const hostPath = join(host, dirname(r));
31
+ const opened = (await deps.folderLocation
32
+ .openOnHost?.(hostPath, AbortSignal.timeout(10_000))
33
+ .catch(() => false)) === true;
34
+ if (!opened && deps.folderLocation.openOnHost)
35
+ deps.log.write("warn", "project.open-folder", {
36
+ projectId,
37
+ detail: "The host helper did not open the folder (not installed, too old, or it refused), so the host path was shown instead.",
38
+ });
39
+ return c.json(folderReplySchema.parse({ opened, location: "docker-host", path: hostPath }));
35
40
  }
36
41
  catch {
37
42
  deps.log.write("warn", "project.locate-folder", {
@@ -41,7 +46,7 @@ export async function replyForFolder(c, deps, projectId, file) {
41
46
  return problem(c, {
42
47
  status: 404,
43
48
  title: titleOf(404),
44
- detail: "This saved output folder is no longer available.",
49
+ detail: "This output's folder was deleted or moved. Use Re-run section on the project page to make the file again.",
45
50
  });
46
51
  }
47
52
  }
@@ -59,7 +64,7 @@ export async function replyForFolder(c, deps, projectId, file) {
59
64
  return problem(c, {
60
65
  status: 503,
61
66
  title: titleOf(503),
62
- detail: "Could not open the file manager on the machine running Slopify. Make sure a desktop session is available.",
67
+ detail: "Slopify could not open a file manager window on the computer it runs on. Make sure you are signed in to that computer's desktop, or use the download buttons instead.",
63
68
  });
64
69
  }
65
70
  }
@@ -17,14 +17,14 @@ export function fontsRoutes(deps) {
17
17
  return problem(c, {
18
18
  status: 415,
19
19
  title: titleOf(415),
20
- detail: "Upload one .ttf or .otf file as multipart/form-data.",
20
+ detail: "The font was not sent as a file. Reload the page and choose a .ttf or .otf file again.",
21
21
  });
22
22
  const body = c.req.raw.body;
23
23
  if (body === null)
24
24
  return problem(c, {
25
25
  status: 400,
26
26
  title: titleOf(400),
27
- detail: "The request carried no font file.",
27
+ detail: "No font file arrived. Choose a .ttf or .otf file and try again.",
28
28
  });
29
29
  const part = await readUpload(contentType, body);
30
30
  if (!part.ok) {
@@ -33,8 +33,8 @@ export function fontsRoutes(deps) {
33
33
  status: tooLarge ? 413 : 400,
34
34
  title: tooLarge ? "Content Too Large" : titleOf(400),
35
35
  detail: tooLarge
36
- ? "A font file must be no larger than 32 MiB."
37
- : "Upload exactly one complete font file in the file part.",
36
+ ? "This font file is larger than 32 MB. Choose a smaller .ttf or .otf file."
37
+ : "The font upload did not arrive complete. Choose one .ttf or .otf file and try again.",
38
38
  });
39
39
  }
40
40
  const result = await uploadFont(deps.paths, part.upload);
@@ -44,7 +44,11 @@ export function fontsRoutes(deps) {
44
44
  })
45
45
  .get("/:id/file", zValidator("param", idParam, (result, c) => {
46
46
  if (!result.success)
47
- return problem(c, { status: 404, title: titleOf(404), detail: "No font has that id." });
47
+ return problem(c, {
48
+ status: 404,
49
+ title: titleOf(404),
50
+ detail: "This font no longer exists; it may have been deleted. Reload the page to see your current fonts.",
51
+ });
48
52
  return undefined;
49
53
  }), async (c) => {
50
54
  try {
@@ -58,7 +62,11 @@ export function fontsRoutes(deps) {
58
62
  catch (error) {
59
63
  if (!isMissingFont(error))
60
64
  throw error;
61
- return problem(c, { status: 404, title: titleOf(404), detail: "No font has that id." });
65
+ return problem(c, {
66
+ status: 404,
67
+ title: titleOf(404),
68
+ detail: "This font no longer exists; it may have been deleted. Reload the page to see your current fonts.",
69
+ });
62
70
  }
63
71
  });
64
72
  }
@@ -127,21 +135,25 @@ function failure(reason) {
127
135
  return {
128
136
  status: 413,
129
137
  title: "Content Too Large",
130
- detail: "A font file must be no larger than 32 MiB.",
138
+ detail: "This font file is larger than 32 MB. Choose a smaller .ttf or .otf file.",
131
139
  };
132
140
  case "unsupported-format":
133
- return { status: 415, title: titleOf(415), detail: "Choose a .ttf or .otf font file." };
141
+ return {
142
+ status: 415,
143
+ title: titleOf(415),
144
+ detail: "Slopify only accepts .ttf or .otf font files. Choose a file with one of those endings.",
145
+ };
134
146
  case "unsafe-filename":
135
147
  return {
136
148
  status: 400,
137
149
  title: titleOf(400),
138
- detail: "The font filename cannot contain a path or control character.",
150
+ detail: "The font's file name contains characters Slopify cannot use. Rename the file on your computer, then upload it again.",
139
151
  };
140
152
  case "invalid-font":
141
153
  return {
142
154
  status: 400,
143
155
  title: titleOf(400),
144
- detail: "This file is not a supported, valid TrueType or OpenType font.",
156
+ detail: "This file is not a valid TrueType (.ttf) or OpenType (.otf) font; it may be damaged. Download the font again or choose another.",
145
157
  };
146
158
  }
147
159
  }