wizards 0.5.6 → 0.5.8
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 +13 -26
- package/apps/runtime/dist/agents/studio-assistant.js +19 -22
- package/apps/runtime/dist/agents/subscription.js +10 -9
- package/apps/runtime/dist/app.js +9 -7
- package/apps/runtime/dist/assistant/knows.js +10 -9
- package/apps/runtime/dist/auth/index.js +7 -0
- package/apps/runtime/dist/connectors/external.js +3 -3
- package/apps/runtime/dist/connectors/index.js +23 -23
- package/apps/runtime/dist/connectors/via-account.js +6 -6
- package/apps/runtime/dist/documents/parse.js +3 -3
- package/apps/runtime/dist/engine/input.js +11 -11
- package/apps/runtime/dist/engine/requirements.js +7 -8
- package/apps/runtime/dist/engine/runner.js +10 -14
- package/apps/runtime/dist/engine/steps.js +6 -6
- package/apps/runtime/dist/engine/surface.js +2 -2
- package/apps/runtime/dist/film/step.js +5 -5
- package/apps/runtime/dist/harness/apple.js +2 -1
- package/apps/runtime/dist/harness/claude.js +2 -2
- package/apps/runtime/dist/harness/codex.js +2 -2
- package/apps/runtime/dist/harness/model.js +4 -5
- package/apps/runtime/dist/harness/terminal.js +4 -4
- package/apps/runtime/dist/media/conversation.js +18 -35
- package/apps/runtime/dist/media/transcribe.js +2 -1
- package/apps/runtime/dist/model-errors.js +1 -1
- package/apps/runtime/dist/models.js +32 -40
- package/apps/runtime/dist/plugins/runs.js +3 -5
- package/apps/runtime/dist/routes/api.js +9 -9
- package/apps/runtime/dist/routes/lang.js +14 -9
- package/apps/runtime/dist/routes/marketplace.js +6 -1
- package/apps/runtime/dist/routes/runs.js +5 -5
- package/apps/runtime/dist/routes/studio.js +38 -37
- package/apps/runtime/dist/services/access.js +4 -4
- package/apps/runtime/dist/services/architect.js +3 -12
- package/apps/runtime/dist/services/assistant-chats.js +2 -2
- package/apps/runtime/dist/services/brand.js +3 -3
- package/apps/runtime/dist/services/cloud.js +5 -5
- package/apps/runtime/dist/services/errors.js +2 -2
- package/apps/runtime/dist/services/knowledge-model.js +3 -3
- package/apps/runtime/dist/services/marketplace.js +18 -8
- package/apps/runtime/dist/services/package-file.js +9 -11
- package/apps/runtime/dist/services/package.js +8 -9
- package/apps/runtime/dist/services/profile.js +2 -1
- package/apps/runtime/dist/services/project-files.js +13 -13
- package/apps/runtime/dist/services/projects.js +7 -11
- package/apps/runtime/dist/services/runners.js +4 -4
- package/apps/runtime/dist/services/runs.js +7 -10
- package/apps/runtime/dist/services/shares.js +2 -2
- package/apps/runtime/dist/services/site.js +8 -11
- package/apps/runtime/dist/services/space-categories.js +7 -7
- package/apps/runtime/dist/services/space-data.js +20 -20
- package/apps/runtime/dist/services/spaces.js +7 -9
- package/apps/runtime/dist/services/wizards.js +11 -13
- package/apps/runtime/dist/store/index.js +2 -2
- package/apps/runtime/dist/texts.js +195 -0
- package/apps/runtime/dist/tools/plugin.js +7 -3
- package/apps/runtime/dist/widgets/step.js +2 -2
- package/apps/web/dist/assets/AboutDialog-BcTtA42P.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-sdSC9eZj.js → ChangelogDialog-DMOWV_Iu.js} +1 -1
- package/apps/web/dist/assets/ChatView-CrnrYHjp.js +10 -0
- package/apps/web/dist/assets/EditorPage-DpEw3eOu.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-B4-ipMdE.js → FlowPreview-CzmH4JUQ.js} +1 -1
- package/apps/web/dist/assets/{Harness-DwI4wFqj.js → Harness-DipH0Vec.js} +2 -2
- package/apps/web/dist/assets/{NewWizardChannels-DkI648G-.js → NewWizardChannels-DShu0PP6.js} +1 -1
- package/apps/web/dist/assets/NewWizardPage-HSa6PNJs.js +7 -0
- package/apps/web/dist/assets/OnboardingPage-CPADMs4c.js +1 -0
- package/apps/web/dist/assets/ProjectPage-DMPcrhgt.js +1 -0
- package/apps/web/dist/assets/PublicRunner-DZ5sWmtg.js +1 -0
- package/apps/web/dist/assets/{ReadOnly-Jvzgyelg.js → ReadOnly-B9xaq_M3.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-9nMGhbSm.js +4 -0
- package/apps/web/dist/assets/{RunnerView-DN4sMYS6.js → RunnerView-NSSP3tc6.js} +1 -1
- package/apps/web/dist/assets/SettingsPage-Dy9MbwQh.js +1 -0
- package/apps/web/dist/assets/SetupPage-kemgI7v5.js +1 -0
- package/apps/web/dist/assets/{SharePage-CmowpiJG.js → SharePage-CQfIdcwy.js} +1 -1
- package/apps/web/dist/assets/{changelog-Cz8fZ-jG.js → changelog-BW40vzI6.js} +1 -1
- package/apps/web/dist/assets/{esm-J06O1nN_.js → esm-BF1nonbH.js} +1 -1
- package/apps/web/dist/assets/index-CnJ4Yzkf.css +1 -0
- package/apps/web/dist/assets/index-DQ9qAB4v.js +14 -0
- package/apps/web/dist/assets/{lock-ChDlFFHO.js → lock-DVvM6IsA.js} +1 -1
- package/apps/web/dist/assets/{phone-Cr7jz_kf.js → phone-3fG1okFs.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-BAwHMXCR.js → settings-menu-D9fbi1Z0.js} +1 -1
- package/apps/web/dist/assets/ui-EuR3jezP.js +134 -0
- package/apps/web/dist/assets/{upload-DcAIMe8d.js → upload-BsMUhEfX.js} +1 -1
- package/apps/web/dist/index.html +3 -3
- package/apps/web/dist/mcp/flow.html +78 -78
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
- package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
- package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
- package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
- package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
- package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
- package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
- package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/AboutDialog-Ck6_TSVJ.js +0 -1
- package/apps/web/dist/assets/ChatView-S1H9Pcih.js +0 -10
- package/apps/web/dist/assets/EditorPage-BSfJXx4W.js +0 -3
- package/apps/web/dist/assets/NewWizardPage-dumNiSqM.js +0 -7
- package/apps/web/dist/assets/OnboardingPage-BAfHLLNT.js +0 -1
- package/apps/web/dist/assets/ProjectPage-fneLQr6R.js +0 -1
- package/apps/web/dist/assets/PublicRunner-CuxDAXK7.js +0 -1
- package/apps/web/dist/assets/RunDrawer-C_T1I77g.js +0 -4
- package/apps/web/dist/assets/SettingsPage-DjFvKMRz.js +0 -1
- package/apps/web/dist/assets/SetupPage-CzYwVUvh.js +0 -1
- package/apps/web/dist/assets/index-CZFfDGHy.css +0 -1
- package/apps/web/dist/assets/index-DZTDv2pA.js +0 -14
- package/apps/web/dist/assets/ui-C5_NcAmG.js +0 -134
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ASSISTANT_LAYOUTS, ASSISTANT_PAGE_BYTES, assistantSignalSchema, assistantTurnSchema, } from "@engenty-wizards/shared/assistant";
|
|
4
|
-
import { MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
|
|
4
|
+
import { LANGS, 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 {
|
|
8
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
9
9
|
import { PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKAGE_MIME, } from "@engenty-wizards/shared/wizard-package";
|
|
10
10
|
import { generateText } from "ai";
|
|
11
11
|
import { Hono } from "hono";
|
|
@@ -63,9 +63,10 @@ import { createWizard, deleteWizard, draftChanges, duplicateWizard, listWizards,
|
|
|
63
63
|
import { readSetting, writeSetting } from "../settings.js";
|
|
64
64
|
import { codeOf } from "../tenants/control.js";
|
|
65
65
|
import { currentTenant } from "../tenants/tenant.js";
|
|
66
|
+
import { serverText } from "../texts.js";
|
|
66
67
|
import { applyUpdate, updateStatus } from "../update.js";
|
|
67
68
|
import { byteRange } from "./delivery.js";
|
|
68
|
-
import {
|
|
69
|
+
import { requestLang, say } from "./lang.js";
|
|
69
70
|
/** The model sources a test call has worked on: the setup is done for those, not for the others. */
|
|
70
71
|
async function testedSources() {
|
|
71
72
|
const tested = await readSetting("setup-done");
|
|
@@ -282,7 +283,7 @@ export const studio = new Hono()
|
|
|
282
283
|
.get("/team", async (c) => {
|
|
283
284
|
if (!managed) {
|
|
284
285
|
return c.json({
|
|
285
|
-
error:
|
|
286
|
+
error: say(c, "onlyAvailableCloud"),
|
|
286
287
|
code: "not_managed",
|
|
287
288
|
}, 404);
|
|
288
289
|
}
|
|
@@ -304,14 +305,14 @@ export const studio = new Hono()
|
|
|
304
305
|
// `?fresh=1` asks GitHub now instead of within the hour.
|
|
305
306
|
.get("/update", async (c) => managed
|
|
306
307
|
? c.json({
|
|
307
|
-
error:
|
|
308
|
+
error: say(c, "onlyAvailableComputer"),
|
|
308
309
|
code: "not_local",
|
|
309
310
|
}, 404)
|
|
310
311
|
: c.json(await updateStatus(c.req.query("fresh") === "1")))
|
|
311
312
|
.post("/update", async (c) => {
|
|
312
313
|
if (managed) {
|
|
313
314
|
return c.json({
|
|
314
|
-
error:
|
|
315
|
+
error: say(c, "onlyAvailableComputer"),
|
|
315
316
|
code: "not_local",
|
|
316
317
|
}, 404);
|
|
317
318
|
}
|
|
@@ -320,7 +321,7 @@ export const studio = new Hono()
|
|
|
320
321
|
})
|
|
321
322
|
.put("/profile", async (c) => c.json(await saveProfile(c.get("user"), profileSchema.parse(await c.req.json()))))
|
|
322
323
|
.put("/lang", async (c) => {
|
|
323
|
-
const { lang } = z.object({ lang: z.enum(
|
|
324
|
+
const { lang } = z.object({ lang: z.enum(LANGS) }).parse(await c.req.json());
|
|
324
325
|
await saveUserLang(c.get("user"), lang);
|
|
325
326
|
return c.json({ lang });
|
|
326
327
|
})
|
|
@@ -342,10 +343,10 @@ export const studio = new Hono()
|
|
|
342
343
|
.post("/assistant/speak", async (c) => {
|
|
343
344
|
const { text } = z.object({ text: z.string().min(1).max(4000) }).parse(await c.req.json());
|
|
344
345
|
if ((await classWays()).speech.kind === "none") {
|
|
345
|
-
throw new ServiceError("refused",
|
|
346
|
+
throw new ServiceError("refused", note("noLanguageModelSet"));
|
|
346
347
|
}
|
|
347
348
|
if (!(await canSpend())) {
|
|
348
|
-
throw new ServiceError("no_credits",
|
|
349
|
+
throw new ServiceError("no_credits", note("creditsUsedUp"));
|
|
349
350
|
}
|
|
350
351
|
const voice = await generateSpeechMedia({ text, abortSignal: c.req.raw.signal });
|
|
351
352
|
return c.body(new Uint8Array(voice.bytes), 200, { "content-type": voice.mime });
|
|
@@ -365,10 +366,10 @@ export const studio = new Hono()
|
|
|
365
366
|
.post("/assistant/turn", async (c) => {
|
|
366
367
|
const user = c.get("user");
|
|
367
368
|
const body = assistantTurnSchema
|
|
368
|
-
.extend({ lang: z.enum(
|
|
369
|
+
.extend({ lang: z.enum(LANGS).default("en") })
|
|
369
370
|
.parse(await c.req.json());
|
|
370
371
|
if (!(await canSpend())) {
|
|
371
|
-
throw new ServiceError("no_credits",
|
|
372
|
+
throw new ServiceError("no_credits", note("creditsUsedUp"));
|
|
372
373
|
}
|
|
373
374
|
const project = body.projectId ? await ownedProject(user.id, body.projectId) : null;
|
|
374
375
|
const writable = project ? await projectWritable(project) : false;
|
|
@@ -454,7 +455,7 @@ export const studio = new Hono()
|
|
|
454
455
|
const left = takeSignals(runId);
|
|
455
456
|
if (!left.length || abort.signal.aborted) {
|
|
456
457
|
changed ||= result.changed;
|
|
457
|
-
reply = result.reply || (changed ? (body.lang
|
|
458
|
+
reply = result.reply || (changed ? serverText(body.lang, "done") : "");
|
|
458
459
|
const stored = await addMessage(chat.id, {
|
|
459
460
|
role: "assistant",
|
|
460
461
|
content: reply,
|
|
@@ -480,9 +481,7 @@ export const studio = new Hono()
|
|
|
480
481
|
await send("error", {
|
|
481
482
|
message: err instanceof ServiceError || err instanceof ModelUnavailableError
|
|
482
483
|
? errorText(err, body.lang)
|
|
483
|
-
: body.lang
|
|
484
|
-
? "Da ist etwas schiefgelaufen. Versuch es noch einmal."
|
|
485
|
-
: "Something went wrong. Try again.",
|
|
484
|
+
: serverText(body.lang, "somethingWrong"),
|
|
486
485
|
});
|
|
487
486
|
}
|
|
488
487
|
endRun(runId);
|
|
@@ -530,7 +529,7 @@ export const studio = new Hono()
|
|
|
530
529
|
const kind = z.enum(PROJECT_FILE_KINDS).parse(c.req.query("kind"));
|
|
531
530
|
const file = (await c.req.formData()).get("file");
|
|
532
531
|
if (!(file instanceof File)) {
|
|
533
|
-
throw new ServiceError("invalid",
|
|
532
|
+
throw new ServiceError("invalid", note("chooseFile"));
|
|
534
533
|
}
|
|
535
534
|
const row = await addProjectFile(project.id, {
|
|
536
535
|
kind,
|
|
@@ -563,7 +562,7 @@ export const studio = new Hono()
|
|
|
563
562
|
await ownedProject(c.get("user").id, c.req.param("id"));
|
|
564
563
|
const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
|
|
565
564
|
const colors = await scanColors(url).catch(() => {
|
|
566
|
-
throw new ServiceError("invalid",
|
|
565
|
+
throw new ServiceError("invalid", note("pageNotReached"));
|
|
567
566
|
});
|
|
568
567
|
return c.json({ colors });
|
|
569
568
|
})
|
|
@@ -579,7 +578,7 @@ export const studio = new Hono()
|
|
|
579
578
|
})
|
|
580
579
|
.parse(await c.req.json());
|
|
581
580
|
if (!(await canSpend())) {
|
|
582
|
-
throw new ServiceError("no_credits",
|
|
581
|
+
throw new ServiceError("no_credits", note("creditsUsedUp"));
|
|
583
582
|
}
|
|
584
583
|
try {
|
|
585
584
|
return c.json({
|
|
@@ -597,7 +596,7 @@ export const studio = new Hono()
|
|
|
597
596
|
await ownedProject(c.get("user").id, c.req.param("id"));
|
|
598
597
|
const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
|
|
599
598
|
const fonts = await scanFonts(url).catch(() => {
|
|
600
|
-
throw new ServiceError("invalid",
|
|
599
|
+
throw new ServiceError("invalid", note("pageNotReached"));
|
|
601
600
|
});
|
|
602
601
|
return c.json({ fonts });
|
|
603
602
|
})
|
|
@@ -839,7 +838,7 @@ export const studio = new Hono()
|
|
|
839
838
|
? { fileId: id }
|
|
840
839
|
: null;
|
|
841
840
|
if (!ref || key[1] !== ":") {
|
|
842
|
-
throw new ServiceError("invalid",
|
|
841
|
+
throw new ServiceError("invalid", note("unknownEntry"));
|
|
843
842
|
}
|
|
844
843
|
const { values } = z
|
|
845
844
|
.object({ values: z.array(z.union([z.string().max(300), z.number(), z.boolean()])).max(50) })
|
|
@@ -953,7 +952,7 @@ export const studio = new Hono()
|
|
|
953
952
|
.post("/projects/:id/wizards/import", async (c) => {
|
|
954
953
|
const file = (await c.req.formData()).get("file");
|
|
955
954
|
if (!(file instanceof File) || !file.size || file.size > PACKAGE_MAX_BYTES) {
|
|
956
|
-
throw new ServiceError("invalid",
|
|
955
|
+
throw new ServiceError("invalid", note("chooseWizardFileMb", { PACKAGE_EXTENSION }));
|
|
957
956
|
}
|
|
958
957
|
return c.json(await importWizard(c.get("user").id, c.req.param("id"), new Uint8Array(await file.arrayBuffer()), file.name, requestLang(c)));
|
|
959
958
|
})
|
|
@@ -964,7 +963,7 @@ export const studio = new Hono()
|
|
|
964
963
|
});
|
|
965
964
|
if (!found) {
|
|
966
965
|
return c.json({
|
|
967
|
-
error:
|
|
966
|
+
error: say(c, "wizardNotPublishedYet"),
|
|
968
967
|
}, 404);
|
|
969
968
|
}
|
|
970
969
|
const { name, zip } = found;
|
|
@@ -1116,7 +1115,7 @@ export const studio = new Hono()
|
|
|
1116
1115
|
.parse(await c.req.json());
|
|
1117
1116
|
await writableWizard(user.id, wizardId);
|
|
1118
1117
|
if (!(await canSpend())) {
|
|
1119
|
-
throw new ServiceError("no_credits",
|
|
1118
|
+
throw new ServiceError("no_credits", note("creditsUsedUp"));
|
|
1120
1119
|
}
|
|
1121
1120
|
return streamSSE(c, async (stream) => {
|
|
1122
1121
|
const abort = new AbortController();
|
|
@@ -1146,7 +1145,7 @@ export const studio = new Hono()
|
|
|
1146
1145
|
data: JSON.stringify({
|
|
1147
1146
|
message: err instanceof ServiceError
|
|
1148
1147
|
? errorText(err, requestLang(c))
|
|
1149
|
-
:
|
|
1148
|
+
: say(c, "somethingWentWrongTry"),
|
|
1150
1149
|
}),
|
|
1151
1150
|
});
|
|
1152
1151
|
}
|
|
@@ -1156,10 +1155,10 @@ export const studio = new Hono()
|
|
|
1156
1155
|
.post("/transcribe", async (c) => {
|
|
1157
1156
|
const file = (await c.req.formData()).get("file");
|
|
1158
1157
|
if (!(file instanceof File) || !file.size || file.size > 10_000_000) {
|
|
1159
|
-
throw new ServiceError("invalid",
|
|
1158
|
+
throw new ServiceError("invalid", note("recordingMbMost"));
|
|
1160
1159
|
}
|
|
1161
1160
|
if (!(await canSpend())) {
|
|
1162
|
-
throw new ServiceError("no_credits",
|
|
1161
|
+
throw new ServiceError("no_credits", note("creditsUsedUp"));
|
|
1163
1162
|
}
|
|
1164
1163
|
try {
|
|
1165
1164
|
const { text } = await transcribeAudio({
|
|
@@ -1230,7 +1229,7 @@ export const studio = new Hono()
|
|
|
1230
1229
|
const { connect } = z.object({ connect: z.boolean() }).parse(await c.req.json());
|
|
1231
1230
|
const [done] = await connectApps(layout(), [c.req.param("id")], !connect);
|
|
1232
1231
|
if (!done) {
|
|
1233
|
-
return c.json({ error:
|
|
1232
|
+
return c.json({ error: say(c, "unknownApp"), code: "not_found" }, 404);
|
|
1234
1233
|
}
|
|
1235
1234
|
// Not done is an answer too: the app's file is no plain JSON, here is what to paste.
|
|
1236
1235
|
return c.json(done.outcome);
|
|
@@ -1251,7 +1250,7 @@ export const studio = new Hono()
|
|
|
1251
1250
|
.put("/models", async (c) => {
|
|
1252
1251
|
if (!mayChangeModels(c.get("user"))) {
|
|
1253
1252
|
return c.json({
|
|
1254
|
-
error:
|
|
1253
|
+
error: say(c, "onlyTeamAdminsChange"),
|
|
1255
1254
|
code: "forbidden",
|
|
1256
1255
|
}, 403);
|
|
1257
1256
|
}
|
|
@@ -1262,7 +1261,7 @@ export const studio = new Hono()
|
|
|
1262
1261
|
input.ollamaUrl ||
|
|
1263
1262
|
Object.values(input.bindings ?? {}).some((ref) => ref && !cloudBinding(ref)))) {
|
|
1264
1263
|
return c.json({
|
|
1265
|
-
error:
|
|
1264
|
+
error: say(c, "notAvailableCloud"),
|
|
1266
1265
|
code: "not_local",
|
|
1267
1266
|
}, 400);
|
|
1268
1267
|
}
|
|
@@ -1273,7 +1272,9 @@ export const studio = new Hono()
|
|
|
1273
1272
|
Object.values(input.bindings ?? {}).some((ref) => ref && !isCreditsRef(ref));
|
|
1274
1273
|
if (own && info && !feature("ownKeys", info.features)) {
|
|
1275
1274
|
return c.json({
|
|
1276
|
-
error:
|
|
1275
|
+
error: info.plan?.name
|
|
1276
|
+
? say(c, "ownKeysNotInPlan", { plan: info.plan.name })
|
|
1277
|
+
: say(c, "ownKeysNotInTeamPlan"),
|
|
1277
1278
|
code: "plan",
|
|
1278
1279
|
}, 403);
|
|
1279
1280
|
}
|
|
@@ -1291,7 +1292,7 @@ export const studio = new Hono()
|
|
|
1291
1292
|
if (!value) {
|
|
1292
1293
|
return c.json({
|
|
1293
1294
|
ok: false,
|
|
1294
|
-
message:
|
|
1295
|
+
message: say(c, "noApiKeySet"),
|
|
1295
1296
|
});
|
|
1296
1297
|
}
|
|
1297
1298
|
return c.json(await checkKey(provider, value, requestLang(c)));
|
|
@@ -1301,12 +1302,12 @@ export const studio = new Hono()
|
|
|
1301
1302
|
.post("/models/test", async (c) => {
|
|
1302
1303
|
if (!mayChangeModels(c.get("user"))) {
|
|
1303
1304
|
return c.json({
|
|
1304
|
-
error:
|
|
1305
|
+
error: say(c, "onlyTeamAdminsTest"),
|
|
1305
1306
|
code: "forbidden",
|
|
1306
1307
|
}, 403);
|
|
1307
1308
|
}
|
|
1308
1309
|
const { cls, lang } = z
|
|
1309
|
-
.object({ cls: z.enum(MODEL_CLASSES), lang: z.enum(
|
|
1310
|
+
.object({ cls: z.enum(MODEL_CLASSES), lang: z.enum(LANGS).default("en") })
|
|
1310
1311
|
.parse(await c.req.json());
|
|
1311
1312
|
return c.json(await testClass(cls, lang));
|
|
1312
1313
|
})
|
|
@@ -1461,7 +1462,7 @@ export const studio = new Hono()
|
|
|
1461
1462
|
.put("/team", async (c) => {
|
|
1462
1463
|
if (!managed) {
|
|
1463
1464
|
return c.json({
|
|
1464
|
-
error:
|
|
1465
|
+
error: say(c, "onlyAvailableCloud"),
|
|
1465
1466
|
code: "not_managed",
|
|
1466
1467
|
}, 404);
|
|
1467
1468
|
}
|
|
@@ -1473,7 +1474,7 @@ export const studio = new Hono()
|
|
|
1473
1474
|
.post("/onboarding/done", async (c) => {
|
|
1474
1475
|
if (!managed) {
|
|
1475
1476
|
return c.json({
|
|
1476
|
-
error:
|
|
1477
|
+
error: say(c, "onlyAvailableCloud"),
|
|
1477
1478
|
code: "not_managed",
|
|
1478
1479
|
}, 404);
|
|
1479
1480
|
}
|
|
@@ -1490,12 +1491,12 @@ export const studio = new Hono()
|
|
|
1490
1491
|
.post("/install-mail", async (c) => {
|
|
1491
1492
|
if (!managed) {
|
|
1492
1493
|
return c.json({
|
|
1493
|
-
error:
|
|
1494
|
+
error: say(c, "onlyAvailableCloud"),
|
|
1494
1495
|
code: "not_managed",
|
|
1495
1496
|
}, 404);
|
|
1496
1497
|
}
|
|
1497
1498
|
const { lang } = z
|
|
1498
|
-
.object({ lang: z.enum(
|
|
1499
|
+
.object({ lang: z.enum(LANGS).default("en") })
|
|
1499
1500
|
.parse(await c.req.json().catch(() => ({})));
|
|
1500
1501
|
await installMail(c.get("user").email, lang);
|
|
1501
1502
|
return c.json({ ok: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import {
|
|
2
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
3
3
|
import { and, eq } from "drizzle-orm";
|
|
4
4
|
import { db, schema } from "../db/client.js";
|
|
5
5
|
import { managed, tenantInfo } from "../manage.js";
|
|
@@ -31,7 +31,7 @@ export function currentRole() {
|
|
|
31
31
|
return actor.getStore() ?? "owner";
|
|
32
32
|
}
|
|
33
33
|
export const isAdminRole = (role) => role === "owner" || role === "admin";
|
|
34
|
-
export const MEMBERS_EDIT_ONLY =
|
|
34
|
+
export const MEMBERS_EDIT_ONLY = note("onlyTeamAdmins");
|
|
35
35
|
/** Refuses a member: making and deleting is for the owner and the admins of the team. */
|
|
36
36
|
export async function requireAdmin() {
|
|
37
37
|
if (syncing() || !managed || isAdminRole(currentRole())) {
|
|
@@ -52,8 +52,8 @@ export async function mayBuild() {
|
|
|
52
52
|
// An older Manage-App does not say: then it builds. One that cannot be reached: nothing changes.
|
|
53
53
|
return info ? info.limits.build !== false : false;
|
|
54
54
|
}
|
|
55
|
-
export const READ_ONLY_PROJECT =
|
|
56
|
-
export const READ_ONLY_TENANT =
|
|
55
|
+
export const READ_ONLY_PROJECT = note("spaceEditedLocally");
|
|
56
|
+
export const READ_ONLY_TENANT = note("wizardsRunOncePublished");
|
|
57
57
|
/** Refuses when the tenant builds nothing here. */
|
|
58
58
|
export async function requireBuild() {
|
|
59
59
|
if (syncing() || (await mayBuild())) {
|
|
@@ -5,6 +5,7 @@ import { listConnectors } from "../connectors/external.js";
|
|
|
5
5
|
import { db, schema } from "../db/client.js";
|
|
6
6
|
import { managed } from "../manage.js";
|
|
7
7
|
import { hasTextModel } from "../models.js";
|
|
8
|
+
import { serverText } from "../texts.js";
|
|
8
9
|
import { draftFiles, readFile } from "./files.js";
|
|
9
10
|
import { ownedProject } from "./projects.js";
|
|
10
11
|
import { addMessage, draftIssues, ownedWizard, wizardMessages, writableWizard } from "./wizards.js";
|
|
@@ -72,19 +73,9 @@ lang, io) {
|
|
|
72
73
|
images,
|
|
73
74
|
lang,
|
|
74
75
|
});
|
|
75
|
-
const de = lang === "de";
|
|
76
76
|
const reply = result.unfinished
|
|
77
|
-
? `${result.reply ? `${result.reply}\n\n` : ""}${
|
|
78
|
-
|
|
79
|
-
: "I have not quite finished. Write “continue” and I will carry on right there."}`
|
|
80
|
-
: result.reply ||
|
|
81
|
-
(result.changed
|
|
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
|
+
? `${result.reply ? `${result.reply}\n\n` : ""}${serverText(lang, "notFinished")}`
|
|
78
|
+
: result.reply || serverText(lang, result.changed ? "done" : "notUnderstood");
|
|
88
79
|
await addMessage(w.id, { role: "assistant", content: reply, changed: result.changed });
|
|
89
80
|
const after = await ownedWizard(userId, wizardId);
|
|
90
81
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ASSISTANT_LAYOUTS } from "@engenty-wizards/shared/assistant";
|
|
2
|
-
import {
|
|
2
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
3
3
|
import { generateText } from "ai";
|
|
4
4
|
import { and, asc, desc, eq, isNotNull, isNull, sql } from "drizzle-orm";
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
@@ -253,7 +253,7 @@ export async function readChat(user, chatId) {
|
|
|
253
253
|
where: and(mine(user), eq(schema.assistantChat.id, chatId)),
|
|
254
254
|
});
|
|
255
255
|
if (!chat) {
|
|
256
|
-
throw new ServiceError("not_found",
|
|
256
|
+
throw new ServiceError("not_found", note("chatNotExist"));
|
|
257
257
|
}
|
|
258
258
|
return {
|
|
259
259
|
id: chat.id,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GRADIENT_TYPES, gradientCss, mainLogo, } from "@engenty-wizards/shared/projects";
|
|
2
|
-
import {
|
|
2
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
3
3
|
import { generateText, Output } from "ai";
|
|
4
4
|
import { and, asc, eq } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -66,14 +66,14 @@ export async function describeGradient(brand, text, current) {
|
|
|
66
66
|
answer = result.output;
|
|
67
67
|
}
|
|
68
68
|
catch {
|
|
69
|
-
throw new ServiceError("invalid",
|
|
69
|
+
throw new ServiceError("invalid", note("gradientFailed"));
|
|
70
70
|
}
|
|
71
71
|
const stops = answer.stops
|
|
72
72
|
.filter((s) => HEX.test(s.color))
|
|
73
73
|
.slice(0, 12)
|
|
74
74
|
.map((s) => ({ color: s.color.toLowerCase(), at: Math.min(100, Math.max(0, s.at)) }));
|
|
75
75
|
if (stops.length < 2) {
|
|
76
|
-
throw new ServiceError("invalid",
|
|
76
|
+
throw new ServiceError("invalid", note("gradientFailed"));
|
|
77
77
|
}
|
|
78
78
|
return {
|
|
79
79
|
name: answer.name.trim().slice(0, 80),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
3
3
|
import { and, asc, eq, inArray, isNotNull, isNull, like } from "drizzle-orm";
|
|
4
4
|
import { accountToken, linkedAccount } from "../auth/account.js";
|
|
5
5
|
import { control, controlDb, db, schema } from "../db/client.js";
|
|
@@ -26,7 +26,7 @@ export async function cloudState(wizardId) {
|
|
|
26
26
|
async function call(method, path, body, timeoutMs = 30_000) {
|
|
27
27
|
const token = await accountToken();
|
|
28
28
|
if (!token) {
|
|
29
|
-
throw new ServiceError("refused",
|
|
29
|
+
throw new ServiceError("refused", note("accountSignedOut"), {
|
|
30
30
|
reason: "signed_out",
|
|
31
31
|
});
|
|
32
32
|
}
|
|
@@ -40,7 +40,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
|
|
|
40
40
|
signal: AbortSignal.timeout(timeoutMs),
|
|
41
41
|
}).catch(() => null);
|
|
42
42
|
if (!res) {
|
|
43
|
-
throw new ServiceError("refused",
|
|
43
|
+
throw new ServiceError("refused", note("cloudNotReachedRight"), {
|
|
44
44
|
reason: "unreachable",
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -50,7 +50,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
|
|
|
50
50
|
function refusal(answer, fallback) {
|
|
51
51
|
const { error, reason, ...rest } = answer.body;
|
|
52
52
|
const message = answer.status === 401
|
|
53
|
-
?
|
|
53
|
+
? note("accountSignedOut")
|
|
54
54
|
: typeof error === "string" && error
|
|
55
55
|
? error
|
|
56
56
|
: fallback;
|
|
@@ -214,7 +214,7 @@ export async function syncToCloud(userId, wizardId) {
|
|
|
214
214
|
const w = await ownedWizard(userId, wizardId);
|
|
215
215
|
const version = await publishedVersion(w);
|
|
216
216
|
if (!version) {
|
|
217
|
-
throw new ServiceError("refused",
|
|
217
|
+
throw new ServiceError("refused", note("publishWizardFirst"));
|
|
218
218
|
}
|
|
219
219
|
const before = await cloudState(w.id);
|
|
220
220
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
2
2
|
import { errorText, NotedError } from "../model-errors.js";
|
|
3
3
|
const STATUS = {
|
|
4
4
|
not_found: 404,
|
|
@@ -26,4 +26,4 @@ export class ServiceError extends NotedError {
|
|
|
26
26
|
return { error: errorText(this, lang), code: this.code, ...this.data };
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
export const notFound = () => new ServiceError("not_found",
|
|
29
|
+
export const notFound = () => new ServiceError("not_found", note("notFound"));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dayOf, slugOf } from "@engenty-wizards/shared/knowledge";
|
|
2
|
-
import {
|
|
2
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
3
3
|
import { columnIdOf } from "@engenty-wizards/shared/space-data";
|
|
4
4
|
import { generateText, Output } from "ai";
|
|
5
5
|
import { and, eq, inArray, isNull } from "drizzle-orm";
|
|
@@ -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",
|
|
507
|
+
throw new ServiceError("not_found", note("valueNotExist"));
|
|
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", note("nothingFiledUnderValue"));
|
|
513
513
|
}
|
|
514
514
|
const model = await textModel("standard");
|
|
515
515
|
const result = await generateText({
|
|
@@ -4,7 +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 {
|
|
7
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
8
8
|
import { and, eq, inArray, notInArray } from "drizzle-orm";
|
|
9
9
|
import { formulaEstimate } from "../credits/estimate.js";
|
|
10
10
|
import { control, controlDb, db, schema, withTenant } from "../db/client.js";
|
|
@@ -251,12 +251,22 @@ export async function searchMarketplace(search) {
|
|
|
251
251
|
facets: addFacets(listed.facets, found.facets),
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* The gallery for someone not signed in (the studio's start page for guests): what the
|
|
256
|
+
* marketplace lists, without a tenant's stars and plugins. An entry that needs a plugin says so.
|
|
257
|
+
*/
|
|
258
|
+
export function searchMarketplaceAsGuest(search) {
|
|
259
|
+
return searchListed(search, true);
|
|
260
|
+
}
|
|
254
261
|
/** Searches the marketplace; without an answer, what is kept here, scored the same way. */
|
|
255
|
-
async function searchListed(search) {
|
|
262
|
+
async function searchListed(search, guest = false) {
|
|
256
263
|
const { q = "", lang, starred: onlyStarred, limit = 60, offset = 0, ...filters } = search;
|
|
257
|
-
const stars = await starredIds();
|
|
264
|
+
const stars = guest ? [] : await starredIds();
|
|
258
265
|
const starred = new Set(stars);
|
|
259
|
-
const unavailable = await missingCapabilities();
|
|
266
|
+
const unavailable = guest ? new Set() : await missingCapabilities();
|
|
267
|
+
const plugged = (entries) => guest
|
|
268
|
+
? entries.map((e) => (e.plugins?.length ? { ...e, needs: e.plugins } : e))
|
|
269
|
+
: withPlugins(entries);
|
|
260
270
|
const params = new URLSearchParams({ lang, limit: String(limit), offset: String(offset) });
|
|
261
271
|
if (onlyStarred) {
|
|
262
272
|
params.set("ids", stars.join(","));
|
|
@@ -278,7 +288,7 @@ async function searchListed(search) {
|
|
|
278
288
|
const optional = await keptOptional(remote.entries.map((e) => e.id), lang, unavailable);
|
|
279
289
|
return {
|
|
280
290
|
...remote,
|
|
281
|
-
entries: await
|
|
291
|
+
entries: await plugged(remote.entries.map((e) => asEntry(e, starred, unavailable, optional.get(e.id)))),
|
|
282
292
|
offline: false,
|
|
283
293
|
unavailable: [...unavailable],
|
|
284
294
|
};
|
|
@@ -290,7 +300,7 @@ async function searchListed(search) {
|
|
|
290
300
|
const optional = await keptOptional(page.entries.map((e) => e.id), lang, unavailable);
|
|
291
301
|
return {
|
|
292
302
|
...page,
|
|
293
|
-
entries: await
|
|
303
|
+
entries: await plugged(page.entries.map((e) => asEntry(e, starred, unavailable, optional.get(e.id)))),
|
|
294
304
|
offline: true,
|
|
295
305
|
unavailable: [...unavailable],
|
|
296
306
|
};
|
|
@@ -404,13 +414,13 @@ export async function marketplaceWizard(id, lang) {
|
|
|
404
414
|
return null;
|
|
405
415
|
}
|
|
406
416
|
if (!readable(found.definition)) {
|
|
407
|
-
throw new ServiceError("refused",
|
|
417
|
+
throw new ServiceError("refused", note("templateNeedsNewerVersion"));
|
|
408
418
|
}
|
|
409
419
|
// A wizard whose plugins are not here would stop at its first step that uses them.
|
|
410
420
|
const have = await pluginIdsOf(currentTenant());
|
|
411
421
|
const needs = pluginsOf(wizardSchema.parse(found.definition)).filter((p) => !have.has(p));
|
|
412
422
|
if (needs.length) {
|
|
413
|
-
throw new ServiceError("refused",
|
|
423
|
+
throw new ServiceError("refused", note("templateNeedsPlugins", { count: needs.length, plugins: needs }));
|
|
414
424
|
}
|
|
415
425
|
return {
|
|
416
426
|
id: found.id,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
2
2
|
import { manifestSchema, PACKAGE_FILES, PACKAGE_FORMAT, PACKAGE_MANIFEST, PACKAGE_MAX_BYTES, } from "@engenty-wizards/shared/wizard-package";
|
|
3
3
|
import { cleanPath, WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
|
|
4
4
|
import { strFromU8, unzipSync } from "fflate";
|
|
@@ -13,7 +13,7 @@ export function json(data) {
|
|
|
13
13
|
return JSON.parse(strFromU8(data));
|
|
14
14
|
}
|
|
15
15
|
catch {
|
|
16
|
-
throw invalid(
|
|
16
|
+
throw invalid(note("wizardJsonUnreadable"));
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
/** A manifest, or a bare definition: a wizard without workspace is fully told by its JSON. */
|
|
@@ -23,7 +23,7 @@ export function readManifest(raw) {
|
|
|
23
23
|
}
|
|
24
24
|
const parsed = manifestSchema.safeParse(raw);
|
|
25
25
|
if (!parsed.success) {
|
|
26
|
-
throw invalid(
|
|
26
|
+
throw invalid(note("wizardFileComesNewer"));
|
|
27
27
|
}
|
|
28
28
|
return parsed.data;
|
|
29
29
|
}
|
|
@@ -46,26 +46,24 @@ export function readPackage(data) {
|
|
|
46
46
|
total += file.originalSize;
|
|
47
47
|
count += 1;
|
|
48
48
|
if (file.originalSize > WORKSPACE_LIMITS.fileBytes) {
|
|
49
|
-
throw invalid(
|
|
49
|
+
throw invalid(note("fileOver5Mb", { name: file.name }));
|
|
50
50
|
}
|
|
51
51
|
if (count > WORKSPACE_LIMITS.files + 1 || total > PACKAGE_MAX_BYTES) {
|
|
52
|
-
throw invalid(
|
|
52
|
+
throw invalid(note("wizardFileTooLarge"));
|
|
53
53
|
}
|
|
54
54
|
return true;
|
|
55
55
|
},
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
catch (err) {
|
|
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
|
+
throw err instanceof ServiceError ? err : invalid(note("wizardFileNotOpened"));
|
|
62
60
|
}
|
|
63
61
|
// A package unpacked and zipped again often sits in a folder of its own.
|
|
64
62
|
const manifestPath = Object.keys(entries)
|
|
65
63
|
.filter((name) => name === PACKAGE_MANIFEST || name.endsWith(`/${PACKAGE_MANIFEST}`))
|
|
66
64
|
.sort((a, b) => a.length - b.length)[0];
|
|
67
65
|
if (manifestPath === undefined) {
|
|
68
|
-
throw invalid(
|
|
66
|
+
throw invalid(note("wizardJsonMissing"));
|
|
69
67
|
}
|
|
70
68
|
const root = manifestPath.slice(0, -PACKAGE_MANIFEST.length);
|
|
71
69
|
const files = Object.entries(entries)
|
|
@@ -73,12 +71,12 @@ export function readPackage(data) {
|
|
|
73
71
|
.map(([name, content]) => {
|
|
74
72
|
const path = cleanPath(name.slice(root.length + PACKAGE_FILES.length));
|
|
75
73
|
if (!path) {
|
|
76
|
-
throw invalid(
|
|
74
|
+
throw invalid(note("notValidFilePath", { name }));
|
|
77
75
|
}
|
|
78
76
|
return { path, data: content };
|
|
79
77
|
});
|
|
80
78
|
if (files.reduce((n, f) => n + f.data.byteLength, 0) > WORKSPACE_LIMITS.totalBytes) {
|
|
81
|
-
throw invalid(
|
|
79
|
+
throw invalid(note("workspaceWizardFileLarger"));
|
|
82
80
|
}
|
|
83
81
|
return { manifest: readManifest(json(entries[manifestPath])), files };
|
|
84
82
|
}
|
|
@@ -4,6 +4,7 @@ import { strToU8, zipSync } from "fflate";
|
|
|
4
4
|
import { listConnectors } from "../connectors/external.js";
|
|
5
5
|
import { db, schema } from "../db/client.js";
|
|
6
6
|
import { getBlob } from "../files/blobs.js";
|
|
7
|
+
import { serverText } from "../texts.js";
|
|
7
8
|
import { requireAdmin } from "./access.js";
|
|
8
9
|
import { draftFiles, writeFile } from "./files.js";
|
|
9
10
|
import { pageTree } from "./knowledge.js";
|
|
@@ -144,16 +145,14 @@ lang = "en") {
|
|
|
144
145
|
// The shape is checked before anything is written; a wrong one leaves no wizard behind.
|
|
145
146
|
const { draft } = parseDraft(manifest.definition, files.map((f) => f.path));
|
|
146
147
|
const lacks = await missing(userId, projectId, draft, manifest);
|
|
147
|
-
const
|
|
148
|
+
const counts = {
|
|
149
|
+
name,
|
|
150
|
+
steps: serverText(lang, "stepCount", { count: draft.steps.length }),
|
|
151
|
+
files: serverText(lang, "fileCount", { count: files.length }),
|
|
152
|
+
};
|
|
148
153
|
const note = [
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
: `Imported${name ? ` from “${name}”` : ""}: ${draft.steps.length} steps, ${files.length} ${files.length === 1 ? "file" : "files"}.`,
|
|
152
|
-
lacks.length
|
|
153
|
-
? de
|
|
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
|
-
: "",
|
|
154
|
+
serverText(lang, name ? "importedFrom" : "imported", counts),
|
|
155
|
+
lacks.length ? serverText(lang, "spaceStillNeeds", { needs: lacks }) : "",
|
|
157
156
|
]
|
|
158
157
|
.filter(Boolean)
|
|
159
158
|
.join("\n\n");
|