wizards 0.4.3 → 0.4.5
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 +5 -1
- package/apps/runtime/dist/docs/dev/plugins/routes.md +35 -1
- package/apps/runtime/dist/docs/dev/plugins/server.md +27 -1
- package/apps/runtime/dist/docs/dev/plugins/starters.md +1 -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/manage.js +29 -0
- package/apps/runtime/dist/model-errors.js +8 -3
- package/apps/runtime/dist/models.js +58 -38
- package/apps/runtime/dist/plugins/channels.js +77 -0
- package/apps/runtime/dist/plugins/loader.js +9 -1
- package/apps/runtime/dist/plugins/public.js +16 -2
- package/apps/runtime/dist/plugins/registry.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/plugin-public.js +65 -12
- 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/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-DWaBKvI6.js → AboutDialog-BCtnYRj2.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-BcM1gXPM.js → ChangelogDialog-DBjoIIHL.js} +1 -1
- package/apps/web/dist/assets/ChatView-BcyUYN0Q.js +10 -0
- package/apps/web/dist/assets/EditorPage-Ce1QD-DK.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-S1s1yRaP.js → FlowPreview-BtpAdyt-.js} +1 -1
- package/apps/web/dist/assets/{Harness-DILx5Edb.js → Harness-DJJ_yMLY.js} +2 -2
- package/apps/web/dist/assets/NewWizardChannels-BrYLMV6F.js +1 -0
- package/apps/web/dist/assets/NewWizardPage-827zwhhW.js +7 -0
- package/apps/web/dist/assets/{OnboardingPage-xoGrKIJ-.js → OnboardingPage-jC6FidpF.js} +1 -1
- package/apps/web/dist/assets/ProjectPage-BYcM1-1H.js +1 -0
- package/apps/web/dist/assets/{PublicRunner-Cab2jUy6.js → PublicRunner-BofZl0MO.js} +1 -1
- package/apps/web/dist/assets/{ReadOnly-DQPHmvS0.js → ReadOnly-B4nIGkoj.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-Dx-BRCkY.js +4 -0
- package/apps/web/dist/assets/RunnerView-CDCp61aq.js +1 -0
- package/apps/web/dist/assets/SettingsPage-B49nLHwv.js +1 -0
- package/apps/web/dist/assets/SetupPage-4tzzHUq0.js +1 -0
- package/apps/web/dist/assets/SharePage-Bs4s73h4.js +1 -0
- package/apps/web/dist/assets/changelog-D-12LwZB.js +1 -0
- package/apps/web/dist/assets/{circle-alert-DCBV7n5k.js → circle-alert-BueS7qO8.js} +1 -1
- package/apps/web/dist/assets/esm-CjGdsKU4.js +7 -0
- package/apps/web/dist/assets/index-BRDn-5v_.js +14 -0
- package/apps/web/dist/assets/index-mlnAPOdf.css +1 -0
- package/apps/web/dist/assets/{lock-DlzTfwxP.js → lock-CkXYnQux.js} +1 -1
- package/apps/web/dist/assets/{phone-BFD815nc.js → phone-CHkEf-Dz.js} +1 -1
- package/apps/web/dist/assets/settings-menu-CrVpArM_.js +1 -0
- package/apps/web/dist/assets/ui-DrdqFaQt.js +134 -0
- package/apps/web/dist/assets/{upload-BEf5-d6X.js → upload-CzkkZqAo.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 +98 -2
- 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/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-D0RRIl15.js +0 -10
- package/apps/web/dist/assets/EditorPage-DrJ6e_2q.js +0 -3
- package/apps/web/dist/assets/NewWizardChannels-CEILDADp.js +0 -1
- package/apps/web/dist/assets/NewWizardPage-DxOtK2E-.js +0 -7
- package/apps/web/dist/assets/ProjectPage-6E5z4nZA.js +0 -1
- package/apps/web/dist/assets/RunDrawer-B3FNYpgZ.js +0 -4
- package/apps/web/dist/assets/RunnerView-D6sGVnRy.js +0 -1
- package/apps/web/dist/assets/SettingsPage-DHtIqmOJ.js +0 -1
- package/apps/web/dist/assets/SetupPage-Cn9iQbJx.js +0 -1
- package/apps/web/dist/assets/SharePage-CMr72PeU.js +0 -1
- package/apps/web/dist/assets/changelog-DfmWTlav.js +0 -1
- package/apps/web/dist/assets/esm-BPfTI3lL.js +0 -7
- package/apps/web/dist/assets/index-B-D8UvL9.js +0 -14
- package/apps/web/dist/assets/index-CmxVazOf.css +0 -1
- package/apps/web/dist/assets/settings-menu-BJm0CwOj.js +0 -1
- package/apps/web/dist/assets/ui-DTuixH4y.js +0 -134
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
1
2
|
import { PACKAGE_MIME } from "@engenty-wizards/shared/wizard-package";
|
|
2
3
|
import { Hono } from "hono";
|
|
3
4
|
import { bodyLimit } from "hono/body-limit";
|
|
@@ -19,6 +20,7 @@ import { siteJob, siteSchema, startSiteJob } from "../services/site.js";
|
|
|
19
20
|
import { checkOnServer, checkSchema, listSyncedSpaces, patchSyncedWizard, removeSyncedSpace, removeSyncedWizard, rotateSyncedLink, syncDataSchema, syncSettingsSchema, syncSpaceData, syncWizard, syncWizardSchema, } from "../services/spaces.js";
|
|
20
21
|
import { createWizard, ownedWizard, publishWizard, writeDraft } from "../services/wizards.js";
|
|
21
22
|
import { codeOf, tenantStatus } from "../tenants/control.js";
|
|
23
|
+
import { inLang } from "./lang.js";
|
|
22
24
|
import { dispatchAs } from "./plugins.js";
|
|
23
25
|
const importSchema = z.object({
|
|
24
26
|
/** An earlier import's wizard: it is updated instead of a new one being made. */
|
|
@@ -66,7 +68,10 @@ async function caller(c, scopes) {
|
|
|
66
68
|
function withinSyncLimit(c, who) {
|
|
67
69
|
return allowed(`sync:${who.tenantId}`, 240, HOUR)
|
|
68
70
|
? null
|
|
69
|
-
: c.json({
|
|
71
|
+
: c.json({
|
|
72
|
+
error: inLang(c, "Zu viele Übertragungen. Bitte später noch einmal.", "Too many transfers. Please try again later."),
|
|
73
|
+
code: "rate_limited",
|
|
74
|
+
}, 429);
|
|
70
75
|
}
|
|
71
76
|
/**
|
|
72
77
|
* A plugin's routes for a client with a key or token (the WordPress plugin's Kontakte, Termine):
|
|
@@ -82,7 +87,7 @@ async function pluginCall(c) {
|
|
|
82
87
|
if (!(await pluginIdsOf(who.tenantId)).has(id)) {
|
|
83
88
|
return loadedPlugin(id) || PRO_PLUGINS.includes(id)
|
|
84
89
|
? c.json({
|
|
85
|
-
error: "Dieses Plugin gehört nicht zu deinem Paket. Mit Pro ist es dabei.",
|
|
90
|
+
error: inLang(c, "Dieses Plugin gehört nicht zu deinem Paket. Mit Pro ist es dabei.", "This plugin is not part of your plan. It comes with Pro."),
|
|
86
91
|
code: "plugin",
|
|
87
92
|
plugin: id,
|
|
88
93
|
}, 403)
|
|
@@ -171,7 +176,10 @@ export const apiRoutes = new Hono()
|
|
|
171
176
|
knowledge: c.req.query("knowledge") === "1",
|
|
172
177
|
});
|
|
173
178
|
if (!found) {
|
|
174
|
-
return c.json({
|
|
179
|
+
return c.json({
|
|
180
|
+
error: inLang(c, "Der Wizard ist noch nicht veröffentlicht.", "The wizard is not published yet."),
|
|
181
|
+
code: "not_found",
|
|
182
|
+
}, 404);
|
|
175
183
|
}
|
|
176
184
|
return new Response(new Uint8Array(found.zip), {
|
|
177
185
|
headers: {
|
|
@@ -278,7 +286,10 @@ export const apiRoutes = new Hono()
|
|
|
278
286
|
return who;
|
|
279
287
|
}
|
|
280
288
|
if (!allowed(`site:${who.tenantId}`, 20, HOUR)) {
|
|
281
|
-
return c.json({
|
|
289
|
+
return c.json({
|
|
290
|
+
error: inLang(c, "Zu oft hintereinander. Bitte später noch einmal.", "Too often in a row. Please try again later."),
|
|
291
|
+
code: "rate_limited",
|
|
292
|
+
}, 429);
|
|
282
293
|
}
|
|
283
294
|
const body = siteSchema.parse(await c.req.json());
|
|
284
295
|
return inTenantAs(who, async () => c.json(await startSiteJob(who, body)));
|
|
@@ -410,7 +421,7 @@ export const apiRoutes = new Hono()
|
|
|
410
421
|
return c.json({ wizardId, revision: written.revision, issues: written.issues });
|
|
411
422
|
}
|
|
412
423
|
if (written.issues.length) {
|
|
413
|
-
throw new ServiceError("has_issues", "Der Wizard hat noch Fehler.", {
|
|
424
|
+
throw new ServiceError("has_issues", bilingual("Der Wizard hat noch Fehler.", "The wizard still has errors."), {
|
|
414
425
|
issues: written.issues,
|
|
415
426
|
wizardId,
|
|
416
427
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The language a visitor's request asks for: what the runner around it speaks, the browser's
|
|
3
|
+
* first language (apps/web: `navigator.language`) — German for "de…", else English; English
|
|
4
|
+
* without one, as the runner starts.
|
|
5
|
+
*/
|
|
6
|
+
export function requestLang(c) {
|
|
7
|
+
const asked = c.req.header("accept-language")?.trim().toLowerCase();
|
|
8
|
+
return asked?.startsWith("de") ? "de" : "en";
|
|
9
|
+
}
|
|
10
|
+
/** A line the person reads, in the language their request asks for. */
|
|
11
|
+
export function inLang(c, de, en) {
|
|
12
|
+
return requestLang(c) === "de" ? de : en;
|
|
13
|
+
}
|
|
@@ -1,26 +1,82 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { withTenant } from "../db/client.js";
|
|
3
|
-
import { tenantOfAddress } from "../plugins/public.js";
|
|
3
|
+
import { SHARED, tenantOfAddress } from "../plugins/public.js";
|
|
4
4
|
import { loadedPlugin, pluginIdsOf } from "../plugins/registry.js";
|
|
5
5
|
import { tenantStatus } from "../tenants/control.js";
|
|
6
6
|
import { answerOf } from "./plugins.js";
|
|
7
7
|
/**
|
|
8
8
|
* Plugins' public routes (`server.registerPublicRoute`), below `/api/public/plugins/<id>/<ref>`.
|
|
9
9
|
* Nobody is signed in: the ref names the tenant, and the plugin checks a secret of its own.
|
|
10
|
+
* Shared routes (`server.registerSharedRoute`) are below `/api/public/plugins/<id>/shared`, for
|
|
11
|
+
* every tenant; their handler enters a tenant by the ref the call carries.
|
|
10
12
|
*/
|
|
11
13
|
const notFound = (c) => c.json({ error: "not found", code: "not_found" }, 404);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
14
|
+
/** What a handler threw, as the answer: its status and code, or a 500. */
|
|
15
|
+
function failed(c, id, what, err) {
|
|
16
|
+
const answer = answerOf(err);
|
|
17
|
+
if (answer) {
|
|
18
|
+
return c.json(answer.body, answer.status);
|
|
19
|
+
}
|
|
20
|
+
console.error(`[plugin ${id}] ${what}:`, err);
|
|
21
|
+
return c.json({ error: "The plugin failed.", code: "plugin_failed" }, 500);
|
|
22
|
+
}
|
|
23
|
+
/** The tenant of a ref, when it is active and has the plugin. */
|
|
24
|
+
async function tenantWithPlugin(id, ref) {
|
|
15
25
|
const tenantId = await tenantOfAddress(id, ref);
|
|
16
26
|
if (!tenantId || (await tenantStatus(tenantId)) !== "active") {
|
|
17
|
-
return
|
|
27
|
+
return null;
|
|
18
28
|
}
|
|
19
|
-
|
|
29
|
+
return (await pluginIdsOf(tenantId)).has(id) ? tenantId : null;
|
|
30
|
+
}
|
|
31
|
+
async function shared(c) {
|
|
32
|
+
const id = c.req.param("id") ?? "";
|
|
33
|
+
const plugin = loadedPlugin(id);
|
|
20
34
|
if (!plugin) {
|
|
21
35
|
return notFound(c);
|
|
22
36
|
}
|
|
23
37
|
const url = new URL(c.req.url);
|
|
38
|
+
const marker = `/plugins/${id}/${SHARED}`;
|
|
39
|
+
const path = url.pathname.slice(url.pathname.indexOf(marker) + marker.length);
|
|
40
|
+
for (const { route, pattern, params } of plugin.sharedRoutes) {
|
|
41
|
+
const match = route.method === c.req.method ? pattern.exec(path) : null;
|
|
42
|
+
if (!match) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const request = {
|
|
46
|
+
request: c.req.raw,
|
|
47
|
+
path: path || "/",
|
|
48
|
+
params: Object.fromEntries(params.map((name, i) => [name, decodeURIComponent(match[i + 1])])),
|
|
49
|
+
query: url.searchParams,
|
|
50
|
+
async inTenant(ref, work) {
|
|
51
|
+
const tenantId = await tenantWithPlugin(id, ref);
|
|
52
|
+
if (!tenantId) {
|
|
53
|
+
throw Object.assign(new Error("No tenant has this address."), {
|
|
54
|
+
status: 404,
|
|
55
|
+
code: "not_found",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return withTenant(tenantId, async () => await work());
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
try {
|
|
62
|
+
const result = await route.handler(request);
|
|
63
|
+
return result instanceof Response ? result : c.json(result ?? null);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return failed(c, id, `shared ${route.method} ${path}`, err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return notFound(c);
|
|
70
|
+
}
|
|
71
|
+
async function dispatch(c) {
|
|
72
|
+
const id = c.req.param("id") ?? "";
|
|
73
|
+
const ref = c.req.param("ref") ?? "";
|
|
74
|
+
const tenantId = await tenantWithPlugin(id, ref);
|
|
75
|
+
const plugin = tenantId ? loadedPlugin(id) : undefined;
|
|
76
|
+
if (!tenantId || !plugin) {
|
|
77
|
+
return notFound(c);
|
|
78
|
+
}
|
|
79
|
+
const url = new URL(c.req.url);
|
|
24
80
|
const marker = `/plugins/${id}/${ref}`;
|
|
25
81
|
const path = url.pathname.slice(url.pathname.indexOf(marker) + marker.length);
|
|
26
82
|
for (const { route, pattern, params } of plugin.publicRoutes) {
|
|
@@ -44,16 +100,13 @@ async function dispatch(c) {
|
|
|
44
100
|
return result instanceof Response ? result : c.json(result ?? null);
|
|
45
101
|
}
|
|
46
102
|
catch (err) {
|
|
47
|
-
|
|
48
|
-
if (answer) {
|
|
49
|
-
return c.json(answer.body, answer.status);
|
|
50
|
-
}
|
|
51
|
-
console.error(`[plugin ${id}] public ${route.method} ${path}:`, err);
|
|
52
|
-
return c.json({ error: "The plugin failed.", code: "plugin_failed" }, 500);
|
|
103
|
+
return failed(c, id, `public ${route.method} ${path}`, err);
|
|
53
104
|
}
|
|
54
105
|
}
|
|
55
106
|
return notFound(c);
|
|
56
107
|
}
|
|
57
108
|
export const publicPluginRoutes = new Hono()
|
|
109
|
+
.all(`/:id/${SHARED}`, shared)
|
|
110
|
+
.all(`/:id/${SHARED}/*`, shared)
|
|
58
111
|
.all("/:id/:ref", dispatch)
|
|
59
112
|
.all("/:id/:ref/*", dispatch);
|
|
@@ -10,12 +10,13 @@ import { nanoid } from "nanoid";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { principalOf } from "../auth/index.js";
|
|
12
12
|
import { MARK_FOCUSED_PERSONAL } from "../browser/snapshot.js";
|
|
13
|
-
import { ConnectError, connectionViews, connectWithCredentials, disconnect, finishOAuth, oauthStateRun, startOAuth, } from "../connectors/index.js";
|
|
13
|
+
import { ConnectError, connectionViews, connectWithCredentials, disconnect, finishOAuth, oauthStateLang, oauthStateRun, startOAuth, } from "../connectors/index.js";
|
|
14
14
|
import { finishPluginOAuth, pluginStateTenant } from "../connectors/plugin-connections.js";
|
|
15
15
|
import { db, schema, withTenant } from "../db/client.js";
|
|
16
16
|
import { answerAsk, pendingAsk } from "../engine/asks.js";
|
|
17
17
|
import { answerDevice, offerDevice, openDeviceCalls } from "../engine/device.js";
|
|
18
18
|
import { emitEvent, signalChanged, watchRun } from "../engine/events.js";
|
|
19
|
+
import { fieldErrorsIn } from "../engine/input.js";
|
|
19
20
|
import { fromOwner } from "../engine/owner.js";
|
|
20
21
|
import { pushAvailable, setPushDevice } from "../engine/push.js";
|
|
21
22
|
import { liveResources } from "../engine/resources.js";
|
|
@@ -38,6 +39,7 @@ import { isIconFile, wizardAvatarSvg, wizardIcon } from "../services/wizard-icon
|
|
|
38
39
|
import { clearStore, deleteRows, listRows, listSecrets, readStoreFile, StoreError, saveRows, scopeOf, storeFiles, updateRow, } from "../store/index.js";
|
|
39
40
|
import { tenantOfLink, tenantOfRun, tenantStatus, tokenOfCode, visitorOverLimit, } from "../tenants/control.js";
|
|
40
41
|
import { byteRange, uploadLimit, wizardManifest } from "./delivery.js";
|
|
42
|
+
import { requestLang } from "./lang.js";
|
|
41
43
|
const VISITOR_COOKIE = "wz_vid";
|
|
42
44
|
/** Kept files a review shows inline. */
|
|
43
45
|
const INLINE_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/webp"]);
|
|
@@ -53,15 +55,6 @@ export function clientIp(c) {
|
|
|
53
55
|
return null;
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
/**
|
|
57
|
-
* The language a visitor's request asks for: what the runner around it speaks, the browser's
|
|
58
|
-
* first language (apps/web: `navigator.language`) — German for "de…", else English; German
|
|
59
|
-
* without one, as the runner starts.
|
|
60
|
-
*/
|
|
61
|
-
function requestLang(c) {
|
|
62
|
-
const asked = c.req.header("accept-language")?.trim().toLowerCase();
|
|
63
|
-
return !asked || asked.startsWith("de") ? "de" : "en";
|
|
64
|
-
}
|
|
65
58
|
/** A refusal the visitor reads, in the language their request asks for. */
|
|
66
59
|
function refuse(c, note, status) {
|
|
67
60
|
return c.json({ error: noteText(note, requestLang(c)) }, status);
|
|
@@ -140,7 +133,7 @@ async function viewOf(run) {
|
|
|
140
133
|
}
|
|
141
134
|
function commandError(c, err) {
|
|
142
135
|
if (err instanceof RunInputError) {
|
|
143
|
-
return c.json({ error: errorText(err, requestLang(c)), fields: err.errors }, 422);
|
|
136
|
+
return c.json({ error: errorText(err, requestLang(c)), fields: fieldErrorsIn(err.errors, requestLang(c)) }, 422);
|
|
144
137
|
}
|
|
145
138
|
if (err instanceof RunConflict) {
|
|
146
139
|
return c.json({ error: errorText(err, requestLang(c)) }, 409);
|
|
@@ -633,7 +626,7 @@ export const runRoutes = new Hono()
|
|
|
633
626
|
.object({
|
|
634
627
|
lat: z.number().min(-90).max(90),
|
|
635
628
|
lng: z.number().min(-180).max(180),
|
|
636
|
-
language: z.enum(["de", "en"]).default("
|
|
629
|
+
language: z.enum(["de", "en"]).default("en"),
|
|
637
630
|
})
|
|
638
631
|
.parse(await c.req.json());
|
|
639
632
|
return c.json({ label: await reverseGeocode(lat, lng, language) });
|
|
@@ -841,12 +834,12 @@ export const runRoutes = new Hono()
|
|
|
841
834
|
return c.json({ error: "not found" }, 404);
|
|
842
835
|
}
|
|
843
836
|
try {
|
|
844
|
-
const url = await startOAuth(scopeOf(run), await projectIdOf(run), run.id, connection, c.req.param("connectorId"));
|
|
837
|
+
const url = await startOAuth(scopeOf(run), await projectIdOf(run), run.id, connection, c.req.param("connectorId"), requestLang(c));
|
|
845
838
|
return c.json({ url });
|
|
846
839
|
}
|
|
847
840
|
catch (err) {
|
|
848
841
|
if (err instanceof ConnectError) {
|
|
849
|
-
return c.json({ error: err
|
|
842
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 422);
|
|
850
843
|
}
|
|
851
844
|
throw err;
|
|
852
845
|
}
|
|
@@ -867,7 +860,7 @@ export const runRoutes = new Hono()
|
|
|
867
860
|
}
|
|
868
861
|
catch (err) {
|
|
869
862
|
if (err instanceof ConnectError) {
|
|
870
|
-
return c.json({ error: err
|
|
863
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 422);
|
|
871
864
|
}
|
|
872
865
|
throw err;
|
|
873
866
|
}
|
|
@@ -965,7 +958,7 @@ export const runRoutes = new Hono()
|
|
|
965
958
|
return c.json({
|
|
966
959
|
lists,
|
|
967
960
|
files: await storeFiles(scope),
|
|
968
|
-
connections: (await connectionViews(run.definition.connections ?? [], scope, await projectIdOf(run))).filter((x) => x.account),
|
|
961
|
+
connections: (await connectionViews(run.definition.connections ?? [], scope, await projectIdOf(run), requestLang(c))).filter((x) => x.account),
|
|
969
962
|
keepsSignIns: secrets.some((s) => s.slot === "browser"),
|
|
970
963
|
});
|
|
971
964
|
})
|
|
@@ -1036,7 +1029,7 @@ export const runRoutes = new Hono()
|
|
|
1036
1029
|
}
|
|
1037
1030
|
catch (err) {
|
|
1038
1031
|
if (err instanceof ServiceError) {
|
|
1039
|
-
return c.json(err.toJSON(), err.status);
|
|
1032
|
+
return c.json(err.toJSON(requestLang(c)), err.status);
|
|
1040
1033
|
}
|
|
1041
1034
|
throw err;
|
|
1042
1035
|
}
|
|
@@ -1051,7 +1044,7 @@ export const runRoutes = new Hono()
|
|
|
1051
1044
|
});
|
|
1052
1045
|
function listError(c, err) {
|
|
1053
1046
|
if (err instanceof TableColumnValueError || err instanceof StoreError) {
|
|
1054
|
-
return c.json({ error: err
|
|
1047
|
+
return c.json({ error: errorText(err, requestLang(c)) }, 422);
|
|
1055
1048
|
}
|
|
1056
1049
|
throw err;
|
|
1057
1050
|
}
|
|
@@ -1098,7 +1091,7 @@ export const connectCallback = new Hono().get("/callback", async (c) => {
|
|
|
1098
1091
|
said = { code: "connectFailed" };
|
|
1099
1092
|
}
|
|
1100
1093
|
}
|
|
1101
|
-
const message = noteText(said, requestLang(c));
|
|
1094
|
+
const message = noteText(said, (state && oauthStateLang(state)) || requestLang(c));
|
|
1102
1095
|
return c.html(`<!doctype html><meta charset="utf-8"><title>engenty wizards</title>
|
|
1103
1096
|
<body style="font:16px system-ui;display:grid;place-items:center;height:100vh;margin:0;color:#333">
|
|
1104
1097
|
<p>${message}</p>
|
|
@@ -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,
|