wizards 0.4.4 → 0.4.6
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/apps/runtime/dist/agents/architect.js +32 -13
- package/apps/runtime/dist/agents/subscription.js +12 -12
- package/apps/runtime/dist/app.js +9 -5
- package/apps/runtime/dist/assistant/knows.js +2 -2
- package/apps/runtime/dist/authoring/example.js +21 -20
- package/apps/runtime/dist/authoring/guide.js +4 -2
- package/apps/runtime/dist/connectors/imap.js +4 -4
- package/apps/runtime/dist/connectors/index.js +40 -15
- package/apps/runtime/dist/connectors/plugin-connections.js +3 -2
- package/apps/runtime/dist/connectors/via-account.js +8 -6
- package/apps/runtime/dist/docs/dev/plugins/connections.md +1 -1
- package/apps/runtime/dist/docs/dev/plugins/reference.md +3 -2
- package/apps/runtime/dist/docs/dev/plugins/runners.md +38 -2
- package/apps/runtime/dist/docs/dev/plugins/server.md +3 -2
- package/apps/runtime/dist/docs/dev/plugins/starters.md +1 -1
- package/apps/runtime/dist/docs/dev/plugins/studio.md +2 -1
- package/apps/runtime/dist/documents/parse.js +5 -3
- package/apps/runtime/dist/engine/events.js +1 -1
- package/apps/runtime/dist/engine/input.js +28 -8
- package/apps/runtime/dist/engine/prepare.js +2 -2
- package/apps/runtime/dist/engine/requirements.js +12 -3
- package/apps/runtime/dist/engine/runner.js +34 -23
- package/apps/runtime/dist/engine/steps.js +6 -5
- package/apps/runtime/dist/engine/surface.js +2 -1
- package/apps/runtime/dist/engine/template.js +1 -1
- package/apps/runtime/dist/engine/types.js +2 -1
- package/apps/runtime/dist/film/project.js +1 -1
- package/apps/runtime/dist/film/skills.js +2 -2
- package/apps/runtime/dist/film/sounds.js +1 -1
- package/apps/runtime/dist/film/step.js +7 -4
- package/apps/runtime/dist/film/tools.js +1 -1
- package/apps/runtime/dist/geocode.js +1 -1
- package/apps/runtime/dist/harness/apple.js +0 -5
- package/apps/runtime/dist/harness/claude.js +2 -1
- package/apps/runtime/dist/harness/codex.js +2 -1
- package/apps/runtime/dist/harness/model.js +4 -3
- package/apps/runtime/dist/harness/terminal.js +4 -3
- package/apps/runtime/dist/model-errors.js +8 -3
- package/apps/runtime/dist/models.js +58 -38
- package/apps/runtime/dist/plugins/channels.js +1 -0
- package/apps/runtime/dist/plugins/runs.js +2 -1
- package/apps/runtime/dist/routes/api.js +16 -5
- package/apps/runtime/dist/routes/lang.js +13 -0
- package/apps/runtime/dist/routes/runs.js +12 -19
- package/apps/runtime/dist/routes/studio.js +40 -28
- package/apps/runtime/dist/services/access.js +4 -3
- package/apps/runtime/dist/services/architect.js +15 -4
- package/apps/runtime/dist/services/assistant-chats.js +2 -1
- package/apps/runtime/dist/services/brand.js +3 -2
- package/apps/runtime/dist/services/errors.js +7 -5
- package/apps/runtime/dist/services/knowledge-model.js +3 -2
- package/apps/runtime/dist/services/marketplace.js +6 -5
- package/apps/runtime/dist/services/package.js +10 -3
- package/apps/runtime/dist/services/profile.js +12 -0
- package/apps/runtime/dist/services/project-files.js +13 -12
- package/apps/runtime/dist/services/projects.js +11 -6
- package/apps/runtime/dist/services/runners.js +17 -2
- package/apps/runtime/dist/services/runs.js +18 -11
- package/apps/runtime/dist/services/shares.js +2 -1
- package/apps/runtime/dist/services/site.js +21 -10
- package/apps/runtime/dist/services/space-categories.js +7 -6
- package/apps/runtime/dist/services/space-data.js +19 -16
- package/apps/runtime/dist/services/spaces.js +8 -7
- package/apps/runtime/dist/services/wizards.js +41 -20
- package/apps/runtime/dist/store/errors.js +2 -1
- package/apps/runtime/dist/store/index.js +21 -4
- package/apps/runtime/dist/store/shared-lists.js +2 -3
- package/apps/runtime/dist/tools/index.js +17 -2
- package/apps/runtime/dist/tools/plugin.js +3 -2
- package/apps/runtime/dist/widgets/render.js +1 -1
- package/apps/runtime/dist/widgets/step.js +2 -1
- package/apps/web/dist/assets/{AboutDialog-nLn3vZAN.js → AboutDialog-i-qD_Nea.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-Cbx3mN2p.js → ChangelogDialog-BYuVcmKR.js} +1 -1
- package/apps/web/dist/assets/ChatView-DMEYEsA7.js +10 -0
- package/apps/web/dist/assets/EditorPage-Bry35WHx.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-Bvj3R8g4.js → FlowPreview-BsfjDVv8.js} +1 -1
- package/apps/web/dist/assets/{Harness-D-Z3MbYq.js → Harness-BxQDTGJk.js} +2 -2
- package/apps/web/dist/assets/NewWizardChannels-DPnLT-md.js +1 -0
- package/apps/web/dist/assets/NewWizardPage-BSh_bUIJ.js +7 -0
- package/apps/web/dist/assets/{OnboardingPage-CdoAfnI2.js → OnboardingPage-BZrRdVwr.js} +1 -1
- package/apps/web/dist/assets/ProjectPage-_3L9R3SZ.js +1 -0
- package/apps/web/dist/assets/PublicRunner-DXbh8uhe.js +1 -0
- package/apps/web/dist/assets/{ReadOnly-DQPHmvS0.js → ReadOnly-DFysMEMF.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-DpUakYb9.js +4 -0
- package/apps/web/dist/assets/RunnerView-DQ-NgcCa.js +1 -0
- package/apps/web/dist/assets/SettingsPage-DyUCIRjo.js +1 -0
- package/apps/web/dist/assets/SetupPage-BgVHvN6j.js +1 -0
- package/apps/web/dist/assets/SharePage-BI6vkc9c.js +1 -0
- package/apps/web/dist/assets/changelog-Cnv7ZZEL.js +1 -0
- package/apps/web/dist/assets/{circle-alert-DCBV7n5k.js → circle-alert-BVj6idzu.js} +1 -1
- package/apps/web/dist/assets/esm-hSq-jeOG.js +7 -0
- package/apps/web/dist/assets/index-Ca1841qE.js +14 -0
- package/apps/web/dist/assets/index-GSl2Rcjy.css +1 -0
- package/apps/web/dist/assets/{lock-DlzTfwxP.js → lock-BoWS-JhN.js} +1 -1
- package/apps/web/dist/assets/{phone-BFD815nc.js → phone-CeqyvwK-.js} +1 -1
- package/apps/web/dist/assets/settings-menu-BK8WpcGv.js +1 -0
- package/apps/web/dist/assets/ui-BbIh6SOJ.js +134 -0
- package/apps/web/dist/assets/{upload-BEf5-d6X.js → upload-BQrAlHIG.js} +1 -1
- package/apps/web/dist/index.html +4 -4
- package/apps/web/dist/mcp/flow.html +76 -76
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +28 -4
- package/node_modules/@engenty-wizards/plugin-sdk/dist/studio.d.ts +52 -1
- package/node_modules/@engenty-wizards/plugin-sdk/dist/thread.js +6 -1
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +6 -2
- package/node_modules/@engenty-wizards/shared/dist/definition.js +13 -5
- package/node_modules/@engenty-wizards/shared/dist/knowledge.js +1 -1
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +11 -1
- package/node_modules/@engenty-wizards/shared/dist/run.js +11 -1
- package/node_modules/@engenty-wizards/shared/dist/runners.d.ts +12 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +8 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +8 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/ChatView-1VuHE2Q7.js +0 -10
- package/apps/web/dist/assets/EditorPage-Cp3jqQsk.js +0 -3
- package/apps/web/dist/assets/NewWizardChannels-DCF7JoL0.js +0 -1
- package/apps/web/dist/assets/NewWizardPage-BTlgq7yN.js +0 -7
- package/apps/web/dist/assets/ProjectPage-CoPSdDO9.js +0 -1
- package/apps/web/dist/assets/PublicRunner-CHwK3X75.js +0 -1
- package/apps/web/dist/assets/RunDrawer-CmGuqp0p.js +0 -4
- package/apps/web/dist/assets/RunnerView-DiaQiDbw.js +0 -1
- package/apps/web/dist/assets/SettingsPage-NzmFrJXb.js +0 -1
- package/apps/web/dist/assets/SetupPage-8mBuezdY.js +0 -1
- package/apps/web/dist/assets/SharePage-C-T5fkp1.js +0 -1
- package/apps/web/dist/assets/changelog-CsxPWKJ-.js +0 -1
- package/apps/web/dist/assets/esm-CnR8rDPi.js +0 -7
- package/apps/web/dist/assets/index-CmxVazOf.css +0 -1
- package/apps/web/dist/assets/index-D57rKyGN.js +0 -14
- package/apps/web/dist/assets/settings-menu-D-UqnL1p.js +0 -1
- package/apps/web/dist/assets/ui-DTuixH4y.js +0 -134
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { COLOR_SECTION_KINDS, cleanAssetSections, cleanLogoSections, FACT_TYPES, FONT_USES, FONT_WEIGHTS, GRADIENT_TYPES, orderColors, PROJECT_LIMITS, RULE_MARKS, } from "@engenty-wizards/shared/projects";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { and, count, eq, isNull } from "drizzle-orm";
|
|
3
4
|
import { nanoid } from "nanoid";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -154,9 +155,11 @@ async function ensureProject() {
|
|
|
154
155
|
const any = await db.query.project.findFirst({ where: madeHere() });
|
|
155
156
|
if (!any) {
|
|
156
157
|
const id = nanoid(12);
|
|
157
|
-
await db
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
await db.insert(schema.project).values({
|
|
159
|
+
id,
|
|
160
|
+
tenantId: currentTenant(),
|
|
161
|
+
name: env.manage.url ? "Meine Wizards" : "Local Space",
|
|
162
|
+
});
|
|
160
163
|
spaceChanged("space.created", id);
|
|
161
164
|
}
|
|
162
165
|
}
|
|
@@ -222,7 +225,9 @@ export async function createProject(_userId, name) {
|
|
|
222
225
|
.where(eq(schema.project.tenantId, currentTenant()));
|
|
223
226
|
const limit = await projectLimit();
|
|
224
227
|
if (n >= limit) {
|
|
225
|
-
throw new ServiceError("refused", limit === 1
|
|
228
|
+
throw new ServiceError("refused", limit === 1
|
|
229
|
+
? bilingual("Hier gibt es ein Projekt.", "There is one project here.")
|
|
230
|
+
: bilingual(`Höchstens ${limit} Projekte.`, `At most ${limit} projects.`));
|
|
226
231
|
}
|
|
227
232
|
const id = nanoid(12);
|
|
228
233
|
await db
|
|
@@ -246,7 +251,7 @@ export async function updateProject(userId, projectId, patch) {
|
|
|
246
251
|
return { ...s, headers };
|
|
247
252
|
});
|
|
248
253
|
if (patch.facts && new Set(patch.facts.map((f) => f.key)).size !== patch.facts.length) {
|
|
249
|
-
throw new ServiceError("invalid", "Zwei Einträge haben denselben Schlüssel.");
|
|
254
|
+
throw new ServiceError("invalid", bilingual("Zwei Einträge haben denselben Schlüssel.", "Two entries have the same key."));
|
|
250
255
|
}
|
|
251
256
|
await db
|
|
252
257
|
.update(schema.project)
|
|
@@ -360,7 +365,7 @@ export async function deleteProject(userId, projectId) {
|
|
|
360
365
|
await requireAdmin();
|
|
361
366
|
const all = await db.query.project.findMany({ where: madeHere() });
|
|
362
367
|
if (all.length <= 1) {
|
|
363
|
-
throw new ServiceError("refused", "Das letzte Projekt bleibt.");
|
|
368
|
+
throw new ServiceError("refused", bilingual("Das letzte Projekt bleibt.", "The last project stays."));
|
|
364
369
|
}
|
|
365
370
|
await removeProject(projectId);
|
|
366
371
|
}
|
|
@@ -22,17 +22,32 @@ export async function runnersOf(tenantId = currentTenant()) {
|
|
|
22
22
|
// A plugin's runner says per tenant whether it is ready (a number connected, a key set).
|
|
23
23
|
const ofPlugins = await Promise.all(plugins.flatMap((p) => [...p.runners.values()].map(async (r) => ({
|
|
24
24
|
...r.info,
|
|
25
|
-
|
|
25
|
+
...problemOf(r.problem
|
|
26
26
|
? await Promise.resolve()
|
|
27
27
|
.then(() => r.problem?.() ?? null)
|
|
28
28
|
.catch((err) => err.message || "The runner is not ready.")
|
|
29
|
-
: null,
|
|
29
|
+
: null),
|
|
30
30
|
}))));
|
|
31
31
|
return [
|
|
32
32
|
...BUILT_IN_RUNNERS.map((r) => CONVERSATION_RUNNERS.includes(r.id) ? { ...r, problem: talk } : r),
|
|
33
33
|
...ofPlugins,
|
|
34
34
|
];
|
|
35
35
|
}
|
|
36
|
+
/** A runner's answer to `problem`, as the studio lists it. */
|
|
37
|
+
function problemOf(answer) {
|
|
38
|
+
if (!answer) {
|
|
39
|
+
return { problem: null };
|
|
40
|
+
}
|
|
41
|
+
if (typeof answer === "string") {
|
|
42
|
+
return { problem: answer, problemKind: "setup" };
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
problem: answer.text || "The runner is not ready.",
|
|
46
|
+
problemKind: answer.kind ?? "setup",
|
|
47
|
+
problemNote: answer.note ?? null,
|
|
48
|
+
problemPlan: answer.kind === "plan" ? (answer.plan ?? null) : null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
36
51
|
/**
|
|
37
52
|
* How a wizard is offered, on the runners there are: a runner switched on that is gone (a
|
|
38
53
|
* plugin the tenant lost) is left out, and the link opens the first one left.
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { FIELD_WAYS, formatsFor, isDecidedField, shownFields, slotText, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { and, count, desc, eq, gte, inArray, max, or, sql } from "drizzle-orm";
|
|
3
4
|
import { canSpend, MICROS_PER_CREDIT } from "../credits/credits.js";
|
|
4
5
|
import { db, schema } from "../db/client.js";
|
|
5
6
|
import { answerAsk } from "../engine/asks.js";
|
|
6
7
|
import { emitEvent, recentEvents, watchRun } from "../engine/events.js";
|
|
8
|
+
import { fieldErrorsIn } from "../engine/input.js";
|
|
7
9
|
import { cancel, createRun, goBack, NoCreditsError, pageContext, RunConflict, RunInputError, retry, reviewStep, submitPage, } from "../engine/runner.js";
|
|
8
|
-
import { ModelUnavailableError } from "../model-errors.js";
|
|
10
|
+
import { ModelUnavailableError, saidOf } from "../model-errors.js";
|
|
9
11
|
import { signedUrl } from "../secrets/signing.js";
|
|
10
12
|
import { currentTenant } from "../tenants/tenant.js";
|
|
11
13
|
import { notFound, ServiceError } from "./errors.js";
|
|
@@ -16,7 +18,7 @@ export async function startTestRun(userId, wizardId, autopilot) {
|
|
|
16
18
|
const w = await ownedWizard(userId, wizardId);
|
|
17
19
|
const { definition, files } = await requireClean(w);
|
|
18
20
|
if (!(await canSpend())) {
|
|
19
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
21
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
20
22
|
}
|
|
21
23
|
const runId = await createRun({
|
|
22
24
|
wizardId: w.id,
|
|
@@ -27,7 +29,7 @@ export async function startTestRun(userId, wizardId, autopilot) {
|
|
|
27
29
|
userId,
|
|
28
30
|
unattended: Boolean(autopilot),
|
|
29
31
|
}).catch((err) => {
|
|
30
|
-
throw err instanceof ModelUnavailableError ? new ServiceError("refused", err
|
|
32
|
+
throw err instanceof ModelUnavailableError ? new ServiceError("refused", saidOf(err)) : err;
|
|
31
33
|
});
|
|
32
34
|
if (autopilot) {
|
|
33
35
|
fly(runId, autopilot.answers, autopilot.acceptReviews);
|
|
@@ -78,9 +80,14 @@ function fly(runId, answers, acceptReviews) {
|
|
|
78
80
|
} while (again);
|
|
79
81
|
}
|
|
80
82
|
catch (err) {
|
|
83
|
+
const answers = (lang) => err instanceof RunInputError
|
|
84
|
+
? fieldErrorsIn(err.errors, lang)
|
|
85
|
+
.map((e) => `${e.field} – ${e.message}`)
|
|
86
|
+
.join("; ")
|
|
87
|
+
: "";
|
|
81
88
|
const message = err instanceof RunInputError
|
|
82
|
-
? `Antworten fehlen oder passen nicht: ${
|
|
83
|
-
: err
|
|
89
|
+
? bilingual(`Antworten fehlen oder passen nicht: ${answers("de")}`, `Answers are missing or do not fit: ${answers("en")}`)
|
|
90
|
+
: saidOf(err);
|
|
84
91
|
await emitEvent(runId, null, "error", message).catch(() => undefined);
|
|
85
92
|
land();
|
|
86
93
|
}
|
|
@@ -347,10 +354,10 @@ export const testRunReport = (userId, runId, waitSeconds = 0) => runReport(userI
|
|
|
347
354
|
/** A command the run refused, as a tool error the client can act on. */
|
|
348
355
|
export function refusedInput(err) {
|
|
349
356
|
if (err instanceof RunInputError) {
|
|
350
|
-
throw new ServiceError("invalid", err
|
|
357
|
+
throw new ServiceError("invalid", saidOf(err), { fields: fieldErrorsIn(err.errors, "en") });
|
|
351
358
|
}
|
|
352
359
|
if (err instanceof RunConflict) {
|
|
353
|
-
throw new ServiceError("refused", err
|
|
360
|
+
throw new ServiceError("refused", saidOf(err));
|
|
354
361
|
}
|
|
355
362
|
throw err;
|
|
356
363
|
}
|
|
@@ -370,7 +377,7 @@ export async function startRun(userId, wizardId, answers = {}) {
|
|
|
370
377
|
throw notFound();
|
|
371
378
|
}
|
|
372
379
|
if (!(await canSpend())) {
|
|
373
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
380
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
374
381
|
}
|
|
375
382
|
const runId = await createRun({
|
|
376
383
|
wizardId: w.id,
|
|
@@ -381,10 +388,10 @@ export async function startRun(userId, wizardId, answers = {}) {
|
|
|
381
388
|
userId,
|
|
382
389
|
}).catch((err) => {
|
|
383
390
|
if (err instanceof ModelUnavailableError) {
|
|
384
|
-
throw new ServiceError("refused", err
|
|
391
|
+
throw new ServiceError("refused", saidOf(err));
|
|
385
392
|
}
|
|
386
393
|
if (err instanceof NoCreditsError) {
|
|
387
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
394
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
388
395
|
}
|
|
389
396
|
throw err;
|
|
390
397
|
});
|
|
@@ -400,7 +407,7 @@ export async function startRun(userId, wizardId, answers = {}) {
|
|
|
400
407
|
}
|
|
401
408
|
catch (err) {
|
|
402
409
|
if (err instanceof RunInputError) {
|
|
403
|
-
return { runId, refused: err.errors };
|
|
410
|
+
return { runId, refused: fieldErrorsIn(err.errors, "en") };
|
|
404
411
|
}
|
|
405
412
|
throw err;
|
|
406
413
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
1
2
|
import { and, eq, isNotNull, lt } from "drizzle-orm";
|
|
2
3
|
import { nanoid } from "nanoid";
|
|
3
4
|
import { db, schema } from "../db/client.js";
|
|
@@ -12,7 +13,7 @@ export const shareUrlFor = (token) => `${env.publicUrl}/s/${token}`;
|
|
|
12
13
|
/** Shares a finished run. Idempotent: the same run keeps the same link. */
|
|
13
14
|
export async function shareRun(run) {
|
|
14
15
|
if (run.status !== "done") {
|
|
15
|
-
throw new ServiceError("refused", "Nur fertige Ergebnisse lassen sich teilen.");
|
|
16
|
+
throw new ServiceError("refused", bilingual("Nur fertige Ergebnisse lassen sich teilen.", "Only finished results can be shared."));
|
|
16
17
|
}
|
|
17
18
|
let token = run.shareToken;
|
|
18
19
|
if (!token) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { factKey, PROJECT_LIMITS } from "@engenty-wizards/shared/projects";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { nanoid } from "nanoid";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { runStudioAssistant } from "../agents/studio-assistant.js";
|
|
5
6
|
import { canSpend } from "../credits/credits.js";
|
|
7
|
+
import { errorText } from "../model-errors.js";
|
|
6
8
|
import { currentTenant } from "../tenants/tenant.js";
|
|
7
9
|
import { projectWritable, requireWritable } from "./access.js";
|
|
8
10
|
import { apiSpace, apiSpaceDetails } from "./api.js";
|
|
@@ -55,6 +57,8 @@ export const siteSchema = z.object({
|
|
|
55
57
|
/** Let the space assistant fill in about and facts; false writes only what the site states. */
|
|
56
58
|
assist: z.boolean().default(true),
|
|
57
59
|
});
|
|
60
|
+
/** The language the site's people read: German for a German site, else English. */
|
|
61
|
+
const siteLang = (input) => input.language?.toLowerCase().startsWith("de") ? "de" : "en";
|
|
58
62
|
const jobs = new Map();
|
|
59
63
|
const KEEP_MS = 60 * 60 * 1000;
|
|
60
64
|
function forget() {
|
|
@@ -146,16 +150,16 @@ function assistMessage(input) {
|
|
|
146
150
|
.slice(0, 6)
|
|
147
151
|
.map((p) => `## ${p.title}${p.url ? ` (${p.url})` : ""}${p.role ? ` [${p.role}]` : ""}\n\n${p.markdown.slice(0, 6000)}`);
|
|
148
152
|
return [
|
|
149
|
-
`
|
|
150
|
-
"
|
|
151
|
-
`Name: ${input.name || "—"}\
|
|
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
|
+
"The details below come straight from WordPress. Read the website with read_website only when something important is missing. Invent nothing.",
|
|
155
|
+
`Name: ${input.name || "—"}\nTagline: ${input.tagline || "—"}\nLanguage: ${input.language ?? "—"}`,
|
|
152
156
|
input.facts.length
|
|
153
|
-
? `
|
|
157
|
+
? `What the website states:\n${input.facts.map((f) => `- ${f.label}: ${f.value}`).join("\n")}`
|
|
154
158
|
: "",
|
|
155
159
|
input.colors.length
|
|
156
|
-
? `
|
|
160
|
+
? `The theme's colours: ${input.colors.map((c) => `${c.name} ${c.value}`).join(", ")}`
|
|
157
161
|
: "",
|
|
158
|
-
pages.length ? `
|
|
162
|
+
pages.length ? `Pages:\n\n${pages.join("\n\n")}` : "",
|
|
159
163
|
]
|
|
160
164
|
.filter(Boolean)
|
|
161
165
|
.join("\n\n")
|
|
@@ -163,7 +167,10 @@ function assistMessage(input) {
|
|
|
163
167
|
}
|
|
164
168
|
async function work(job, projectId, input) {
|
|
165
169
|
try {
|
|
166
|
-
job.activity =
|
|
170
|
+
job.activity =
|
|
171
|
+
siteLang(input) === "de"
|
|
172
|
+
? "Übernimmt die Angaben der Website …"
|
|
173
|
+
: "Taking over what the website says …";
|
|
167
174
|
await fillFromSite(job.userId, projectId, input);
|
|
168
175
|
job.activity = "Schreibt die Seiten in Wissen …";
|
|
169
176
|
job.pages = await writePages(projectId, input);
|
|
@@ -181,7 +188,7 @@ async function work(job, projectId, input) {
|
|
|
181
188
|
message: assistMessage(input),
|
|
182
189
|
page: null,
|
|
183
190
|
browserTools: [],
|
|
184
|
-
lang: input
|
|
191
|
+
lang: siteLang(input),
|
|
185
192
|
onActivity: (label) => {
|
|
186
193
|
job.activity = label;
|
|
187
194
|
},
|
|
@@ -201,7 +208,11 @@ async function work(job, projectId, input) {
|
|
|
201
208
|
catch (err) {
|
|
202
209
|
job.status = "failed";
|
|
203
210
|
job.error =
|
|
204
|
-
err instanceof ServiceError
|
|
211
|
+
err instanceof ServiceError
|
|
212
|
+
? errorText(err, siteLang(input))
|
|
213
|
+
: siteLang(input) === "de"
|
|
214
|
+
? "Die Website ließ sich nicht übernehmen."
|
|
215
|
+
: "The website could not be taken over.";
|
|
205
216
|
console.error("[site]", err);
|
|
206
217
|
}
|
|
207
218
|
}
|
|
@@ -256,7 +267,7 @@ function siteJobView(job) {
|
|
|
256
267
|
export function siteJob(jobId) {
|
|
257
268
|
const job = jobs.get(jobId);
|
|
258
269
|
if (!job || job.tenantId !== currentTenant()) {
|
|
259
|
-
throw new ServiceError("not_found", "Diesen Vorgang gibt es nicht (mehr).");
|
|
270
|
+
throw new ServiceError("not_found", bilingual("Diesen Vorgang gibt es nicht (mehr).", "There is no such job (any more)."));
|
|
260
271
|
}
|
|
261
272
|
return siteJobView(job);
|
|
262
273
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CATEGORY_TYPES, dayOf, } from "@engenty-wizards/shared/knowledge";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { and, asc, eq, inArray, isNotNull, or, sql } from "drizzle-orm";
|
|
3
4
|
import { nanoid } from "nanoid";
|
|
4
5
|
import { db, schema } from "../db/client.js";
|
|
@@ -272,10 +273,10 @@ const MAX_CATEGORIES = 60;
|
|
|
272
273
|
function checkDraft(draft) {
|
|
273
274
|
const name = draft.name.replace(/\s+/g, " ").trim().slice(0, 80);
|
|
274
275
|
if (!name) {
|
|
275
|
-
throw new ServiceError("invalid", "Eine Kategorie braucht einen Namen.");
|
|
276
|
+
throw new ServiceError("invalid", bilingual("Eine Kategorie braucht einen Namen.", "A category needs a name."));
|
|
276
277
|
}
|
|
277
278
|
if (!CATEGORY_TYPES.includes(draft.type)) {
|
|
278
|
-
throw new ServiceError("invalid", `Unbekannter Typ „${draft.type}
|
|
279
|
+
throw new ServiceError("invalid", bilingual(`Unbekannter Typ „${draft.type}“.`, `Unknown type “${draft.type}”.`));
|
|
279
280
|
}
|
|
280
281
|
return name;
|
|
281
282
|
}
|
|
@@ -329,7 +330,7 @@ export async function putCategory(projectId, draft, opts = {}) {
|
|
|
329
330
|
return known;
|
|
330
331
|
}
|
|
331
332
|
if (rows.length >= MAX_CATEGORIES) {
|
|
332
|
-
throw new ServiceError("refused", `Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`);
|
|
333
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`, `A space has at most ${MAX_CATEGORIES} categories.`));
|
|
333
334
|
}
|
|
334
335
|
const id = nanoid(12);
|
|
335
336
|
const [row] = await db
|
|
@@ -408,7 +409,7 @@ export async function updateCategory(categoryId, patch) {
|
|
|
408
409
|
if (name !== row.name) {
|
|
409
410
|
const clash = byName(await categoryRows(row.projectId), name);
|
|
410
411
|
if (clash && clash.id !== row.id) {
|
|
411
|
-
throw new ServiceError("invalid", `Die Kategorie „${name}“ gibt es schon.`);
|
|
412
|
+
throw new ServiceError("invalid", bilingual(`Die Kategorie „${name}“ gibt es schon.`, `The category “${name}” already exists.`));
|
|
412
413
|
}
|
|
413
414
|
}
|
|
414
415
|
await db
|
|
@@ -458,7 +459,7 @@ export async function addCategoryValue(categoryId, value) {
|
|
|
458
459
|
const category = await categoryRow(categoryId);
|
|
459
460
|
await requireWritableProject(category.projectId);
|
|
460
461
|
if (category.type !== "choice") {
|
|
461
|
-
throw new ServiceError("invalid", "Nur eine Auswahl hat Werte.");
|
|
462
|
+
throw new ServiceError("invalid", bilingual("Nur eine Auswahl hat Werte.", "Only a choice has values."));
|
|
462
463
|
}
|
|
463
464
|
const ids = await addValues(category.id, [value]);
|
|
464
465
|
return { id: [...ids.values()].at(-1) };
|
|
@@ -488,7 +489,7 @@ export async function mergeCategoryValues(fromId, intoId) {
|
|
|
488
489
|
const from = await valueRow(fromId);
|
|
489
490
|
const into = await valueRow(intoId);
|
|
490
491
|
if (from.category.id !== into.category.id || fromId === intoId) {
|
|
491
|
-
throw new ServiceError("invalid", "Nur Werte einer Kategorie lassen sich zusammenführen.");
|
|
492
|
+
throw new ServiceError("invalid", bilingual("Nur Werte einer Kategorie lassen sich zusammenführen.", "Only values of one category can be merged."));
|
|
492
493
|
}
|
|
493
494
|
await requireWritableProject(from.category.projectId);
|
|
494
495
|
const t = schema.spaceItemCategory;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { TableColumnValueError, tableColumnsSchema, } from "@engenty-wizards/shared/engenty/data-tables";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { editCells, SPACE_DATA_LIMITS, } from "@engenty-wizards/shared/space-data";
|
|
3
4
|
import { rowKey } from "@engenty-wizards/shared/store";
|
|
4
5
|
import { and, asc, count, desc, eq, inArray, isNotNull, isNull, max } from "drizzle-orm";
|
|
5
6
|
import { nanoid } from "nanoid";
|
|
6
7
|
import { db, schema } from "../db/client.js";
|
|
8
|
+
import { saidOf } from "../model-errors.js";
|
|
7
9
|
import { currentTenant } from "../tenants/tenant.js";
|
|
8
10
|
import { projectWritable, requireWritableProject } from "./access.js";
|
|
9
11
|
import { notFound, ServiceError } from "./errors.js";
|
|
@@ -21,20 +23,20 @@ async function ownerOf(projectId, wizardId) {
|
|
|
21
23
|
columns: { id: true },
|
|
22
24
|
});
|
|
23
25
|
if (!w) {
|
|
24
|
-
throw new ServiceError("invalid", "Diesen Wizard gibt es im Space nicht.");
|
|
26
|
+
throw new ServiceError("invalid", bilingual("Diesen Wizard gibt es im Space nicht.", "There is no such wizard in the space."));
|
|
25
27
|
}
|
|
26
28
|
return w.id;
|
|
27
29
|
}
|
|
28
30
|
function cellError(err) {
|
|
29
31
|
if (err instanceof TableColumnValueError) {
|
|
30
|
-
throw new ServiceError("invalid", err
|
|
32
|
+
throw new ServiceError("invalid", saidOf(err), { column: err.columnId });
|
|
31
33
|
}
|
|
32
34
|
throw err;
|
|
33
35
|
}
|
|
34
36
|
export function checkColumns(columns) {
|
|
35
37
|
const parsed = tableColumnsSchema.safeParse(columns);
|
|
36
38
|
if (!parsed.success) {
|
|
37
|
-
throw new ServiceError("invalid", parsed.error.issues[0]?.message ?? "Ungültige Spalten.");
|
|
39
|
+
throw new ServiceError("invalid", parsed.error.issues[0]?.message ?? bilingual("Ungültige Spalten.", "Invalid columns."));
|
|
38
40
|
}
|
|
39
41
|
return parsed.data;
|
|
40
42
|
}
|
|
@@ -102,10 +104,11 @@ async function tableCount(projectId) {
|
|
|
102
104
|
.from(schema.spaceTable)
|
|
103
105
|
.where(eq(schema.spaceTable.projectId, projectId));
|
|
104
106
|
if (n >= SPACE_DATA_LIMITS.tables) {
|
|
105
|
-
throw new ServiceError("refused", `Ein Space hat höchstens ${SPACE_DATA_LIMITS.tables} Tabellen.`);
|
|
107
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.tables} Tabellen.`, `A space has at most ${SPACE_DATA_LIMITS.tables} tables.`));
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
|
-
export async function createTable(userId, projectId, input) {
|
|
110
|
+
export async function createTable(userId, projectId, input, lang = "en") {
|
|
111
|
+
const de = lang === "de";
|
|
109
112
|
const project = await ownedProject(userId, projectId);
|
|
110
113
|
await requireWritableProject(project.id);
|
|
111
114
|
await tableCount(project.id);
|
|
@@ -113,10 +116,10 @@ export async function createTable(userId, projectId, input) {
|
|
|
113
116
|
? checkColumns(input.columns)
|
|
114
117
|
: input.format === "faq"
|
|
115
118
|
? [
|
|
116
|
-
{ id: "
|
|
119
|
+
{ id: "question", name: de ? "Frage" : "Question", type: "text" },
|
|
117
120
|
{
|
|
118
|
-
id: "
|
|
119
|
-
name: "Antwort",
|
|
121
|
+
id: "answer",
|
|
122
|
+
name: de ? "Antwort" : "Answer",
|
|
120
123
|
type: "text",
|
|
121
124
|
format: { style: "multiline" },
|
|
122
125
|
},
|
|
@@ -124,7 +127,7 @@ export async function createTable(userId, projectId, input) {
|
|
|
124
127
|
: [{ id: "name", name: "Name", type: "text" }];
|
|
125
128
|
const id = nanoid(12);
|
|
126
129
|
const wizardId = await ownerOf(project.id, input.wizardId);
|
|
127
|
-
const title = input.title.trim() || "Tabelle";
|
|
130
|
+
const title = input.title.trim() || (de ? "Tabelle" : "Table");
|
|
128
131
|
await db.insert(schema.spaceTable).values({
|
|
129
132
|
id,
|
|
130
133
|
tenantId: currentTenant(),
|
|
@@ -184,7 +187,7 @@ export async function getTable(userId, tableId) {
|
|
|
184
187
|
export async function updateTable(_userId, tableId, patch) {
|
|
185
188
|
const table = await writableTable(tableId);
|
|
186
189
|
if (table.list && (patch.columns !== undefined || patch.wizardId !== undefined)) {
|
|
187
|
-
throw new ServiceError("refused", "Diese Tabelle gehört zu einer Liste des Wizards: ihre Spalten kommen aus dem Wizard.");
|
|
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."));
|
|
188
191
|
}
|
|
189
192
|
const title = patch.title !== undefined ? patch.title.trim() || table.title : table.title;
|
|
190
193
|
const wizardId = patch.wizardId !== undefined ? await ownerOf(table.projectId, patch.wizardId) : table.wizardId;
|
|
@@ -247,7 +250,7 @@ async function refuseClash(tableId, key, rowId) {
|
|
|
247
250
|
columns: { id: true },
|
|
248
251
|
});
|
|
249
252
|
if (other && other.id !== rowId) {
|
|
250
|
-
throw new ServiceError("invalid", "Diesen Eintrag gibt es schon.");
|
|
253
|
+
throw new ServiceError("invalid", bilingual("Diesen Eintrag gibt es schon.", "This entry already exists."));
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
/** After a row of a table of Wissen changed: its Kategorien from its columns, its unit. */
|
|
@@ -266,7 +269,7 @@ export async function addTableRow(_userId, tableId, cells = {}) {
|
|
|
266
269
|
.from(schema.spaceTableRow)
|
|
267
270
|
.where(eq(schema.spaceTableRow.tableId, table.id));
|
|
268
271
|
if (n >= SPACE_DATA_LIMITS.rowsPerTable) {
|
|
269
|
-
throw new ServiceError("refused", `Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`);
|
|
272
|
+
throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table has at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
|
|
270
273
|
}
|
|
271
274
|
let value;
|
|
272
275
|
try {
|
|
@@ -331,7 +334,7 @@ async function pageRow(pageId) {
|
|
|
331
334
|
}
|
|
332
335
|
function checkMarkdown(markdown) {
|
|
333
336
|
if (markdown.length > SPACE_DATA_LIMITS.pageChars) {
|
|
334
|
-
throw new ServiceError("refused", `Eine Seite hat höchstens ${SPACE_DATA_LIMITS.pageChars.toLocaleString("de")} Zeichen.`);
|
|
337
|
+
throw new ServiceError("refused", bilingual(`Eine Seite hat höchstens ${SPACE_DATA_LIMITS.pageChars.toLocaleString("de")} Zeichen.`, `A page has at most ${SPACE_DATA_LIMITS.pageChars.toLocaleString("en")} characters.`));
|
|
335
338
|
}
|
|
336
339
|
return markdown;
|
|
337
340
|
}
|
|
@@ -341,7 +344,7 @@ async function pageCount(projectId, more = 1) {
|
|
|
341
344
|
.from(schema.spacePage)
|
|
342
345
|
.where(eq(schema.spacePage.projectId, projectId));
|
|
343
346
|
if (n + more > SPACE_DATA_LIMITS.pages) {
|
|
344
|
-
throw new ServiceError("refused", `Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`);
|
|
347
|
+
throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space has at most ${SPACE_DATA_LIMITS.pages} pages.`));
|
|
345
348
|
}
|
|
346
349
|
}
|
|
347
350
|
/** A page of the space's own that a sub-page may stand under: never one of its own sub-pages. */
|
|
@@ -354,11 +357,11 @@ async function parentOf(projectId, parentId, pageId) {
|
|
|
354
357
|
columns: { id: true, wizardId: true, parentId: true },
|
|
355
358
|
});
|
|
356
359
|
if (!parent || parent.wizardId) {
|
|
357
|
-
throw new ServiceError("invalid", "Unter diese Seite kann keine Seite.");
|
|
360
|
+
throw new ServiceError("invalid", bilingual("Unter diese Seite kann keine Seite.", "No page can go under this page."));
|
|
358
361
|
}
|
|
359
362
|
for (let at = parent.id; at && pageId;) {
|
|
360
363
|
if (at === pageId) {
|
|
361
|
-
throw new ServiceError("invalid", "Eine Seite kann nicht unter sich selbst stehen.");
|
|
364
|
+
throw new ServiceError("invalid", bilingual("Eine Seite kann nicht unter sich selbst stehen.", "A page cannot stand under itself."));
|
|
362
365
|
}
|
|
363
366
|
const up = await db.query.spacePage.findFirst({
|
|
364
367
|
where: eq(schema.spacePage.id, at),
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { tableColumnsSchema } from "@engenty-wizards/shared/engenty/data-tables";
|
|
3
3
|
import { CATEGORY_TYPES } from "@engenty-wizards/shared/knowledge";
|
|
4
4
|
import { PROJECT_LIMITS } from "@engenty-wizards/shared/projects";
|
|
5
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
5
6
|
import { SPACE_DATA_LIMITS } from "@engenty-wizards/shared/space-data";
|
|
6
7
|
import { WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
|
|
7
8
|
import { and, count, eq, inArray, isNull, notInArray, sql } from "drizzle-orm";
|
|
@@ -150,8 +151,8 @@ async function requireRoom(spaceId) {
|
|
|
150
151
|
const limit = await projectLimit();
|
|
151
152
|
if (others.length >= limit) {
|
|
152
153
|
throw new ServiceError("refused", limit === 1
|
|
153
|
-
? "Dieses Konto hat hier schon ein Projekt."
|
|
154
|
-
: `Dieses Konto hat hier schon ${limit} Projekte.`, {
|
|
154
|
+
? bilingual("Dieses Konto hat hier schon ein Projekt.", "This account already has a project here.")
|
|
155
|
+
: bilingual(`Dieses Konto hat hier schon ${limit} Projekte.`, `This account already has ${limit} projects here.`), {
|
|
155
156
|
reason: "space_limit",
|
|
156
157
|
limit,
|
|
157
158
|
spaces: others.filter((p) => p.origin === "local").map((p) => ({ id: p.id, name: p.name })),
|
|
@@ -165,7 +166,7 @@ async function requireWizardRoom(spaceId) {
|
|
|
165
166
|
.from(schema.wizard)
|
|
166
167
|
.where(eq(schema.wizard.projectId, spaceId));
|
|
167
168
|
if ((row?.n ?? 0) >= env.limits.syncedWizards) {
|
|
168
|
-
throw new ServiceError("refused", `Hier haben höchstens ${env.limits.syncedWizards} veröffentlichte Wizards Platz.`, { reason: "wizard_limit", limit: env.limits.syncedWizards });
|
|
169
|
+
throw new ServiceError("refused", bilingual(`Hier haben höchstens ${env.limits.syncedWizards} veröffentlichte Wizards Platz.`, `There is room for at most ${env.limits.syncedWizards} published wizards here.`), { reason: "wizard_limit", limit: env.limits.syncedWizards });
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
const hashOf = (data) => createHash("sha256").update(data).digest("hex");
|
|
@@ -199,7 +200,7 @@ async function syncLogo(projectId, logo) {
|
|
|
199
200
|
async function syncSpace(spaceId, space) {
|
|
200
201
|
const existing = await syncedProject(spaceId);
|
|
201
202
|
if (existing && existing.origin !== "local") {
|
|
202
|
-
throw new ServiceError("refused", "Ein Projekt mit dieser Kennung wurde hier angelegt.", {
|
|
203
|
+
throw new ServiceError("refused", bilingual("Ein Projekt mit dieser Kennung wurde hier angelegt.", "A project with this id was created here."), {
|
|
203
204
|
reason: "id_taken",
|
|
204
205
|
});
|
|
205
206
|
}
|
|
@@ -250,7 +251,7 @@ export async function syncWizard(rawSpaceId, rawWizardId, input) {
|
|
|
250
251
|
where: and(eq(schema.wizard.id, wizardId), eq(schema.wizard.tenantId, currentTenant())),
|
|
251
252
|
});
|
|
252
253
|
if (existing && existing.projectId !== spaceId) {
|
|
253
|
-
throw new ServiceError("refused", "Ein Wizard mit dieser Kennung gehört hier woanders hin.", {
|
|
254
|
+
throw new ServiceError("refused", bilingual("Ein Wizard mit dieser Kennung gehört hier woanders hin.", "A wizard with this id belongs elsewhere here."), {
|
|
254
255
|
reason: "id_taken",
|
|
255
256
|
});
|
|
256
257
|
}
|
|
@@ -506,7 +507,7 @@ export async function syncSpaceData(rawSpaceId, input) {
|
|
|
506
507
|
: [],
|
|
507
508
|
]);
|
|
508
509
|
if ([...tablesHere, ...pagesHere].some((x) => x.projectId !== spaceId || x.wizardId !== null)) {
|
|
509
|
-
throw new ServiceError("refused", "Eine Kennung ist hier schon vergeben.", {
|
|
510
|
+
throw new ServiceError("refused", bilingual("Eine Kennung ist hier schon vergeben.", "An id is already taken here."), {
|
|
510
511
|
reason: "id_taken",
|
|
511
512
|
});
|
|
512
513
|
}
|
|
@@ -584,7 +585,7 @@ async function syncCategories(spaceId, categories, assignments) {
|
|
|
584
585
|
})
|
|
585
586
|
: [];
|
|
586
587
|
if (taken.some((c) => c.projectId !== spaceId)) {
|
|
587
|
-
throw new ServiceError("refused", "Eine Kennung ist hier schon vergeben.", {
|
|
588
|
+
throw new ServiceError("refused", bilingual("Eine Kennung ist hier schon vergeben.", "An id is already taken here."), {
|
|
588
589
|
reason: "id_taken",
|
|
589
590
|
});
|
|
590
591
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { versionChanges } from "@engenty-wizards/shared/changes";
|
|
2
2
|
import { pluginToolOf, shapeIssues, validateWizard, wizardSchema, } from "@engenty-wizards/shared/definition";
|
|
3
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
3
4
|
import { and, desc, eq, sql } from "drizzle-orm";
|
|
4
5
|
import { nanoid } from "nanoid";
|
|
5
6
|
import { applyOps, OpError } from "../authoring/ops.js";
|
|
6
7
|
import { listConnectors } from "../connectors/external.js";
|
|
7
8
|
import { db, schema } from "../db/client.js";
|
|
8
9
|
import { env } from "../env.js";
|
|
10
|
+
import { saidOf } from "../model-errors.js";
|
|
9
11
|
import { pluginToolsOf } from "../plugins/registry.js";
|
|
10
12
|
import { dropLinks, putLink } from "../tenants/control.js";
|
|
11
13
|
import { currentTenant } from "../tenants/tenant.js";
|
|
@@ -17,21 +19,33 @@ import { forgetWizardLinks } from "./links.js";
|
|
|
17
19
|
import { asLang, countInstall, marketplaceWizard } from "./marketplace.js";
|
|
18
20
|
import { defaultProject, ownedProject } from "./projects.js";
|
|
19
21
|
import { checkRunnerSettings } from "./runners.js";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
/** The wizard a new one starts as, in the language of whoever makes it. */
|
|
23
|
+
function blankWizard(lang) {
|
|
24
|
+
const de = lang === "de";
|
|
25
|
+
return {
|
|
26
|
+
version: 1,
|
|
27
|
+
title: de ? "Neuer Wizard" : "New wizard",
|
|
28
|
+
description: "",
|
|
29
|
+
avatar: "round",
|
|
30
|
+
lang,
|
|
31
|
+
steps: [
|
|
32
|
+
{
|
|
33
|
+
id: "start",
|
|
34
|
+
type: "page",
|
|
35
|
+
title: de ? "Los geht's" : "Let's start",
|
|
36
|
+
fields: [
|
|
37
|
+
{
|
|
38
|
+
id: "input",
|
|
39
|
+
label: de ? "Worum geht es?" : "What is it about?",
|
|
40
|
+
kind: "textarea",
|
|
41
|
+
required: true,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
{ id: "done", type: "result", title: de ? "Fertig" : "Done", deliverables: [] },
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
35
49
|
export const studioUrl = (wizardId) => `${env.appUrl}/studio/edit/${wizardId}`;
|
|
36
50
|
export const shareUrl = (token) => `${env.publicUrl}/w/${token}`;
|
|
37
51
|
const newShareToken = () => nanoid(14);
|
|
@@ -227,9 +241,16 @@ export async function createWizard(userId, input, writer = { source: "studio" })
|
|
|
227
241
|
if (input.starterId && !starter) {
|
|
228
242
|
throw new ServiceError("invalid", `There is no starter "${input.starterId}".`);
|
|
229
243
|
}
|
|
244
|
+
const maker = input.lang?.toLowerCase().startsWith("de") ? "de" : "en";
|
|
230
245
|
const { draft, issues } = input.definition !== undefined
|
|
231
246
|
? parseDraft(input.definition)
|
|
232
|
-
:
|
|
247
|
+
: starter
|
|
248
|
+
? // A starter names the language it is written in, where its definition does not.
|
|
249
|
+
{
|
|
250
|
+
draft: { ...starter.definition, lang: starter.definition.lang ?? starter.language },
|
|
251
|
+
issues: [],
|
|
252
|
+
}
|
|
253
|
+
: { draft: blankWizard(maker), issues: [] };
|
|
233
254
|
const fromStarter = input.definition === undefined ? starter : undefined;
|
|
234
255
|
const id = nanoid(12);
|
|
235
256
|
const shareToken = newShareToken();
|
|
@@ -287,7 +308,7 @@ export async function writeDraft(userId, wizardId, input, writer = { source: "st
|
|
|
287
308
|
.returning({ revision: schema.wizard.revision });
|
|
288
309
|
if (!row) {
|
|
289
310
|
const current = await ownedWizard(userId, wizardId);
|
|
290
|
-
throw new ServiceError("revision_conflict", "Der Wizard wurde inzwischen geändert.", {
|
|
311
|
+
throw new ServiceError("revision_conflict", bilingual("Der Wizard wurde inzwischen geändert.", "The wizard has been changed in the meantime."), {
|
|
291
312
|
revision: current.revision,
|
|
292
313
|
});
|
|
293
314
|
}
|
|
@@ -308,7 +329,7 @@ export async function writeDraft(userId, wizardId, input, writer = { source: "st
|
|
|
308
329
|
export async function editWizard(userId, wizardId, input, writer) {
|
|
309
330
|
const w = await writableWizard(userId, wizardId);
|
|
310
331
|
if (w.revision !== input.baseRevision) {
|
|
311
|
-
throw new ServiceError("revision_conflict", "Der Wizard wurde inzwischen geändert.", {
|
|
332
|
+
throw new ServiceError("revision_conflict", bilingual("Der Wizard wurde inzwischen geändert.", "The wizard has been changed in the meantime."), {
|
|
312
333
|
revision: w.revision,
|
|
313
334
|
});
|
|
314
335
|
}
|
|
@@ -318,7 +339,7 @@ export async function editWizard(userId, wizardId, input, writer) {
|
|
|
318
339
|
}
|
|
319
340
|
catch (err) {
|
|
320
341
|
if (err instanceof OpError) {
|
|
321
|
-
throw new ServiceError("invalid", err
|
|
342
|
+
throw new ServiceError("invalid", saidOf(err));
|
|
322
343
|
}
|
|
323
344
|
throw err;
|
|
324
345
|
}
|
|
@@ -380,7 +401,7 @@ export async function requireClean(w) {
|
|
|
380
401
|
const files = await draftFiles(w.id);
|
|
381
402
|
const issues = await checkDraft(w.projectId, w.draft, files.map((f) => f.path));
|
|
382
403
|
if (issues.length) {
|
|
383
|
-
throw new ServiceError("has_issues", "Der Wizard hat noch Fehler.", { issues });
|
|
404
|
+
throw new ServiceError("has_issues", bilingual("Der Wizard hat noch Fehler.", "The wizard still has errors."), { issues });
|
|
384
405
|
}
|
|
385
406
|
return { definition: w.draft, files };
|
|
386
407
|
}
|