wizards 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -42
- package/apps/runtime/dist/agents/studio-assistant.js +3 -3
- package/apps/runtime/dist/app.js +7 -4
- package/apps/runtime/dist/auth/index.js +2 -2
- package/apps/runtime/dist/authoring/guide.js +1 -1
- package/apps/runtime/dist/cli/account.js +31 -28
- package/apps/runtime/dist/cli/autostart.js +5 -3
- package/apps/runtime/dist/cli/clients.js +1 -1
- package/apps/runtime/dist/cli/connect.js +24 -24
- package/apps/runtime/dist/cli/features.js +6 -6
- package/apps/runtime/dist/cli/main.js +51 -39
- package/apps/runtime/dist/cli/mcp.js +5 -4
- package/apps/runtime/dist/cli/menu.js +32 -30
- package/apps/runtime/dist/cli/report.js +7 -9
- package/apps/runtime/dist/cli/setup.js +42 -38
- package/apps/runtime/dist/cli/start.js +16 -15
- package/apps/runtime/dist/cli/status.js +20 -20
- package/apps/runtime/dist/cli/ui.js +9 -2
- package/apps/runtime/dist/cli/update.js +6 -6
- package/apps/runtime/dist/cli/validate.js +1 -1
- package/apps/runtime/dist/connectors/external.js +3 -2
- package/apps/runtime/dist/connectors/index.js +5 -5
- package/apps/runtime/dist/connectors/plugin-connections.js +2 -2
- package/apps/runtime/dist/connectors/via-account.js +5 -5
- package/apps/runtime/dist/db/control-schema.js +14 -0
- package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
- package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +7 -0
- package/apps/runtime/dist/docs/dev/index.md +16 -14
- package/apps/runtime/dist/docs/dev/plugins/connections.md +27 -24
- package/apps/runtime/dist/docs/dev/plugins/events.md +19 -17
- package/apps/runtime/dist/docs/dev/plugins/examples.md +13 -12
- package/apps/runtime/dist/docs/dev/plugins/index.md +29 -24
- package/apps/runtime/dist/docs/dev/plugins/layout.md +20 -19
- package/apps/runtime/dist/docs/dev/plugins/quick-start.md +16 -15
- package/apps/runtime/dist/docs/dev/plugins/reference.md +52 -40
- package/apps/runtime/dist/docs/dev/plugins/reloading.md +24 -23
- package/apps/runtime/dist/docs/dev/plugins/routes.md +26 -22
- package/apps/runtime/dist/docs/dev/plugins/runners.md +49 -44
- package/apps/runtime/dist/docs/dev/plugins/server.md +95 -43
- package/apps/runtime/dist/docs/dev/plugins/shipping.md +19 -16
- package/apps/runtime/dist/docs/dev/plugins/space.md +75 -69
- package/apps/runtime/dist/docs/dev/plugins/starters.md +22 -17
- package/apps/runtime/dist/docs/dev/plugins/studio.md +80 -37
- package/apps/runtime/dist/docs/dev/plugins/styling.md +44 -41
- package/apps/runtime/dist/docs/dev/plugins/tables.md +11 -11
- package/apps/runtime/dist/docs/dev/plugins/tools.md +26 -25
- package/apps/runtime/dist/docs/dev/wizard-format.md +35 -23
- package/apps/runtime/dist/docs/user/ai-apps.md +46 -43
- package/apps/runtime/dist/docs/user/assistant.md +41 -36
- package/apps/runtime/dist/docs/user/build-a-wizard.md +64 -39
- package/apps/runtime/dist/docs/user/command-line.md +47 -44
- package/apps/runtime/dist/docs/user/connectors.md +40 -35
- package/apps/runtime/dist/docs/user/create-account.md +27 -22
- package/apps/runtime/dist/docs/user/data.md +29 -29
- package/apps/runtime/dist/docs/user/first-start.md +26 -28
- package/apps/runtime/dist/docs/user/index.md +16 -17
- package/apps/runtime/dist/docs/user/install.md +28 -27
- package/apps/runtime/dist/docs/user/local-only.md +21 -17
- package/apps/runtime/dist/docs/user/models.md +92 -84
- package/apps/runtime/dist/docs/user/plugins.md +28 -23
- package/apps/runtime/dist/docs/user/run.md +51 -50
- package/apps/runtime/dist/docs/user/server.md +9 -9
- package/apps/runtime/dist/docs/user/space.md +87 -65
- package/apps/runtime/dist/docs/user/steps.md +73 -69
- package/apps/runtime/dist/docs/user/test-and-share.md +127 -105
- package/apps/runtime/dist/docs/user/troubleshooting.md +40 -38
- package/apps/runtime/dist/docs/user/wordpress.md +62 -54
- package/apps/runtime/dist/documents/parse.js +2 -2
- package/apps/runtime/dist/engenty/shims/web-ingest.js +17 -1
- package/apps/runtime/dist/engine/input.js +4 -4
- package/apps/runtime/dist/engine/push.js +2 -2
- package/apps/runtime/dist/engine/runner.js +8 -8
- package/apps/runtime/dist/engine/steps.js +2 -2
- package/apps/runtime/dist/engine/surface.js +1 -1
- package/apps/runtime/dist/film/step.js +3 -3
- package/apps/runtime/dist/harness/claude.js +1 -1
- package/apps/runtime/dist/harness/codex.js +1 -1
- package/apps/runtime/dist/harness/model.js +2 -2
- package/apps/runtime/dist/harness/terminal.js +3 -3
- package/apps/runtime/dist/mcp/handler.js +2 -2
- package/apps/runtime/dist/mcp/tools.js +9 -9
- package/apps/runtime/dist/models.js +15 -15
- package/apps/runtime/dist/plugins/claims.js +56 -0
- package/apps/runtime/dist/plugins/loader.js +2 -0
- package/apps/runtime/dist/plugins/public.js +2 -3
- package/apps/runtime/dist/plugins/runs.js +13 -8
- package/apps/runtime/dist/render/chromium.js +24 -11
- package/apps/runtime/dist/routes/api.js +15 -9
- package/apps/runtime/dist/routes/debug.js +1 -1
- package/apps/runtime/dist/routes/delivery.js +46 -0
- package/apps/runtime/dist/routes/lang.js +7 -0
- package/apps/runtime/dist/routes/marketplace.js +2 -1
- package/apps/runtime/dist/routes/plugin-public.js +2 -1
- package/apps/runtime/dist/routes/plugins.js +13 -14
- package/apps/runtime/dist/routes/runs.js +59 -49
- package/apps/runtime/dist/routes/studio.js +53 -23
- package/apps/runtime/dist/services/access.js +3 -3
- package/apps/runtime/dist/services/assistant-chats.js +1 -1
- package/apps/runtime/dist/services/brand.js +2 -2
- package/apps/runtime/dist/services/cloud.js +5 -4
- package/apps/runtime/dist/services/errors.js +2 -1
- package/apps/runtime/dist/services/knowledge-model.js +2 -2
- package/apps/runtime/dist/services/marketplace.js +2 -2
- package/apps/runtime/dist/services/package-file.js +12 -9
- package/apps/runtime/dist/services/package.js +2 -2
- package/apps/runtime/dist/services/project-files.js +10 -12
- package/apps/runtime/dist/services/projects.js +13 -8
- package/apps/runtime/dist/services/runners.js +4 -3
- package/apps/runtime/dist/services/runs.js +3 -3
- package/apps/runtime/dist/services/site.js +2 -2
- package/apps/runtime/dist/services/space-categories.js +4 -4
- package/apps/runtime/dist/services/space-data.js +13 -13
- package/apps/runtime/dist/services/space-origin.js +1 -1
- package/apps/runtime/dist/services/spaces.js +7 -7
- package/apps/runtime/dist/services/wizards.js +5 -5
- package/apps/runtime/dist/tools/net-guard.js +135 -24
- package/apps/runtime/dist/tools/plugin.js +1 -1
- package/apps/runtime/dist/tools/store.js +1 -1
- package/apps/runtime/dist/widgets/step.js +1 -1
- package/apps/web/dist/assets/AboutDialog-DaYwk84X.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-BXpkgatF.js → ChangelogDialog-CsYbEayb.js} +1 -1
- package/apps/web/dist/assets/{ChatView-t7z44uFN.js → ChatView-BRD3fKZU.js} +1 -1
- package/apps/web/dist/assets/{EditorPage-4qxNJgKr.js → EditorPage-ocPlZD3E.js} +3 -3
- package/apps/web/dist/assets/{FlowPreview-WpS8DCPl.js → FlowPreview-Bm7sv746.js} +1 -1
- package/apps/web/dist/assets/{Harness-DL7Bse40.js → Harness-DKE-YNA5.js} +1 -1
- package/apps/web/dist/assets/{NewWizardChannels-Crt6PzH2.js → NewWizardChannels-CnWckqjO.js} +1 -1
- package/apps/web/dist/assets/{NewWizardPage-COiVmNsl.js → NewWizardPage-BXTpbHVM.js} +1 -1
- package/apps/web/dist/assets/{OnboardingPage-BwluEqXq.js → OnboardingPage-3Rz5uOsa.js} +1 -1
- package/apps/web/dist/assets/{ProjectPage-BIRU_AlN.js → ProjectPage-QdSJwm3k.js} +1 -1
- package/apps/web/dist/assets/{PublicRunner-BxMb4gEt.js → PublicRunner-DEpBer5g.js} +1 -1
- package/apps/web/dist/assets/{ReadOnly-D9r5ze5S.js → ReadOnly-rr_RJD-a.js} +1 -1
- package/apps/web/dist/assets/{RunDrawer-DmJl2QOJ.js → RunDrawer-BsZt0tL9.js} +1 -1
- package/apps/web/dist/assets/{RunnerView-CF_1rC-7.js → RunnerView-BbRukEdE.js} +1 -1
- package/apps/web/dist/assets/SettingsPage-qkWsQIMN.js +1 -0
- package/apps/web/dist/assets/{SetupPage-BGKtbS7j.js → SetupPage-PzvPPLfM.js} +1 -1
- package/apps/web/dist/assets/{SharePage-C4MqXKDA.js → SharePage-7vdDWG1n.js} +1 -1
- package/apps/web/dist/assets/changelog-BFdAYD28.js +1 -0
- package/apps/web/dist/assets/{esm-CsY8TkyE.js → esm-g0kaGp8t.js} +1 -1
- package/apps/web/dist/assets/{index-BjQvGQp_.js → index-BzPid1oQ.js} +4 -4
- package/apps/web/dist/assets/{lock-CI6TpWL-.js → lock-BK9RYIft.js} +1 -1
- package/apps/web/dist/assets/oss-credits-BJrR7BR1.js +1 -0
- package/apps/web/dist/assets/{phone-BrtQz-fG.js → phone-DtkY0NfU.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-BtEKL72n.js → settings-menu-7EJQRFOi.js} +1 -1
- package/apps/web/dist/assets/{ui-4IaTD7LB.js → ui-B6S5hYPd.js} +3 -3
- package/apps/web/dist/assets/{upload-CWJAtPRo.js → upload-rYCAy7ox.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/mcp/flow.html +3 -3
- package/bin/wizards.mjs +1 -1
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +21 -2
- package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +48 -48
- package/node_modules/@engenty-wizards/shared/dist/run.js +48 -48
- package/package.json +5 -4
- package/apps/web/dist/assets/AboutDialog-A_ILyeue.js +0 -1
- package/apps/web/dist/assets/SettingsPage-3xSInE-A.js +0 -1
- package/apps/web/dist/assets/changelog-CM_GdIVI.js +0 -1
- package/apps/web/dist/assets/oss-credits-BQQX0kvk.js +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { and, asc, eq, inArray, isNotNull, isNull, like } from "drizzle-orm";
|
|
3
4
|
import { accountToken, linkedAccount } from "../auth/account.js";
|
|
4
5
|
import { control, controlDb, db, schema } from "../db/client.js";
|
|
@@ -25,7 +26,7 @@ export async function cloudState(wizardId) {
|
|
|
25
26
|
async function call(method, path, body, timeoutMs = 30_000) {
|
|
26
27
|
const token = await accountToken();
|
|
27
28
|
if (!token) {
|
|
28
|
-
throw new ServiceError("refused", "The account is signed out.
|
|
29
|
+
throw new ServiceError("refused", bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again."), {
|
|
29
30
|
reason: "signed_out",
|
|
30
31
|
});
|
|
31
32
|
}
|
|
@@ -39,7 +40,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
|
|
|
39
40
|
signal: AbortSignal.timeout(timeoutMs),
|
|
40
41
|
}).catch(() => null);
|
|
41
42
|
if (!res) {
|
|
42
|
-
throw new ServiceError("refused", "The cloud
|
|
43
|
+
throw new ServiceError("refused", bilingual("Die Cloud ist gerade nicht erreichbar.", "The cloud can’t be reached right now."), {
|
|
43
44
|
reason: "unreachable",
|
|
44
45
|
});
|
|
45
46
|
}
|
|
@@ -49,7 +50,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
|
|
|
49
50
|
function refusal(answer, fallback) {
|
|
50
51
|
const { error, reason, ...rest } = answer.body;
|
|
51
52
|
const message = answer.status === 401
|
|
52
|
-
? "The account is signed out.
|
|
53
|
+
? bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again.")
|
|
53
54
|
: typeof error === "string" && error
|
|
54
55
|
? error
|
|
55
56
|
: fallback;
|
|
@@ -213,7 +214,7 @@ export async function syncToCloud(userId, wizardId) {
|
|
|
213
214
|
const w = await ownedWizard(userId, wizardId);
|
|
214
215
|
const version = await publishedVersion(w);
|
|
215
216
|
if (!version) {
|
|
216
|
-
throw new ServiceError("refused", "Publish the wizard first.");
|
|
217
|
+
throw new ServiceError("refused", bilingual("Veröffentliche den Wizard zuerst.", "Publish the wizard first."));
|
|
217
218
|
}
|
|
218
219
|
const before = await cloudState(w.id);
|
|
219
220
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
1
2
|
import { errorText, NotedError } from "../model-errors.js";
|
|
2
3
|
const STATUS = {
|
|
3
4
|
not_found: 404,
|
|
@@ -25,4 +26,4 @@ export class ServiceError extends NotedError {
|
|
|
25
26
|
return { error: errorText(this, lang), code: this.code, ...this.data };
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
export const notFound = () => new ServiceError("not_found", "
|
|
29
|
+
export const notFound = () => new ServiceError("not_found", bilingual("Das gibt es nicht oder nicht mehr.", "This doesn’t exist or was deleted."));
|
|
@@ -504,12 +504,12 @@ export async function writeSummary(valueId) {
|
|
|
504
504
|
where: eq(schema.spaceCategoryValue.id, valueId),
|
|
505
505
|
});
|
|
506
506
|
if (!value) {
|
|
507
|
-
throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "
|
|
507
|
+
throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "This value doesn’t exist."));
|
|
508
508
|
}
|
|
509
509
|
const category = await categoryRow(value.categoryId);
|
|
510
510
|
const { items, total } = await itemsOfValue(value.id);
|
|
511
511
|
if (!items.length) {
|
|
512
|
-
throw new ServiceError("invalid", "
|
|
512
|
+
throw new ServiceError("invalid", bilingual("Unter diesem Wert steht noch nichts.", "Nothing is filed under this value yet."));
|
|
513
513
|
}
|
|
514
514
|
const model = await textModel("standard");
|
|
515
515
|
const result = await generateText({
|
|
@@ -404,13 +404,13 @@ export async function marketplaceWizard(id, lang) {
|
|
|
404
404
|
return null;
|
|
405
405
|
}
|
|
406
406
|
if (!readable(found.definition)) {
|
|
407
|
-
throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version
|
|
407
|
+
throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version von engenty wizards. Aktualisiere zuerst engenty wizards.", "This template needs a newer version of engenty wizards. Update engenty wizards first."));
|
|
408
408
|
}
|
|
409
409
|
// A wizard whose plugins are not here would stop at its first step that uses them.
|
|
410
410
|
const have = await pluginIdsOf(currentTenant());
|
|
411
411
|
const needs = pluginsOf(wizardSchema.parse(found.definition)).filter((p) => !have.has(p));
|
|
412
412
|
if (needs.length) {
|
|
413
|
-
throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}:
|
|
413
|
+
throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: bei Pro auf engenty.ai dabei.`, `This template needs the ${needs.length === 1 ? "plugin" : "plugins"} ${needs.join(", ")}: included with Pro on engenty.ai.`));
|
|
414
414
|
}
|
|
415
415
|
return {
|
|
416
416
|
id: found.id,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
1
2
|
import { manifestSchema, PACKAGE_FILES, PACKAGE_FORMAT, PACKAGE_MANIFEST, PACKAGE_MAX_BYTES, } from "@engenty-wizards/shared/wizard-package";
|
|
2
3
|
import { cleanPath, WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
|
|
3
4
|
import { strFromU8, unzipSync } from "fflate";
|
|
@@ -6,13 +7,13 @@ import { ServiceError } from "./errors.js";
|
|
|
6
7
|
* Reading a `.wizard` package, with nothing of a project or a database: the studio's import and
|
|
7
8
|
* `wizards validate` both start here.
|
|
8
9
|
*/
|
|
9
|
-
const invalid = (
|
|
10
|
+
const invalid = (said) => new ServiceError("invalid", said);
|
|
10
11
|
export function json(data) {
|
|
11
12
|
try {
|
|
12
13
|
return JSON.parse(strFromU8(data));
|
|
13
14
|
}
|
|
14
15
|
catch {
|
|
15
|
-
throw invalid("This
|
|
16
|
+
throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json darin lässt sich nicht lesen.", "This isn’t a wizard file: its wizard.json can’t be read."));
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
/** A manifest, or a bare definition: a wizard without workspace is fully told by its JSON. */
|
|
@@ -22,7 +23,7 @@ export function readManifest(raw) {
|
|
|
22
23
|
}
|
|
23
24
|
const parsed = manifestSchema.safeParse(raw);
|
|
24
25
|
if (!parsed.success) {
|
|
25
|
-
throw invalid("This wizard
|
|
26
|
+
throw invalid(bilingual("Diese Wizard-Datei stammt aus einer neueren Version oder ist beschädigt. Aktualisiere engenty wizards oder exportiere den Wizard neu.", "This wizard file comes from a newer version, or it’s damaged. Update engenty wizards or export the wizard again."));
|
|
26
27
|
}
|
|
27
28
|
return parsed.data;
|
|
28
29
|
}
|
|
@@ -45,24 +46,26 @@ export function readPackage(data) {
|
|
|
45
46
|
total += file.originalSize;
|
|
46
47
|
count += 1;
|
|
47
48
|
if (file.originalSize > WORKSPACE_LIMITS.fileBytes) {
|
|
48
|
-
throw invalid(`${file.name} is larger than 5 MB.`);
|
|
49
|
+
throw invalid(bilingual(`${file.name} ist größer als 5 MB.`, `${file.name} is larger than 5 MB.`));
|
|
49
50
|
}
|
|
50
51
|
if (count > WORKSPACE_LIMITS.files + 1 || total > PACKAGE_MAX_BYTES) {
|
|
51
|
-
throw invalid("The
|
|
52
|
+
throw invalid(bilingual("Die Wizard-Datei ist zu groß: Ein Arbeitsbereich fasst höchstens 200 Dateien und 25 MB.", "The wizard file is too large: a workspace holds at most 200 files and 25 MB."));
|
|
52
53
|
}
|
|
53
54
|
return true;
|
|
54
55
|
},
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
catch (err) {
|
|
58
|
-
throw err instanceof ServiceError
|
|
59
|
+
throw err instanceof ServiceError
|
|
60
|
+
? err
|
|
61
|
+
: invalid(bilingual("Die Wizard-Datei lässt sich nicht öffnen. Exportiere den Wizard neu und versuche es noch einmal.", "The wizard file can’t be opened. Export the wizard again and try once more."));
|
|
59
62
|
}
|
|
60
63
|
// A package unpacked and zipped again often sits in a folder of its own.
|
|
61
64
|
const manifestPath = Object.keys(entries)
|
|
62
65
|
.filter((name) => name === PACKAGE_MANIFEST || name.endsWith(`/${PACKAGE_MANIFEST}`))
|
|
63
66
|
.sort((a, b) => a.length - b.length)[0];
|
|
64
67
|
if (manifestPath === undefined) {
|
|
65
|
-
throw invalid("This
|
|
68
|
+
throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json fehlt.", "This isn’t a wizard file: wizard.json is missing."));
|
|
66
69
|
}
|
|
67
70
|
const root = manifestPath.slice(0, -PACKAGE_MANIFEST.length);
|
|
68
71
|
const files = Object.entries(entries)
|
|
@@ -70,12 +73,12 @@ export function readPackage(data) {
|
|
|
70
73
|
.map(([name, content]) => {
|
|
71
74
|
const path = cleanPath(name.slice(root.length + PACKAGE_FILES.length));
|
|
72
75
|
if (!path) {
|
|
73
|
-
throw invalid(
|
|
76
|
+
throw invalid(bilingual(`„${name}“ ist kein gültiger Dateipfad.`, `“${name}” isn’t a valid file path.`));
|
|
74
77
|
}
|
|
75
78
|
return { path, data: content };
|
|
76
79
|
});
|
|
77
80
|
if (files.reduce((n, f) => n + f.data.byteLength, 0) > WORKSPACE_LIMITS.totalBytes) {
|
|
78
|
-
throw invalid("The workspace in the
|
|
81
|
+
throw invalid(bilingual("Der Arbeitsbereich in der Wizard-Datei ist größer als 25 MB.", "The workspace in the wizard file is larger than 25 MB."));
|
|
79
82
|
}
|
|
80
83
|
return { manifest: readManifest(json(entries[manifestPath])), files };
|
|
81
84
|
}
|
|
@@ -151,8 +151,8 @@ lang = "en") {
|
|
|
151
151
|
: `Imported${name ? ` from “${name}”` : ""}: ${draft.steps.length} steps, ${files.length} ${files.length === 1 ? "file" : "files"}.`,
|
|
152
152
|
lacks.length
|
|
153
153
|
? de
|
|
154
|
-
? `Diesem
|
|
155
|
-
: `This
|
|
154
|
+
? `Diesem Space fehlt noch: ${lacks.join(", ")}. Füge es unter Einstellungen → Connectors hinzu.`
|
|
155
|
+
: `This space still needs: ${lacks.join(", ")}. Add it under Settings → Connectors.`
|
|
156
156
|
: "",
|
|
157
157
|
]
|
|
158
158
|
.filter(Boolean)
|
|
@@ -28,7 +28,7 @@ export function fileView(row) {
|
|
|
28
28
|
description: row.description,
|
|
29
29
|
source: row.source,
|
|
30
30
|
status: stale ? "failed" : row.status,
|
|
31
|
-
error: stale ? "
|
|
31
|
+
error: stale ? "Reading was interrupted. Read it in again." : row.error,
|
|
32
32
|
indexed: row.indexed,
|
|
33
33
|
pages: row.pages,
|
|
34
34
|
chars: row.chars,
|
|
@@ -82,11 +82,11 @@ export async function addProjectFile(projectId, input) {
|
|
|
82
82
|
? bilingual("Ein Logo ist ein Bild (PNG, SVG, JPEG, WebP).", "A logo is an image (PNG, SVG, JPEG, WebP).")
|
|
83
83
|
: input.kind === "pattern"
|
|
84
84
|
? bilingual("Ein Muster ist ein Bild (SVG, PNG, JPEG, WebP).", "A pattern is an image (SVG, PNG, JPEG, WebP).")
|
|
85
|
-
: bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen
|
|
85
|
+
: bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen gehören ins Wissen.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models go here. Texts to look things up in go under knowledge."));
|
|
86
86
|
}
|
|
87
87
|
const limit = projectFileLimit(input.kind, mime);
|
|
88
88
|
if (!input.data.byteLength || input.data.byteLength > limit) {
|
|
89
|
-
throw new ServiceError("invalid", bilingual(`
|
|
89
|
+
throw new ServiceError("invalid", bilingual(`Wähl eine Datei bis ${Math.round(limit / 1_000_000)} MB.`, `Choose a file of ${Math.round(limit / 1_000_000)} MB or less.`));
|
|
90
90
|
}
|
|
91
91
|
const scope = and(eq(schema.projectFile.projectId, projectId), eq(schema.projectFile.kind, input.kind));
|
|
92
92
|
const [{ n, last }] = await db
|
|
@@ -94,7 +94,7 @@ export async function addProjectFile(projectId, input) {
|
|
|
94
94
|
.from(schema.projectFile)
|
|
95
95
|
.where(scope);
|
|
96
96
|
if (n >= PROJECT_LIMITS.files[input.kind]) {
|
|
97
|
-
throw new ServiceError("refused", bilingual(`
|
|
97
|
+
throw new ServiceError("refused", bilingual(`Hier haben höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien Platz.`, `There’s room for at most ${PROJECT_LIMITS.files[input.kind]} files here.`));
|
|
98
98
|
}
|
|
99
99
|
const name = input.name.slice(0, 120) || "datei";
|
|
100
100
|
const ref = await saveAsset({
|
|
@@ -150,15 +150,15 @@ export async function addProjectFileFromUrl(projectId, input) {
|
|
|
150
150
|
headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
|
|
151
151
|
signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
|
|
152
152
|
}).catch(() => {
|
|
153
|
-
throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address
|
|
153
|
+
throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address can’t be reached."));
|
|
154
154
|
});
|
|
155
155
|
if (!res.ok) {
|
|
156
|
-
throw new ServiceError("invalid", bilingual(`Die Adresse antwortete ${res.status}.`, `The address
|
|
156
|
+
throw new ServiceError("invalid", bilingual(`Die Adresse antwortete mit ${res.status}.`, `The address responded with ${res.status}.`));
|
|
157
157
|
}
|
|
158
158
|
const mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
159
159
|
if (mime === "text/html" && kind !== "document") {
|
|
160
160
|
await res.body?.cancel();
|
|
161
|
-
throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address
|
|
161
|
+
throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address leads to a web page, not an image."), {
|
|
162
162
|
reason: "page",
|
|
163
163
|
url: res.url,
|
|
164
164
|
});
|
|
@@ -296,9 +296,7 @@ async function prepare(row) {
|
|
|
296
296
|
console.error("[project-file]", row.name, err);
|
|
297
297
|
await set({
|
|
298
298
|
status: "failed",
|
|
299
|
-
error: err instanceof UnreadableDocument
|
|
300
|
-
? err.message
|
|
301
|
-
: "Reading did not work. Please try again.",
|
|
299
|
+
error: err instanceof UnreadableDocument ? err.message : "Reading didn’t work. Try again.",
|
|
302
300
|
});
|
|
303
301
|
}
|
|
304
302
|
});
|
|
@@ -382,7 +380,7 @@ export async function putOriginFile(origin, input) {
|
|
|
382
380
|
});
|
|
383
381
|
const mime = documentMime(input.name, input.mime) || "application/octet-stream";
|
|
384
382
|
if (!input.data.byteLength || input.data.byteLength > projectFileLimit("document", mime)) {
|
|
385
|
-
throw new ServiceError("invalid",
|
|
383
|
+
throw new ServiceError("invalid", bilingual(`„${input.name}“ ist leer oder zu groß für ein Dokument.`, `“${input.name}” is empty or too large for a document.`));
|
|
386
384
|
}
|
|
387
385
|
const name = input.name.slice(0, 120) || "datei";
|
|
388
386
|
const values = {
|
|
@@ -409,7 +407,7 @@ export async function putOriginFile(origin, input) {
|
|
|
409
407
|
.from(schema.projectFile)
|
|
410
408
|
.where(and(eq(schema.projectFile.projectId, input.space), eq(schema.projectFile.kind, "document")));
|
|
411
409
|
if (n >= PROJECT_LIMITS.files.document) {
|
|
412
|
-
throw new ServiceError("refused", `A space
|
|
410
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${PROJECT_LIMITS.files.document} Dokumente.`, `A space can have at most ${PROJECT_LIMITS.files.document} documents.`));
|
|
413
411
|
}
|
|
414
412
|
const ref = await saveAsset({ kind: "project", mime, name, data: input.data });
|
|
415
413
|
id = ref.id;
|
|
@@ -147,18 +147,23 @@ export async function ownedProject(_userId, projectId) {
|
|
|
147
147
|
const madeHere = () => and(eq(schema.project.tenantId, currentTenant()), isNull(schema.project.origin));
|
|
148
148
|
/** The tenant's projects a local install synced. */
|
|
149
149
|
const synced = () => and(eq(schema.project.tenantId, currentTenant()), eq(schema.project.origin, "local"));
|
|
150
|
+
/** What the first space is called, in the language of whoever first opens the studio. */
|
|
151
|
+
const FIRST_SPACE = {
|
|
152
|
+
cloud: { de: "Meine Wizards", en: "My wizards" },
|
|
153
|
+
local: { de: "Mein Computer", en: "My computer" },
|
|
154
|
+
};
|
|
150
155
|
/**
|
|
151
156
|
* A tenant that builds here always has a project of its own: the first one is made when its
|
|
152
|
-
* studio is first opened.
|
|
157
|
+
* studio is first opened, named in that request's language.
|
|
153
158
|
*/
|
|
154
|
-
async function ensureProject() {
|
|
159
|
+
async function ensureProject(lang = "en") {
|
|
155
160
|
const any = await db.query.project.findFirst({ where: madeHere() });
|
|
156
161
|
if (!any) {
|
|
157
162
|
const id = nanoid(12);
|
|
158
163
|
await db.insert(schema.project).values({
|
|
159
164
|
id,
|
|
160
165
|
tenantId: currentTenant(),
|
|
161
|
-
name: env.manage.url ? "
|
|
166
|
+
name: FIRST_SPACE[env.manage.url ? "cloud" : "local"][lang],
|
|
162
167
|
});
|
|
163
168
|
spaceChanged("space.created", id);
|
|
164
169
|
}
|
|
@@ -185,10 +190,10 @@ export async function defaultProject(_userId) {
|
|
|
185
190
|
* The projects the studio shows. A tenant that builds here: its own and the synced ones, up to
|
|
186
191
|
* its number. One that does not: only what its local install synced — possibly nothing yet.
|
|
187
192
|
*/
|
|
188
|
-
export async function listProjects(_userId) {
|
|
193
|
+
export async function listProjects(_userId, lang) {
|
|
189
194
|
const build = await mayBuild();
|
|
190
195
|
if (build) {
|
|
191
|
-
await ensureProject();
|
|
196
|
+
await ensureProject(lang);
|
|
192
197
|
}
|
|
193
198
|
// The oldest come first: with a limit of one, that is the project everything lands in.
|
|
194
199
|
const limit = await projectLimit();
|
|
@@ -226,8 +231,8 @@ export async function createProject(_userId, name) {
|
|
|
226
231
|
const limit = await projectLimit();
|
|
227
232
|
if (n >= limit) {
|
|
228
233
|
throw new ServiceError("refused", limit === 1
|
|
229
|
-
? bilingual("Hier
|
|
230
|
-
: bilingual(`
|
|
234
|
+
? bilingual("Hier ist nur ein Space möglich.", "Only one space is allowed here.")
|
|
235
|
+
: bilingual(`Hier sind höchstens ${limit} Spaces möglich.`, `You can have at most ${limit} spaces here.`));
|
|
231
236
|
}
|
|
232
237
|
const id = nanoid(12);
|
|
233
238
|
await db
|
|
@@ -365,7 +370,7 @@ export async function deleteProject(userId, projectId) {
|
|
|
365
370
|
await requireAdmin();
|
|
366
371
|
const all = await db.query.project.findMany({ where: madeHere() });
|
|
367
372
|
if (all.length <= 1) {
|
|
368
|
-
throw new ServiceError("refused", bilingual("
|
|
373
|
+
throw new ServiceError("refused", bilingual("Der letzte Space lässt sich nicht löschen.", "You can’t delete the last space."));
|
|
369
374
|
}
|
|
370
375
|
await removeProject(projectId);
|
|
371
376
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { wizardLang } from "@engenty-wizards/shared/definition";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { BUILT_IN_RUNNERS, CONVERSATION_RUNNERS, DEFAULT_RUNNER, DEFAULT_RUNNER_SETTINGS, runnerFit, } from "@engenty-wizards/shared/runners";
|
|
3
4
|
import { env } from "../env.js";
|
|
4
5
|
import { conversationAccess, conversationAvailable } from "../models.js";
|
|
@@ -71,14 +72,14 @@ export async function checkRunnerSettings(settings) {
|
|
|
71
72
|
const ids = new Set(available.map((r) => r.id));
|
|
72
73
|
const unknown = settings.enabled.find((id) => !ids.has(id));
|
|
73
74
|
if (unknown) {
|
|
74
|
-
throw new ServiceError("invalid", `There
|
|
75
|
+
throw new ServiceError("invalid", bilingual(`Einen Runner „${unknown}“ gibt es hier nicht.`, `There’s no runner “${unknown}” here.`));
|
|
75
76
|
}
|
|
76
77
|
const enabled = [...new Set(settings.enabled)];
|
|
77
78
|
if (!enabled.length) {
|
|
78
|
-
throw new ServiceError("invalid", "At least one runner
|
|
79
|
+
throw new ServiceError("invalid", bilingual("Mindestens ein Runner muss eingeschaltet bleiben.", "At least one runner has to stay on."));
|
|
79
80
|
}
|
|
80
81
|
if (!enabled.includes(settings.default)) {
|
|
81
|
-
throw new ServiceError("invalid", "The link
|
|
82
|
+
throw new ServiceError("invalid", bilingual("Der Link muss einen eingeschalteten Runner öffnen.", "The link has to open a runner that’s turned on."));
|
|
82
83
|
}
|
|
83
84
|
return { default: settings.default, enabled };
|
|
84
85
|
}
|
|
@@ -86,7 +86,7 @@ function fly(runId, answers, acceptReviews) {
|
|
|
86
86
|
.join("; ")
|
|
87
87
|
: "";
|
|
88
88
|
const message = err instanceof RunInputError
|
|
89
|
-
? bilingual(`Antworten fehlen oder passen nicht: ${answers("de")}`, `Answers are missing or
|
|
89
|
+
? bilingual(`Antworten fehlen oder passen nicht: ${answers("de")}`, `Answers are missing or don’t fit: ${answers("en")}`)
|
|
90
90
|
: saidOf(err);
|
|
91
91
|
await emitEvent(runId, null, "error", message).catch(() => undefined);
|
|
92
92
|
land();
|
|
@@ -368,7 +368,7 @@ export function refusedInput(err) {
|
|
|
368
368
|
export async function startRun(userId, wizardId, answers = {}) {
|
|
369
369
|
const w = await ownedWizard(userId, wizardId);
|
|
370
370
|
if (w.publishedVersion === null) {
|
|
371
|
-
throw new ServiceError("refused", "This wizard
|
|
371
|
+
throw new ServiceError("refused", "This wizard isn’t published yet. Publish it, or try the draft with start_test_run.");
|
|
372
372
|
}
|
|
373
373
|
const version = await db.query.wizardVersion.findFirst({
|
|
374
374
|
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
@@ -429,7 +429,7 @@ export async function controlRun(userId, runId, action) {
|
|
|
429
429
|
export async function answerRunAsk(userId, runId, askId, answer) {
|
|
430
430
|
await ownedRun(userId, runId);
|
|
431
431
|
if (!(await answerAsk(runId, askId, answer === "allow" ? { type: "done" } : { type: "skip" }))) {
|
|
432
|
-
throw new ServiceError("refused",
|
|
432
|
+
throw new ServiceError("refused", { code: "askClosed" });
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
export async function listRuns(userId, wizardId) {
|
|
@@ -150,7 +150,7 @@ function assistMessage(input) {
|
|
|
150
150
|
.slice(0, 6)
|
|
151
151
|
.map((p) => `## ${p.title}${p.url ? ` (${p.url})` : ""}${p.role ? ` [${p.role}]` : ""}\n\n${p.markdown.slice(0, 6000)}`);
|
|
152
152
|
return [
|
|
153
|
-
`This is the WordPress website ${input.url}. Fill the space from what it says about itself: a title, a description (about), facts (address, phone, email, VAT
|
|
153
|
+
`This is the WordPress website ${input.url}. Fill the space from what it says about itself: a title, a description (about), facts (address, phone, email, VAT ID, company register, opening hours, who runs it) and the colours, if there are none yet. Its pages are already in Knowledge; do not copy them. Write in the website's own language.`,
|
|
154
154
|
"The details below come straight from WordPress. Read the website with read_website only when something important is missing. Invent nothing.",
|
|
155
155
|
`Name: ${input.name || "—"}\nTagline: ${input.tagline || "—"}\nLanguage: ${input.language ?? "—"}`,
|
|
156
156
|
input.facts.length
|
|
@@ -267,7 +267,7 @@ function siteJobView(job) {
|
|
|
267
267
|
export function siteJob(jobId) {
|
|
268
268
|
const job = jobs.get(jobId);
|
|
269
269
|
if (!job || job.tenantId !== currentTenant()) {
|
|
270
|
-
throw new ServiceError("not_found", bilingual("Diesen Vorgang gibt es nicht
|
|
270
|
+
throw new ServiceError("not_found", bilingual("Diesen Vorgang gibt es nicht, oder er ist schon beendet.", "This job doesn’t exist or has already finished."));
|
|
271
271
|
}
|
|
272
272
|
return siteJobView(job);
|
|
273
273
|
}
|
|
@@ -330,7 +330,7 @@ export async function putCategory(projectId, draft, opts = {}) {
|
|
|
330
330
|
return known;
|
|
331
331
|
}
|
|
332
332
|
if (rows.length >= MAX_CATEGORIES) {
|
|
333
|
-
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`, `A space
|
|
333
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`, `A space can have at most ${MAX_CATEGORIES} categories.`));
|
|
334
334
|
}
|
|
335
335
|
const id = nanoid(12);
|
|
336
336
|
const [row] = await db
|
|
@@ -459,7 +459,7 @@ export async function addCategoryValue(categoryId, value) {
|
|
|
459
459
|
const category = await categoryRow(categoryId);
|
|
460
460
|
await requireWritableProject(category.projectId);
|
|
461
461
|
if (category.type !== "choice") {
|
|
462
|
-
throw new ServiceError("invalid", bilingual("
|
|
462
|
+
throw new ServiceError("invalid", bilingual("Werte gibt es nur bei Kategorien vom Typ Auswahl.", "Only choice categories have values."));
|
|
463
463
|
}
|
|
464
464
|
const ids = await addValues(category.id, [value]);
|
|
465
465
|
return { id: [...ids.values()].at(-1) };
|
|
@@ -731,7 +731,7 @@ export async function syncTableRowCategories(tableId, rowIds) {
|
|
|
731
731
|
function condition(category, w, valueIds) {
|
|
732
732
|
const t = schema.spaceItemCategory;
|
|
733
733
|
const list = Array.isArray(w) ? w : null;
|
|
734
|
-
const bad = () => new ServiceError("invalid",
|
|
734
|
+
const bad = () => new ServiceError("invalid", `“${category.name}” is a ${category.type}: ${{
|
|
735
735
|
choice: "give a value, a list of values or { atLeast }",
|
|
736
736
|
text: "give a text, a list or { contains }",
|
|
737
737
|
number: "give a number, a list or { gte, lte }",
|
|
@@ -827,7 +827,7 @@ export async function filterItems(projectId, where) {
|
|
|
827
827
|
for (const [name, w] of entries) {
|
|
828
828
|
const category = byName(rows, name);
|
|
829
829
|
if (!category) {
|
|
830
|
-
throw new ServiceError("invalid", `No category
|
|
830
|
+
throw new ServiceError("invalid", `No category “${name}”. There are: ${rows.map((c) => `${c.name} (${c.type})`).join(", ") || "none"}.`);
|
|
831
831
|
}
|
|
832
832
|
const valueIds = new Map((await db.query.spaceCategoryValue.findMany({
|
|
833
833
|
where: eq(schema.spaceCategoryValue.categoryId, category.id),
|
|
@@ -23,7 +23,7 @@ async function ownerOf(projectId, wizardId) {
|
|
|
23
23
|
columns: { id: true },
|
|
24
24
|
});
|
|
25
25
|
if (!w) {
|
|
26
|
-
throw new ServiceError("invalid", bilingual("Diesen Wizard gibt es im Space nicht.", "
|
|
26
|
+
throw new ServiceError("invalid", bilingual("Diesen Wizard gibt es im Space nicht.", "This wizard isn’t in the space."));
|
|
27
27
|
}
|
|
28
28
|
return w.id;
|
|
29
29
|
}
|
|
@@ -104,7 +104,7 @@ async function tableCount(projectId) {
|
|
|
104
104
|
.from(schema.spaceTable)
|
|
105
105
|
.where(eq(schema.spaceTable.projectId, projectId));
|
|
106
106
|
if (n >= SPACE_DATA_LIMITS.tables) {
|
|
107
|
-
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.tables} Tabellen.`, `A space
|
|
107
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.tables} Tabellen.`, `A space can have at most ${SPACE_DATA_LIMITS.tables} tables.`));
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
export async function createTable(userId, projectId, input, lang = "en") {
|
|
@@ -187,7 +187,7 @@ export async function getTable(userId, tableId) {
|
|
|
187
187
|
export async function updateTable(_userId, tableId, patch) {
|
|
188
188
|
const table = await writableTable(tableId);
|
|
189
189
|
if (table.list && (patch.columns !== undefined || patch.wizardId !== undefined)) {
|
|
190
|
-
throw new ServiceError("refused", bilingual("Diese Tabelle gehört zu einer Liste des Wizards
|
|
190
|
+
throw new ServiceError("refused", bilingual("Diese Tabelle gehört zu einer Liste des Wizards. Ihre Spalten kommen aus dem Wizard.", "This table belongs to one of the wizard’s lists. Its columns come from the wizard."));
|
|
191
191
|
}
|
|
192
192
|
const title = patch.title !== undefined ? patch.title.trim() || table.title : table.title;
|
|
193
193
|
const wizardId = patch.wizardId !== undefined ? await ownerOf(table.projectId, patch.wizardId) : table.wizardId;
|
|
@@ -269,7 +269,7 @@ export async function addTableRow(_userId, tableId, cells = {}) {
|
|
|
269
269
|
.from(schema.spaceTableRow)
|
|
270
270
|
.where(eq(schema.spaceTableRow.tableId, table.id));
|
|
271
271
|
if (n >= SPACE_DATA_LIMITS.rowsPerTable) {
|
|
272
|
-
throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table
|
|
272
|
+
throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table can have at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
|
|
273
273
|
}
|
|
274
274
|
let value;
|
|
275
275
|
try {
|
|
@@ -334,7 +334,7 @@ async function pageRow(pageId) {
|
|
|
334
334
|
}
|
|
335
335
|
function checkMarkdown(markdown) {
|
|
336
336
|
if (markdown.length > SPACE_DATA_LIMITS.pageChars) {
|
|
337
|
-
throw new ServiceError("refused", bilingual(`Eine Seite hat höchstens ${SPACE_DATA_LIMITS.pageChars.toLocaleString("de")} Zeichen.`, `A page
|
|
337
|
+
throw new ServiceError("refused", bilingual(`Eine Seite hat höchstens ${SPACE_DATA_LIMITS.pageChars.toLocaleString("de")} Zeichen.`, `A page can have at most ${SPACE_DATA_LIMITS.pageChars.toLocaleString("en")} characters.`));
|
|
338
338
|
}
|
|
339
339
|
return markdown;
|
|
340
340
|
}
|
|
@@ -344,7 +344,7 @@ async function pageCount(projectId, more = 1) {
|
|
|
344
344
|
.from(schema.spacePage)
|
|
345
345
|
.where(eq(schema.spacePage.projectId, projectId));
|
|
346
346
|
if (n + more > SPACE_DATA_LIMITS.pages) {
|
|
347
|
-
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space
|
|
347
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space can have at most ${SPACE_DATA_LIMITS.pages} pages.`));
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
/** A page of the space's own that a sub-page may stand under: never one of its own sub-pages. */
|
|
@@ -357,11 +357,11 @@ async function parentOf(projectId, parentId, pageId) {
|
|
|
357
357
|
columns: { id: true, wizardId: true, parentId: true },
|
|
358
358
|
});
|
|
359
359
|
if (!parent || parent.wizardId) {
|
|
360
|
-
throw new ServiceError("invalid", bilingual("
|
|
360
|
+
throw new ServiceError("invalid", bilingual("Diese Seite kann keine Unterseiten haben.", "This page can’t have subpages."));
|
|
361
361
|
}
|
|
362
362
|
for (let at = parent.id; at && pageId;) {
|
|
363
363
|
if (at === pageId) {
|
|
364
|
-
throw new ServiceError("invalid", bilingual("Eine Seite kann nicht
|
|
364
|
+
throw new ServiceError("invalid", bilingual("Eine Seite kann nicht ihre eigene Unterseite sein.", "A page can’t be its own subpage."));
|
|
365
365
|
}
|
|
366
366
|
const up = await db.query.spacePage.findFirst({
|
|
367
367
|
where: eq(schema.spacePage.id, at),
|
|
@@ -492,7 +492,7 @@ export async function runPages(projectId, wizardId) {
|
|
|
492
492
|
export async function writeRunPage(projectId, wizardId, input) {
|
|
493
493
|
const title = input.title.trim();
|
|
494
494
|
if (!title) {
|
|
495
|
-
throw new ServiceError("invalid", "A page needs a title.");
|
|
495
|
+
throw new ServiceError("invalid", bilingual("Eine Seite braucht einen Titel.", "A page needs a title."));
|
|
496
496
|
}
|
|
497
497
|
const existing = (await db.query.spacePage.findMany({
|
|
498
498
|
where: and(eq(schema.spacePage.projectId, projectId), eq(schema.spacePage.wizardId, wizardId)),
|
|
@@ -513,7 +513,7 @@ export async function writeRunPage(projectId, wizardId, input) {
|
|
|
513
513
|
.from(schema.spacePage)
|
|
514
514
|
.where(eq(schema.spacePage.projectId, projectId));
|
|
515
515
|
if (n >= SPACE_DATA_LIMITS.pages) {
|
|
516
|
-
throw new ServiceError("refused", `A space
|
|
516
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space can have at most ${SPACE_DATA_LIMITS.pages} pages.`));
|
|
517
517
|
}
|
|
518
518
|
const id = nanoid(12);
|
|
519
519
|
await db.insert(schema.spacePage).values({
|
|
@@ -532,7 +532,7 @@ async function originSpace(space) {
|
|
|
532
532
|
columns: { id: true },
|
|
533
533
|
});
|
|
534
534
|
if (!project) {
|
|
535
|
-
throw new ServiceError("not_found", `There
|
|
535
|
+
throw new ServiceError("not_found", bilingual(`Den Space „${space}“ gibt es nicht.`, `There’s no space “${space}”.`));
|
|
536
536
|
}
|
|
537
537
|
await requireWritableProject(project.id);
|
|
538
538
|
return project.id;
|
|
@@ -566,7 +566,7 @@ export async function putOriginPage(origin, input) {
|
|
|
566
566
|
})
|
|
567
567
|
: null;
|
|
568
568
|
if (input.parent && !parent) {
|
|
569
|
-
throw new ServiceError("invalid", `There
|
|
569
|
+
throw new ServiceError("invalid", bilingual(`Es gibt keine Seite mit dem Schlüssel „${input.parent}“, unter die diese Seite könnte.`, `There’s no page with the key “${input.parent}” to put this page under.`));
|
|
570
570
|
}
|
|
571
571
|
const title = input.title.replace(/\s+/g, " ").trim().slice(0, 200) || "Seite";
|
|
572
572
|
const markdown = checkMarkdown(input.markdown);
|
|
@@ -630,7 +630,7 @@ export async function putOriginTable(origin, input) {
|
|
|
630
630
|
}
|
|
631
631
|
const columns = checkColumns(input.columns);
|
|
632
632
|
if (input.rows.length > SPACE_DATA_LIMITS.rowsPerTable) {
|
|
633
|
-
throw new ServiceError("refused", `A table
|
|
633
|
+
throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table can have at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
|
|
634
634
|
}
|
|
635
635
|
let cells;
|
|
636
636
|
try {
|
|
@@ -74,7 +74,7 @@ export function originData(origin) {
|
|
|
74
74
|
const id = await putOriginFile(origin, { ...input, categories: undefined });
|
|
75
75
|
const file = await db.query.projectFile.findFirst({ where: eq(schema.projectFile.id, id) });
|
|
76
76
|
if (!file) {
|
|
77
|
-
throw new ServiceError("not_found", "The document
|
|
77
|
+
throw new ServiceError("not_found", "The document was removed while it was being read.");
|
|
78
78
|
}
|
|
79
79
|
const path = (await filePaths(input.space)).get(id) ?? "";
|
|
80
80
|
const kept = (await db.query.spacePage.findFirst({
|
|
@@ -151,8 +151,8 @@ async function requireRoom(spaceId) {
|
|
|
151
151
|
const limit = await projectLimit();
|
|
152
152
|
if (others.length >= limit) {
|
|
153
153
|
throw new ServiceError("refused", limit === 1
|
|
154
|
-
? bilingual("Dieses Konto hat hier schon
|
|
155
|
-
: bilingual(`Dieses Konto hat hier schon ${limit}
|
|
154
|
+
? bilingual("Dieses Konto hat hier schon einen Space.", "This account already has a space here.")
|
|
155
|
+
: bilingual(`Dieses Konto hat hier schon ${limit} Spaces.`, `This account already has ${limit} spaces here.`), {
|
|
156
156
|
reason: "space_limit",
|
|
157
157
|
limit,
|
|
158
158
|
spaces: others.filter((p) => p.origin === "local").map((p) => ({ id: p.id, name: p.name })),
|
|
@@ -166,7 +166,7 @@ async function requireWizardRoom(spaceId) {
|
|
|
166
166
|
.from(schema.wizard)
|
|
167
167
|
.where(eq(schema.wizard.projectId, spaceId));
|
|
168
168
|
if ((row?.n ?? 0) >= env.limits.syncedWizards) {
|
|
169
|
-
throw new ServiceError("refused", bilingual(`Hier haben höchstens ${env.limits.syncedWizards} veröffentlichte Wizards Platz.`, `There
|
|
169
|
+
throw new ServiceError("refused", bilingual(`Hier haben höchstens ${env.limits.syncedWizards} veröffentlichte Wizards Platz.`, `There’s room for at most ${env.limits.syncedWizards} published wizards here.`), { reason: "wizard_limit", limit: env.limits.syncedWizards });
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
const hashOf = (data) => createHash("sha256").update(data).digest("hex");
|
|
@@ -200,7 +200,7 @@ async function syncLogo(projectId, logo) {
|
|
|
200
200
|
async function syncSpace(spaceId, space) {
|
|
201
201
|
const existing = await syncedProject(spaceId);
|
|
202
202
|
if (existing && existing.origin !== "local") {
|
|
203
|
-
throw new ServiceError("refused", bilingual("
|
|
203
|
+
throw new ServiceError("refused", bilingual("Einen Space mit dieser Kennung gibt es hier schon, und er kommt nicht von deinem Computer.", "A space with this ID already exists here and didn’t come from your computer."), {
|
|
204
204
|
reason: "id_taken",
|
|
205
205
|
});
|
|
206
206
|
}
|
|
@@ -251,7 +251,7 @@ export async function syncWizard(rawSpaceId, rawWizardId, input) {
|
|
|
251
251
|
where: and(eq(schema.wizard.id, wizardId), eq(schema.wizard.tenantId, currentTenant())),
|
|
252
252
|
});
|
|
253
253
|
if (existing && existing.projectId !== spaceId) {
|
|
254
|
-
throw new ServiceError("refused", bilingual("Ein Wizard mit dieser Kennung gehört hier
|
|
254
|
+
throw new ServiceError("refused", bilingual("Ein Wizard mit dieser Kennung gehört hier schon zu einem anderen Space.", "A wizard with this ID already belongs to another space here."), {
|
|
255
255
|
reason: "id_taken",
|
|
256
256
|
});
|
|
257
257
|
}
|
|
@@ -507,7 +507,7 @@ export async function syncSpaceData(rawSpaceId, input) {
|
|
|
507
507
|
: [],
|
|
508
508
|
]);
|
|
509
509
|
if ([...tablesHere, ...pagesHere].some((x) => x.projectId !== spaceId || x.wizardId !== null)) {
|
|
510
|
-
throw new ServiceError("refused", bilingual("
|
|
510
|
+
throw new ServiceError("refused", bilingual("Diese Kennung ist hier schon vergeben.", "This ID is already in use here."), {
|
|
511
511
|
reason: "id_taken",
|
|
512
512
|
});
|
|
513
513
|
}
|
|
@@ -585,7 +585,7 @@ async function syncCategories(spaceId, categories, assignments) {
|
|
|
585
585
|
})
|
|
586
586
|
: [];
|
|
587
587
|
if (taken.some((c) => c.projectId !== spaceId)) {
|
|
588
|
-
throw new ServiceError("refused", bilingual("
|
|
588
|
+
throw new ServiceError("refused", bilingual("Diese Kennung ist hier schon vergeben.", "This ID is already in use here."), {
|
|
589
589
|
reason: "id_taken",
|
|
590
590
|
});
|
|
591
591
|
}
|