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
|
@@ -5,6 +5,7 @@ import { MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition
|
|
|
5
5
|
import { CATEGORY_TYPES, whereSchema } from "@engenty-wizards/shared/knowledge";
|
|
6
6
|
import { PROJECT_FILE_KINDS } from "@engenty-wizards/shared/projects";
|
|
7
7
|
import { isCreditsRef } from "@engenty-wizards/shared/providers";
|
|
8
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
8
9
|
import { PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKAGE_MIME, } from "@engenty-wizards/shared/wizard-package";
|
|
9
10
|
import { generateText } from "ai";
|
|
10
11
|
import { Hono } from "hono";
|
|
@@ -34,6 +35,7 @@ import { installMail, managed, renameTenant, tenantInfo, tenantMembers, userInfo
|
|
|
34
35
|
import { seenApps } from "../mcp/seen.js";
|
|
35
36
|
import { generateImageMedia, generateSpeechMedia, generateVideoMedia } from "../media/generate.js";
|
|
36
37
|
import { transcribeAudio } from "../media/transcribe.js";
|
|
38
|
+
import { errorText, saidOf } from "../model-errors.js";
|
|
37
39
|
import { checkKey, classProblem, classWays, embeddingModel, hasTextModel, LOCAL_KEYS, localModelSettings, ModelUnavailableError, modelSettings, saveLocalModels, saveModels, storedKey, textModel, } from "../models.js";
|
|
38
40
|
import { refreshPro } from "../plugins/pro.js";
|
|
39
41
|
import { HTML_RESPONSE_CSP } from "../render/guard.js";
|
|
@@ -48,7 +50,7 @@ import { categoryContents, listKnowledge, resolvePath, valueContents, } from "..
|
|
|
48
50
|
import { writeSummary } from "../services/knowledge-model.js";
|
|
49
51
|
import { applyWebsite, finishOnboarding, needsOnboarding } from "../services/onboarding.js";
|
|
50
52
|
import { exportWizard, importWizard } from "../services/package.js";
|
|
51
|
-
import { profileSchema, saveProfile, userProfile } from "../services/profile.js";
|
|
53
|
+
import { profileSchema, saveProfile, saveUserLang, userLang, userProfile, } from "../services/profile.js";
|
|
52
54
|
import { addProjectFile, addProjectFileFromUrl, fileView, orderProjectFiles, projectFile, projectFileContent, projectFiles, reindexProjectFile, removeProjectFile, updateProjectFile, } from "../services/project-files.js";
|
|
53
55
|
import { documentText, queueIndex, searchKnowledge } from "../services/project-index.js";
|
|
54
56
|
import { createProject, deleteProject, gradientSchema, listProjects, maskedServers, ownedProject, projectLimitOrNull, projectPatchSchema, updateProject, } from "../services/projects.js";
|
|
@@ -63,6 +65,7 @@ import { codeOf } from "../tenants/control.js";
|
|
|
63
65
|
import { currentTenant } from "../tenants/tenant.js";
|
|
64
66
|
import { applyUpdate, updateStatus } from "../update.js";
|
|
65
67
|
import { byteRange } from "./delivery.js";
|
|
68
|
+
import { inLang, requestLang } from "./lang.js";
|
|
66
69
|
/** The model sources a test call has worked on: the setup is done for those, not for the others. */
|
|
67
70
|
async function testedSources() {
|
|
68
71
|
const tested = await readSetting("setup-done");
|
|
@@ -215,6 +218,8 @@ export const studio = new Hono()
|
|
|
215
218
|
user: { id: user.id, name: profile.name, email: profile.email, image: user.image ?? null },
|
|
216
219
|
/** What the person says about themselves; name and e-mail are the account's when managed. */
|
|
217
220
|
profile,
|
|
221
|
+
/** The language the person picked, wherever they picked it; null until they do. */
|
|
222
|
+
lang: await userLang(user),
|
|
218
223
|
tenant: {
|
|
219
224
|
id: user.tenantId,
|
|
220
225
|
role: user.role,
|
|
@@ -305,6 +310,11 @@ export const studio = new Hono()
|
|
|
305
310
|
return c.json(await updateStatus());
|
|
306
311
|
})
|
|
307
312
|
.put("/profile", async (c) => c.json(await saveProfile(c.get("user"), profileSchema.parse(await c.req.json()))))
|
|
313
|
+
.put("/lang", async (c) => {
|
|
314
|
+
const { lang } = z.object({ lang: z.enum(["de", "en"]) }).parse(await c.req.json());
|
|
315
|
+
await saveUserLang(c.get("user"), lang);
|
|
316
|
+
return c.json({ lang });
|
|
317
|
+
})
|
|
308
318
|
// --- the studio's assistant -------------------------------------------------
|
|
309
319
|
// One open chat per person and tenant, on every page; turns stream as SSE events shaped like
|
|
310
320
|
// AG-UI's (packages/shared/src/assistant.ts).
|
|
@@ -323,10 +333,10 @@ export const studio = new Hono()
|
|
|
323
333
|
.post("/assistant/speak", async (c) => {
|
|
324
334
|
const { text } = z.object({ text: z.string().min(1).max(4000) }).parse(await c.req.json());
|
|
325
335
|
if ((await classWays()).speech.kind === "none") {
|
|
326
|
-
throw new ServiceError("refused", "Hier spricht kein Sprachmodell.");
|
|
336
|
+
throw new ServiceError("refused", bilingual("Hier spricht kein Sprachmodell.", "No language model speaks here."));
|
|
327
337
|
}
|
|
328
338
|
if (!(await canSpend())) {
|
|
329
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
339
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
330
340
|
}
|
|
331
341
|
const voice = await generateSpeechMedia({ text, abortSignal: c.req.raw.signal });
|
|
332
342
|
return c.body(new Uint8Array(voice.bytes), 200, { "content-type": voice.mime });
|
|
@@ -346,10 +356,10 @@ export const studio = new Hono()
|
|
|
346
356
|
.post("/assistant/turn", async (c) => {
|
|
347
357
|
const user = c.get("user");
|
|
348
358
|
const body = assistantTurnSchema
|
|
349
|
-
.extend({ lang: z.enum(["de", "en"]).default("
|
|
359
|
+
.extend({ lang: z.enum(["de", "en"]).default("en") })
|
|
350
360
|
.parse(await c.req.json());
|
|
351
361
|
if (!(await canSpend())) {
|
|
352
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
362
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
353
363
|
}
|
|
354
364
|
const project = body.projectId ? await ownedProject(user.id, body.projectId) : null;
|
|
355
365
|
const writable = project ? await projectWritable(project) : false;
|
|
@@ -460,7 +470,7 @@ export const studio = new Hono()
|
|
|
460
470
|
}
|
|
461
471
|
await send("error", {
|
|
462
472
|
message: err instanceof ServiceError || err instanceof ModelUnavailableError
|
|
463
|
-
? err.
|
|
473
|
+
? errorText(err, body.lang)
|
|
464
474
|
: body.lang === "de"
|
|
465
475
|
? "Da ist etwas schiefgelaufen. Bitte noch einmal versuchen."
|
|
466
476
|
: "Something went wrong. Please try again.",
|
|
@@ -511,7 +521,7 @@ export const studio = new Hono()
|
|
|
511
521
|
const kind = z.enum(PROJECT_FILE_KINDS).parse(c.req.query("kind"));
|
|
512
522
|
const file = (await c.req.formData()).get("file");
|
|
513
523
|
if (!(file instanceof File)) {
|
|
514
|
-
throw new ServiceError("invalid", "Bitte eine Datei wählen.");
|
|
524
|
+
throw new ServiceError("invalid", bilingual("Bitte eine Datei wählen.", "Please choose a file."));
|
|
515
525
|
}
|
|
516
526
|
const row = await addProjectFile(project.id, {
|
|
517
527
|
kind,
|
|
@@ -544,7 +554,7 @@ export const studio = new Hono()
|
|
|
544
554
|
await ownedProject(c.get("user").id, c.req.param("id"));
|
|
545
555
|
const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
|
|
546
556
|
const colors = await scanColors(url).catch(() => {
|
|
547
|
-
throw new ServiceError("invalid", "Die Seite ist nicht erreichbar.");
|
|
557
|
+
throw new ServiceError("invalid", bilingual("Die Seite ist nicht erreichbar.", "The page cannot be reached."));
|
|
548
558
|
});
|
|
549
559
|
return c.json({ colors });
|
|
550
560
|
})
|
|
@@ -560,7 +570,7 @@ export const studio = new Hono()
|
|
|
560
570
|
})
|
|
561
571
|
.parse(await c.req.json());
|
|
562
572
|
if (!(await canSpend())) {
|
|
563
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
573
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
564
574
|
}
|
|
565
575
|
try {
|
|
566
576
|
return c.json({
|
|
@@ -569,7 +579,7 @@ export const studio = new Hono()
|
|
|
569
579
|
}
|
|
570
580
|
catch (err) {
|
|
571
581
|
if (err instanceof ModelUnavailableError) {
|
|
572
|
-
throw new ServiceError("refused", err
|
|
582
|
+
throw new ServiceError("refused", saidOf(err));
|
|
573
583
|
}
|
|
574
584
|
throw err;
|
|
575
585
|
}
|
|
@@ -578,7 +588,7 @@ export const studio = new Hono()
|
|
|
578
588
|
await ownedProject(c.get("user").id, c.req.param("id"));
|
|
579
589
|
const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
|
|
580
590
|
const fonts = await scanFonts(url).catch(() => {
|
|
581
|
-
throw new ServiceError("invalid", "Die Seite ist nicht erreichbar.");
|
|
591
|
+
throw new ServiceError("invalid", bilingual("Die Seite ist nicht erreichbar.", "The page cannot be reached."));
|
|
582
592
|
});
|
|
583
593
|
return c.json({ fonts });
|
|
584
594
|
})
|
|
@@ -841,7 +851,7 @@ export const studio = new Hono()
|
|
|
841
851
|
format: z.enum(["faq"]).nullish(),
|
|
842
852
|
})
|
|
843
853
|
.parse(await c.req.json());
|
|
844
|
-
return c.json(await createTable(c.get("user").id, c.req.param("id"), body));
|
|
854
|
+
return c.json(await createTable(c.get("user").id, c.req.param("id"), body, requestLang(c)));
|
|
845
855
|
})
|
|
846
856
|
.get("/tables/:id", async (c) => c.json(await getTable(c.get("user").id, c.req.param("id"))))
|
|
847
857
|
.patch("/tables/:id", async (c) => {
|
|
@@ -934,9 +944,9 @@ export const studio = new Hono()
|
|
|
934
944
|
.post("/projects/:id/wizards/import", async (c) => {
|
|
935
945
|
const file = (await c.req.formData()).get("file");
|
|
936
946
|
if (!(file instanceof File) || !file.size || file.size > PACKAGE_MAX_BYTES) {
|
|
937
|
-
throw new ServiceError("invalid", `Bitte ein Wizard-Paket (${PACKAGE_EXTENSION}) bis 30 MB.`);
|
|
947
|
+
throw new ServiceError("invalid", bilingual(`Bitte ein Wizard-Paket (${PACKAGE_EXTENSION}) bis 30 MB.`, `Please a wizard package (${PACKAGE_EXTENSION}) of up to 30 MB.`));
|
|
938
948
|
}
|
|
939
|
-
return c.json(await importWizard(c.get("user").id, c.req.param("id"), new Uint8Array(await file.arrayBuffer()), file.name));
|
|
949
|
+
return c.json(await importWizard(c.get("user").id, c.req.param("id"), new Uint8Array(await file.arrayBuffer()), file.name, requestLang(c)));
|
|
940
950
|
})
|
|
941
951
|
.get("/wizards/:id/export", async (c) => {
|
|
942
952
|
const found = await exportWizard(c.get("user").id, c.req.param("id"), {
|
|
@@ -944,7 +954,9 @@ export const studio = new Hono()
|
|
|
944
954
|
knowledge: c.req.query("knowledge") === "1",
|
|
945
955
|
});
|
|
946
956
|
if (!found) {
|
|
947
|
-
return c.json({
|
|
957
|
+
return c.json({
|
|
958
|
+
error: inLang(c, "Der Wizard ist noch nicht veröffentlicht.", "The wizard is not published yet."),
|
|
959
|
+
}, 404);
|
|
948
960
|
}
|
|
949
961
|
const { name, zip } = found;
|
|
950
962
|
return c.body(new Uint8Array(zip), 200, {
|
|
@@ -1048,7 +1060,7 @@ export const studio = new Hono()
|
|
|
1048
1060
|
// The models the draft needs that this runtime cannot serve, by class, with the steps that call them.
|
|
1049
1061
|
.get("/wizards/:id/models", async (c) => {
|
|
1050
1062
|
const w = await ownedWizard(c.get("user").id, c.req.param("id"));
|
|
1051
|
-
return c.json({ missing: await missingModels(w.draft) });
|
|
1063
|
+
return c.json({ missing: await missingModels(w.draft, requestLang(c)) });
|
|
1052
1064
|
})
|
|
1053
1065
|
// A runtime that runs alone: the wizard's copy in the cloud of the linked account.
|
|
1054
1066
|
.get("/wizards/:id/cloud", async (c) => {
|
|
@@ -1095,13 +1107,13 @@ export const studio = new Hono()
|
|
|
1095
1107
|
.parse(await c.req.json());
|
|
1096
1108
|
await writableWizard(user.id, wizardId);
|
|
1097
1109
|
if (!(await canSpend())) {
|
|
1098
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
1110
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
1099
1111
|
}
|
|
1100
1112
|
return streamSSE(c, async (stream) => {
|
|
1101
1113
|
const abort = new AbortController();
|
|
1102
1114
|
stream.onAbort(() => abort.abort());
|
|
1103
1115
|
try {
|
|
1104
|
-
const result = await architectTurn(user.id, wizardId, message, {
|
|
1116
|
+
const result = await architectTurn(user.id, wizardId, message, requestLang(c), {
|
|
1105
1117
|
signal: abort.signal,
|
|
1106
1118
|
onText: (delta) => {
|
|
1107
1119
|
void stream.writeSSE({ event: "text", data: JSON.stringify(delta) });
|
|
@@ -1124,8 +1136,8 @@ export const studio = new Hono()
|
|
|
1124
1136
|
event: "error",
|
|
1125
1137
|
data: JSON.stringify({
|
|
1126
1138
|
message: err instanceof ServiceError
|
|
1127
|
-
? err
|
|
1128
|
-
: "Da ist etwas schiefgelaufen. Bitte noch einmal versuchen.",
|
|
1139
|
+
? errorText(err, requestLang(c))
|
|
1140
|
+
: inLang(c, "Da ist etwas schiefgelaufen. Bitte noch einmal versuchen.", "Something went wrong. Please try again."),
|
|
1129
1141
|
}),
|
|
1130
1142
|
});
|
|
1131
1143
|
}
|
|
@@ -1135,10 +1147,10 @@ export const studio = new Hono()
|
|
|
1135
1147
|
.post("/transcribe", async (c) => {
|
|
1136
1148
|
const file = (await c.req.formData()).get("file");
|
|
1137
1149
|
if (!(file instanceof File) || !file.size || file.size > 10_000_000) {
|
|
1138
|
-
throw new ServiceError("invalid", "Bitte eine Aufnahme bis 10 MB.");
|
|
1150
|
+
throw new ServiceError("invalid", bilingual("Bitte eine Aufnahme bis 10 MB.", "Please a recording of up to 10 MB."));
|
|
1139
1151
|
}
|
|
1140
1152
|
if (!(await canSpend())) {
|
|
1141
|
-
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
1153
|
+
throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
|
|
1142
1154
|
}
|
|
1143
1155
|
try {
|
|
1144
1156
|
const { text } = await transcribeAudio({
|
|
@@ -1149,7 +1161,7 @@ export const studio = new Hono()
|
|
|
1149
1161
|
}
|
|
1150
1162
|
catch (err) {
|
|
1151
1163
|
if (err instanceof ModelUnavailableError) {
|
|
1152
|
-
throw new ServiceError("refused", err
|
|
1164
|
+
throw new ServiceError("refused", saidOf(err));
|
|
1153
1165
|
}
|
|
1154
1166
|
throw err;
|
|
1155
1167
|
}
|
|
@@ -1226,7 +1238,7 @@ export const studio = new Hono()
|
|
|
1226
1238
|
})
|
|
1227
1239
|
// --- models: where each class runs, own API keys, the credits behind them -----------------
|
|
1228
1240
|
// Alone for this machine; on a Manage-App's runtime for the team, whose admins change it.
|
|
1229
|
-
.get("/models", async (c) => c.json(await modelSettings()))
|
|
1241
|
+
.get("/models", async (c) => c.json(await modelSettings(requestLang(c))))
|
|
1230
1242
|
.put("/models", async (c) => {
|
|
1231
1243
|
if (!mayChangeModels(c.get("user"))) {
|
|
1232
1244
|
return c.json({ error: "Only an admin of the team changes its models." }, 403);
|
|
@@ -1246,13 +1258,13 @@ export const studio = new Hono()
|
|
|
1246
1258
|
Object.values(input.bindings ?? {}).some((ref) => ref && !isCreditsRef(ref));
|
|
1247
1259
|
if (own && info && !feature("ownKeys", info.features)) {
|
|
1248
1260
|
return c.json({
|
|
1249
|
-
error: `Eigene API Keys gibt es nicht im Paket ${info.plan?.name ?? "des Teams"}.`,
|
|
1261
|
+
error: inLang(c, `Eigene API Keys gibt es nicht im Paket ${info.plan?.name ?? "des Teams"}.`, `Own API keys are not part of the ${info.plan?.name ?? "team's"} plan.`),
|
|
1250
1262
|
code: "plan",
|
|
1251
1263
|
}, 403);
|
|
1252
1264
|
}
|
|
1253
1265
|
}
|
|
1254
1266
|
await saveModels(input);
|
|
1255
|
-
return c.json(await modelSettings());
|
|
1267
|
+
return c.json(await modelSettings(requestLang(c)));
|
|
1256
1268
|
})
|
|
1257
1269
|
// Whether a provider takes a key — the one typed, or the one stored. Nothing is generated.
|
|
1258
1270
|
.post("/models/keys/:provider/check", async (c) => {
|
|
@@ -1264,7 +1276,7 @@ export const studio = new Hono()
|
|
|
1264
1276
|
if (!value) {
|
|
1265
1277
|
return c.json({ ok: false, message: "Kein API Key hinterlegt." });
|
|
1266
1278
|
}
|
|
1267
|
-
return c.json(await checkKey(provider, value));
|
|
1279
|
+
return c.json(await checkKey(provider, value, requestLang(c)));
|
|
1268
1280
|
})
|
|
1269
1281
|
// One call on a class, as a run makes it: text answers a word, an image, a voice or a short
|
|
1270
1282
|
// clip comes back to look at, a voice note comes back as text. Paid like any call.
|
|
@@ -1273,7 +1285,7 @@ export const studio = new Hono()
|
|
|
1273
1285
|
return c.json({ error: "Only an admin of the team tests its models." }, 403);
|
|
1274
1286
|
}
|
|
1275
1287
|
const { cls, lang } = z
|
|
1276
|
-
.object({ cls: z.enum(MODEL_CLASSES), lang: z.enum(["de", "en"]).default("
|
|
1288
|
+
.object({ cls: z.enum(MODEL_CLASSES), lang: z.enum(["de", "en"]).default("en") })
|
|
1277
1289
|
.parse(await c.req.json());
|
|
1278
1290
|
return c.json(await testClass(cls, lang));
|
|
1279
1291
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { and, eq } from "drizzle-orm";
|
|
3
4
|
import { db, schema } from "../db/client.js";
|
|
4
5
|
import { managed, tenantInfo } from "../manage.js";
|
|
@@ -30,7 +31,7 @@ export function currentRole() {
|
|
|
30
31
|
return actor.getStore() ?? "owner";
|
|
31
32
|
}
|
|
32
33
|
export const isAdminRole = (role) => role === "owner" || role === "admin";
|
|
33
|
-
export const MEMBERS_EDIT_ONLY = "Das dürfen nur Admins des Teams. Als Mitglied bearbeitest du die Wizards, die es gibt.";
|
|
34
|
+
export const MEMBERS_EDIT_ONLY = bilingual("Das dürfen nur Admins des Teams. Als Mitglied bearbeitest du die Wizards, die es gibt.", "Only the team's admins may do that. As a member you edit the wizards that are there.");
|
|
34
35
|
/** Refuses a member: making and deleting is for the owner and the admins of the team. */
|
|
35
36
|
export async function requireAdmin() {
|
|
36
37
|
if (syncing() || !managed || isAdminRole(currentRole())) {
|
|
@@ -51,8 +52,8 @@ export async function mayBuild() {
|
|
|
51
52
|
// An older Manage-App does not say: then it builds. One that cannot be reached: nothing changes.
|
|
52
53
|
return info ? info.limits.build !== false : false;
|
|
53
54
|
}
|
|
54
|
-
export const READ_ONLY_PROJECT = "Dieses Projekt kommt aus einer lokalen Installation und wird dort geändert.";
|
|
55
|
-
export const READ_ONLY_TENANT = "Hier wird nichts gebaut: Auf diesem Server laufen die Wizards, die deine lokale Installation veröffentlicht.";
|
|
55
|
+
export const READ_ONLY_PROJECT = bilingual("Dieses Projekt kommt aus einer lokalen Installation und wird dort geändert.", "This project comes from a local install and is changed there.");
|
|
56
|
+
export const READ_ONLY_TENANT = bilingual("Hier wird nichts gebaut: Auf diesem Server laufen die Wizards, die deine lokale Installation veröffentlicht.", "Nothing is built here: this server runs the wizards your local install publishes.");
|
|
56
57
|
/** Refuses when the tenant builds nothing here. */
|
|
57
58
|
export async function requireBuild() {
|
|
58
59
|
if (syncing() || (await mayBuild())) {
|
|
@@ -30,7 +30,9 @@ async function attachedImages(userId, wizardId, message) {
|
|
|
30
30
|
return images;
|
|
31
31
|
}
|
|
32
32
|
/** One studio chat turn: the architect reads the draft, its workspace and the thread, and changes them with tools. */
|
|
33
|
-
export async function architectTurn(userId, wizardId, message,
|
|
33
|
+
export async function architectTurn(userId, wizardId, message,
|
|
34
|
+
/** The language the admin reads: the lines about what it does, and its stock replies. */
|
|
35
|
+
lang, io) {
|
|
34
36
|
// Asked before the first model call: a wizard that is not changed here costs nothing to refuse.
|
|
35
37
|
const w = await writableWizard(userId, wizardId);
|
|
36
38
|
const history = await wizardMessages(w.id);
|
|
@@ -51,6 +53,7 @@ export async function architectTurn(userId, wizardId, message, io) {
|
|
|
51
53
|
onThought: io.onThought,
|
|
52
54
|
onBuilding: io.onBuilding,
|
|
53
55
|
images,
|
|
56
|
+
lang,
|
|
54
57
|
})),
|
|
55
58
|
unfinished: false,
|
|
56
59
|
}
|
|
@@ -67,13 +70,21 @@ export async function architectTurn(userId, wizardId, message, io) {
|
|
|
67
70
|
onThought: io.onThought,
|
|
68
71
|
onBuilding: io.onBuilding,
|
|
69
72
|
images,
|
|
73
|
+
lang,
|
|
70
74
|
});
|
|
75
|
+
const de = lang === "de";
|
|
71
76
|
const reply = result.unfinished
|
|
72
|
-
? `${result.reply ? `${result.reply}\n\n` : ""}
|
|
77
|
+
? `${result.reply ? `${result.reply}\n\n` : ""}${de
|
|
78
|
+
? "Ich bin noch nicht ganz fertig geworden. Schreib „weiter“, dann mache ich genau dort weiter."
|
|
79
|
+
: "I have not quite finished. Write “continue” and I will carry on right there."}`
|
|
73
80
|
: result.reply ||
|
|
74
81
|
(result.changed
|
|
75
|
-
?
|
|
76
|
-
|
|
82
|
+
? de
|
|
83
|
+
? "Erledigt."
|
|
84
|
+
: "Done."
|
|
85
|
+
: de
|
|
86
|
+
? "Das habe ich nicht verstanden – magst du es anders formulieren?"
|
|
87
|
+
: "I did not understand that – could you put it differently?");
|
|
77
88
|
await addMessage(w.id, { role: "assistant", content: reply, changed: result.changed });
|
|
78
89
|
const after = await ownedWizard(userId, wizardId);
|
|
79
90
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ASSISTANT_LAYOUTS } from "@engenty-wizards/shared/assistant";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { generateText } from "ai";
|
|
3
4
|
import { and, asc, desc, eq, isNotNull, isNull, sql } from "drizzle-orm";
|
|
4
5
|
import { nanoid } from "nanoid";
|
|
@@ -252,7 +253,7 @@ export async function readChat(user, chatId) {
|
|
|
252
253
|
where: and(mine(user), eq(schema.assistantChat.id, chatId)),
|
|
253
254
|
});
|
|
254
255
|
if (!chat) {
|
|
255
|
-
throw new ServiceError("not_found", "Diesen Chat gibt es nicht.");
|
|
256
|
+
throw new ServiceError("not_found", bilingual("Diesen Chat gibt es nicht.", "There is no such chat."));
|
|
256
257
|
}
|
|
257
258
|
return {
|
|
258
259
|
id: chat.id,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GRADIENT_TYPES, gradientCss, mainLogo, } from "@engenty-wizards/shared/projects";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { generateText, Output } from "ai";
|
|
3
4
|
import { and, asc, eq } from "drizzle-orm";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -65,14 +66,14 @@ export async function describeGradient(brand, text, current) {
|
|
|
65
66
|
answer = result.output;
|
|
66
67
|
}
|
|
67
68
|
catch {
|
|
68
|
-
throw new ServiceError("invalid", "Daraus ließ sich kein Verlauf machen. Beschreib ihn bitte anders.");
|
|
69
|
+
throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn bitte anders.", "No gradient could be made from that. Please describe it differently."));
|
|
69
70
|
}
|
|
70
71
|
const stops = answer.stops
|
|
71
72
|
.filter((s) => HEX.test(s.color))
|
|
72
73
|
.slice(0, 12)
|
|
73
74
|
.map((s) => ({ color: s.color.toLowerCase(), at: Math.min(100, Math.max(0, s.at)) }));
|
|
74
75
|
if (stops.length < 2) {
|
|
75
|
-
throw new ServiceError("invalid", "Daraus ließ sich kein Verlauf machen. Beschreib ihn bitte anders.");
|
|
76
|
+
throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn bitte anders.", "No gradient could be made from that. Please describe it differently."));
|
|
76
77
|
}
|
|
77
78
|
return {
|
|
78
79
|
name: answer.name.trim().slice(0, 80),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorText, NotedError } from "../model-errors.js";
|
|
1
2
|
const STATUS = {
|
|
2
3
|
not_found: 404,
|
|
3
4
|
invalid: 400,
|
|
@@ -8,19 +9,20 @@ const STATUS = {
|
|
|
8
9
|
read_only: 403,
|
|
9
10
|
forbidden: 403,
|
|
10
11
|
};
|
|
11
|
-
export class ServiceError extends
|
|
12
|
+
export class ServiceError extends NotedError {
|
|
12
13
|
code;
|
|
13
14
|
data;
|
|
14
|
-
constructor(code,
|
|
15
|
-
super(
|
|
15
|
+
constructor(code, said, data = {}) {
|
|
16
|
+
super(said);
|
|
16
17
|
this.code = code;
|
|
17
18
|
this.data = data;
|
|
18
19
|
}
|
|
19
20
|
get status() {
|
|
20
21
|
return STATUS[this.code];
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
/** The answer, its words in the asker's language; English where none is known. */
|
|
24
|
+
toJSON(lang = "en") {
|
|
25
|
+
return { error: errorText(this, lang), code: this.code, ...this.data };
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
export const notFound = () => new ServiceError("not_found", "not found");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dayOf, slugOf } from "@engenty-wizards/shared/knowledge";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { columnIdOf } from "@engenty-wizards/shared/space-data";
|
|
3
4
|
import { generateText, Output } from "ai";
|
|
4
5
|
import { and, eq, inArray, isNull } from "drizzle-orm";
|
|
@@ -222,7 +223,7 @@ function titleOf(markdown, name) {
|
|
|
222
223
|
function reviewOf(parsed, markdown) {
|
|
223
224
|
const illegible = (markdown.match(/\[unleserlich\]/gi) ?? []).length;
|
|
224
225
|
if (parsed.via === "vision" && (illegible >= 3 || markdown.replace(/\s/g, "").length < 200)) {
|
|
225
|
-
return "
|
|
226
|
+
return "Hard to read: please compare with the original.";
|
|
226
227
|
}
|
|
227
228
|
return null;
|
|
228
229
|
}
|
|
@@ -503,7 +504,7 @@ export async function writeSummary(valueId) {
|
|
|
503
504
|
where: eq(schema.spaceCategoryValue.id, valueId),
|
|
504
505
|
});
|
|
505
506
|
if (!value) {
|
|
506
|
-
throw new ServiceError("not_found", "Diesen Wert gibt es nicht.");
|
|
507
|
+
throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "There is no such value."));
|
|
507
508
|
}
|
|
508
509
|
const category = await categoryRow(value.categoryId);
|
|
509
510
|
const { items, total } = await itemsOfValue(value.id);
|
|
@@ -4,6 +4,7 @@ import { DEFINITION_VERSION, readable, wizardSchema, } from "@engenty-wizards/sh
|
|
|
4
4
|
import { capabilitiesOf, costTierOf, effortOf, formatsOf, MARKETPLACE_LANGS, pluginsOf, } from "@engenty-wizards/shared/marketplace";
|
|
5
5
|
import { wizardOutline } from "@engenty-wizards/shared/marketplace-entry";
|
|
6
6
|
import { searchEntries } from "@engenty-wizards/shared/marketplace-search";
|
|
7
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
7
8
|
import { and, eq, inArray, notInArray } from "drizzle-orm";
|
|
8
9
|
import { formulaEstimate } from "../credits/estimate.js";
|
|
9
10
|
import { control, controlDb, db, schema, withTenant } from "../db/client.js";
|
|
@@ -25,7 +26,7 @@ import { ServiceError } from "./errors.js";
|
|
|
25
26
|
* listed first and never fetched. They are known as `<plugin id>.<starter id>`; a marketplace
|
|
26
27
|
* entry's id has no dot, so the two never meet.
|
|
27
28
|
*/
|
|
28
|
-
export const asLang = (raw) => MARKETPLACE_LANGS.includes(raw ?? "") ? raw : "
|
|
29
|
+
export const asLang = (raw) => MARKETPLACE_LANGS.includes(raw ?? "") ? raw : "en";
|
|
29
30
|
/** A search waits this long for the marketplace; then it searches what is kept here. */
|
|
30
31
|
const TIMEOUT_MS = 5000;
|
|
31
32
|
/** One call to the marketplace's API; null without an answer, or with an error. */
|
|
@@ -114,7 +115,7 @@ function pluginSummary({ id, plugin, starter }) {
|
|
|
114
115
|
.slice(0, 16),
|
|
115
116
|
updatedAt: (changed?.mtime ?? new Date()).toISOString(),
|
|
116
117
|
starter: true,
|
|
117
|
-
language: starter.language ?? "
|
|
118
|
+
language: starter.language ?? "en",
|
|
118
119
|
title: starter.title,
|
|
119
120
|
pitch: starter.description,
|
|
120
121
|
// Found by the plugin's name as well: "Termine" finds the appointment plugin's starters.
|
|
@@ -390,7 +391,7 @@ export async function marketplaceWizard(id, lang) {
|
|
|
390
391
|
revision: null,
|
|
391
392
|
plugin: plugin.source.id,
|
|
392
393
|
title: starter.title,
|
|
393
|
-
language: starter.language ?? "
|
|
394
|
+
language: starter.language ?? "en",
|
|
394
395
|
definition: structuredClone(starter.definition),
|
|
395
396
|
files: Object.fromEntries(Object.entries(starter.files ?? {}).map(([path, data]) => [
|
|
396
397
|
path,
|
|
@@ -403,13 +404,13 @@ export async function marketplaceWizard(id, lang) {
|
|
|
403
404
|
return null;
|
|
404
405
|
}
|
|
405
406
|
if (!readable(found.definition)) {
|
|
406
|
-
throw new ServiceError("refused", "Diese Vorlage braucht eine neuere Version der App. Bitte aktualisiere die App.");
|
|
407
|
+
throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version der App. Bitte aktualisiere die App.", "This template needs a newer version of the app. Please update the app."));
|
|
407
408
|
}
|
|
408
409
|
// A wizard whose plugins are not here would stop at its first step that uses them.
|
|
409
410
|
const have = await pluginIdsOf(currentTenant());
|
|
410
411
|
const needs = pluginsOf(wizardSchema.parse(found.definition)).filter((p) => !have.has(p));
|
|
411
412
|
if (needs.length) {
|
|
412
|
-
throw new ServiceError("refused", `Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: mit Pro auf engenty.ai dabei.`);
|
|
413
|
+
throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: mit Pro auf engenty.ai dabei.`, `This template needs the ${needs.length === 1 ? "plugin" : "plugins"} ${needs.join(", ")}: included with Pro on engenty.ai.`));
|
|
413
414
|
}
|
|
414
415
|
return {
|
|
415
416
|
id: found.id,
|
|
@@ -136,16 +136,23 @@ async function missing(userId, projectId, draft, manifest) {
|
|
|
136
136
|
];
|
|
137
137
|
}
|
|
138
138
|
/** Makes a new wizard of a package in the project: the draft and its workspace, not published. */
|
|
139
|
-
export async function importWizard(userId, projectId, data, name
|
|
139
|
+
export async function importWizard(userId, projectId, data, name,
|
|
140
|
+
/** The language the admin reads the import's note in. */
|
|
141
|
+
lang = "en") {
|
|
140
142
|
await requireAdmin();
|
|
141
143
|
const { manifest, files } = readPackage(data);
|
|
142
144
|
// The shape is checked before anything is written; a wrong one leaves no wizard behind.
|
|
143
145
|
const { draft } = parseDraft(manifest.definition, files.map((f) => f.path));
|
|
144
146
|
const lacks = await missing(userId, projectId, draft, manifest);
|
|
147
|
+
const de = lang === "de";
|
|
145
148
|
const note = [
|
|
146
|
-
|
|
149
|
+
de
|
|
150
|
+
? `Importiert${name ? ` aus „${name}“` : ""}: ${draft.steps.length} Schritte, ${files.length} ${files.length === 1 ? "Datei" : "Dateien"}.`
|
|
151
|
+
: `Imported${name ? ` from “${name}”` : ""}: ${draft.steps.length} steps, ${files.length} ${files.length === 1 ? "file" : "files"}.`,
|
|
147
152
|
lacks.length
|
|
148
|
-
?
|
|
153
|
+
? de
|
|
154
|
+
? `Diesem Projekt fehlt noch: ${lacks.join(", ")} – in den Einstellungen unter „Connectors“ hinzufügen.`
|
|
155
|
+
: `This project still lacks: ${lacks.join(", ")} – add it under “Connectors” in the settings.`
|
|
149
156
|
: "",
|
|
150
157
|
]
|
|
151
158
|
.filter(Boolean)
|
|
@@ -27,6 +27,18 @@ export async function userProfile(user) {
|
|
|
27
27
|
phone: stored.phone ?? "",
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
const langKey = (user) => `lang:${user.tenantId}:${user.id}`;
|
|
31
|
+
/**
|
|
32
|
+
* The language the person picked in the studio, kept with them rather than one browser: a new
|
|
33
|
+
* browser or device opens in it. Null until they pick one; the browser's language stands then.
|
|
34
|
+
*/
|
|
35
|
+
export async function userLang(user) {
|
|
36
|
+
const lang = await readSetting(langKey(user));
|
|
37
|
+
return lang === "de" || lang === "en" ? lang : null;
|
|
38
|
+
}
|
|
39
|
+
export async function saveUserLang(user, lang) {
|
|
40
|
+
await writeSetting(langKey(user), lang);
|
|
41
|
+
}
|
|
30
42
|
export async function saveProfile(user, profile) {
|
|
31
43
|
// Name and e-mail an account gives stay the person's own underneath: they come back unlinked.
|
|
32
44
|
const stored = (await readSetting(key(user))) ?? {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PROJECT_LIMITS, projectFileLimit, } from "@engenty-wizards/shared/projects";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { generateText } from "ai";
|
|
3
4
|
import { and, asc, count, eq, max } from "drizzle-orm";
|
|
4
5
|
import { db, schema } from "../db/client.js";
|
|
@@ -27,7 +28,7 @@ export function fileView(row) {
|
|
|
27
28
|
description: row.description,
|
|
28
29
|
source: row.source,
|
|
29
30
|
status: stale ? "failed" : row.status,
|
|
30
|
-
error: stale ? "
|
|
31
|
+
error: stale ? "Interrupted. Please read it in again." : row.error,
|
|
31
32
|
indexed: row.indexed,
|
|
32
33
|
pages: row.pages,
|
|
33
34
|
chars: row.chars,
|
|
@@ -78,14 +79,14 @@ export async function addProjectFile(projectId, input) {
|
|
|
78
79
|
const mime = documentMime(input.name, input.mime) || "application/octet-stream";
|
|
79
80
|
if (!accepts(input.kind, mime)) {
|
|
80
81
|
throw new ServiceError("invalid", input.kind === "logo"
|
|
81
|
-
? "Ein Logo ist ein Bild (PNG, SVG, JPEG, WebP)."
|
|
82
|
+
? bilingual("Ein Logo ist ein Bild (PNG, SVG, JPEG, WebP).", "A logo is an image (PNG, SVG, JPEG, WebP).")
|
|
82
83
|
: input.kind === "pattern"
|
|
83
|
-
? "Ein Muster ist ein Bild (SVG, PNG, JPEG, WebP)."
|
|
84
|
-
: "Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen bitte zu den Dokumenten.");
|
|
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 bitte zu den Dokumenten.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models belong here. Texts to look things up in go to the documents."));
|
|
85
86
|
}
|
|
86
87
|
const limit = projectFileLimit(input.kind, mime);
|
|
87
88
|
if (!input.data.byteLength || input.data.byteLength > limit) {
|
|
88
|
-
throw new ServiceError("invalid", `Bitte eine Datei bis ${Math.round(limit / 1_000_000)} MB wählen.`);
|
|
89
|
+
throw new ServiceError("invalid", bilingual(`Bitte eine Datei bis ${Math.round(limit / 1_000_000)} MB wählen.`, `Please choose a file of up to ${Math.round(limit / 1_000_000)} MB.`));
|
|
89
90
|
}
|
|
90
91
|
const scope = and(eq(schema.projectFile.projectId, projectId), eq(schema.projectFile.kind, input.kind));
|
|
91
92
|
const [{ n, last }] = await db
|
|
@@ -93,7 +94,7 @@ export async function addProjectFile(projectId, input) {
|
|
|
93
94
|
.from(schema.projectFile)
|
|
94
95
|
.where(scope);
|
|
95
96
|
if (n >= PROJECT_LIMITS.files[input.kind]) {
|
|
96
|
-
throw new ServiceError("refused", `Höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien.`);
|
|
97
|
+
throw new ServiceError("refused", bilingual(`Höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien.`, `At most ${PROJECT_LIMITS.files[input.kind]} files.`));
|
|
97
98
|
}
|
|
98
99
|
const name = input.name.slice(0, 120) || "datei";
|
|
99
100
|
const ref = await saveAsset({
|
|
@@ -149,21 +150,21 @@ export async function addProjectFileFromUrl(projectId, input) {
|
|
|
149
150
|
headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
|
|
150
151
|
signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
|
|
151
152
|
}).catch(() => {
|
|
152
|
-
throw new ServiceError("invalid", "Die Adresse ist nicht erreichbar.");
|
|
153
|
+
throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address cannot be reached."));
|
|
153
154
|
});
|
|
154
155
|
if (!res.ok) {
|
|
155
|
-
throw new ServiceError("invalid", `Die Adresse antwortete ${res.status}.`);
|
|
156
|
+
throw new ServiceError("invalid", bilingual(`Die Adresse antwortete ${res.status}.`, `The address answered ${res.status}.`));
|
|
156
157
|
}
|
|
157
158
|
const mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
158
159
|
if (mime === "text/html" && kind !== "document") {
|
|
159
160
|
await res.body?.cancel();
|
|
160
|
-
throw new ServiceError("invalid", "Unter der Adresse steht eine Webseite, kein Bild.", {
|
|
161
|
+
throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address holds a web page, not an image."), {
|
|
161
162
|
reason: "page",
|
|
162
163
|
url: res.url,
|
|
163
164
|
});
|
|
164
165
|
}
|
|
165
166
|
if (Number(res.headers.get("content-length") ?? 0) > projectFileLimit(kind, mime)) {
|
|
166
|
-
throw new ServiceError("invalid", "Die Datei ist zu groß.");
|
|
167
|
+
throw new ServiceError("invalid", bilingual("Die Datei ist zu groß.", "The file is too large."));
|
|
167
168
|
}
|
|
168
169
|
const data = new Uint8Array(await res.arrayBuffer());
|
|
169
170
|
const name = decodeURIComponent(new URL(res.url).pathname.split("/").pop() || kind);
|
|
@@ -297,7 +298,7 @@ async function prepare(row) {
|
|
|
297
298
|
status: "failed",
|
|
298
299
|
error: err instanceof UnreadableDocument
|
|
299
300
|
? err.message
|
|
300
|
-
: "
|
|
301
|
+
: "Reading did not work. Please try again.",
|
|
301
302
|
});
|
|
302
303
|
}
|
|
303
304
|
});
|
|
@@ -306,7 +307,7 @@ export async function reindexProjectFile(projectId, fileId) {
|
|
|
306
307
|
await requireWritableProject(projectId);
|
|
307
308
|
const row = await projectFile(projectId, fileId);
|
|
308
309
|
if (row.kind !== "document") {
|
|
309
|
-
throw new ServiceError("invalid", "Nur Dokumente stehen im Index.");
|
|
310
|
+
throw new ServiceError("invalid", bilingual("Nur Dokumente stehen im Index.", "Only documents are in the index."));
|
|
310
311
|
}
|
|
311
312
|
const [pending] = await db
|
|
312
313
|
.update(schema.projectFile)
|