wizards 0.4.4 → 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/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/model-errors.js +8 -3
- package/apps/runtime/dist/models.js +58 -38
- 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/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-BCtnYRj2.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-Cbx3mN2p.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-Bvj3R8g4.js → FlowPreview-BtpAdyt-.js} +1 -1
- package/apps/web/dist/assets/{Harness-D-Z3MbYq.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-CdoAfnI2.js → OnboardingPage-jC6FidpF.js} +1 -1
- package/apps/web/dist/assets/ProjectPage-BYcM1-1H.js +1 -0
- package/apps/web/dist/assets/{PublicRunner-CHwK3X75.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 +4 -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-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/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
|
@@ -53,7 +53,8 @@ HOW YOU WORK
|
|
|
53
53
|
- Finish with one to three sentences to the admin: what you built or changed, and at most one suggestion. If they only asked a question, just answer it.`;
|
|
54
54
|
}
|
|
55
55
|
/** Turns a tool call into the line the admin sees while it runs. */
|
|
56
|
-
function activity(tool, args) {
|
|
56
|
+
function activity(tool, args, lang = "en") {
|
|
57
|
+
const de = lang === "de";
|
|
57
58
|
const host = (u) => {
|
|
58
59
|
try {
|
|
59
60
|
return new URL(String(u)).hostname;
|
|
@@ -65,24 +66,42 @@ function activity(tool, args) {
|
|
|
65
66
|
switch (tool) {
|
|
66
67
|
case "replace_wizard":
|
|
67
68
|
case "edit_wizard":
|
|
68
|
-
return "Baut den Ablauf …";
|
|
69
|
+
return de ? "Baut den Ablauf …" : "Building the flow …";
|
|
69
70
|
case "write_file":
|
|
70
|
-
return args.path
|
|
71
|
+
return args.path
|
|
72
|
+
? de
|
|
73
|
+
? `Schreibt ${String(args.path)} …`
|
|
74
|
+
: `Writing ${String(args.path)} …`
|
|
75
|
+
: de
|
|
76
|
+
? "Schreibt eine Datei …"
|
|
77
|
+
: "Writing a file …";
|
|
71
78
|
case "download_files":
|
|
72
|
-
return "Lädt Daten herunter …";
|
|
79
|
+
return de ? "Lädt Daten herunter …" : "Downloading data …";
|
|
73
80
|
case "web_fetch":
|
|
74
|
-
return args.url
|
|
81
|
+
return args.url
|
|
82
|
+
? de
|
|
83
|
+
? `Liest ${host(args.url)} …`
|
|
84
|
+
: `Reading ${host(args.url)} …`
|
|
85
|
+
: de
|
|
86
|
+
? "Liest im Web …"
|
|
87
|
+
: "Reading the web …";
|
|
75
88
|
case "web_search":
|
|
76
|
-
return "Sucht im Web …";
|
|
89
|
+
return de ? "Sucht im Web …" : "Searching the web …";
|
|
77
90
|
case "check_widget":
|
|
78
|
-
return "Testet das Widget …";
|
|
91
|
+
return de ? "Testet das Widget …" : "Testing the widget …";
|
|
79
92
|
case "run_script":
|
|
80
|
-
return args.output
|
|
93
|
+
return args.output
|
|
94
|
+
? de
|
|
95
|
+
? `Bereitet ${String(args.output)} vor …`
|
|
96
|
+
: `Preparing ${String(args.output)} …`
|
|
97
|
+
: de
|
|
98
|
+
? "Rechnet …"
|
|
99
|
+
: "Calculating …";
|
|
81
100
|
case "list_files":
|
|
82
101
|
case "read_file":
|
|
83
|
-
return "Sieht sich die Dateien an …";
|
|
102
|
+
return de ? "Sieht sich die Dateien an …" : "Looking at the files …";
|
|
84
103
|
case "delete_file":
|
|
85
|
-
return "Räumt Dateien auf …";
|
|
104
|
+
return de ? "Räumt Dateien auf …" : "Tidying up files …";
|
|
86
105
|
default:
|
|
87
106
|
return null;
|
|
88
107
|
}
|
|
@@ -410,7 +429,7 @@ export async function runArchitect(input) {
|
|
|
410
429
|
}
|
|
411
430
|
else if (chunk.type === "tool-call-input-streaming-start") {
|
|
412
431
|
streaming.set(payload.toolCallId, { tool: payload.toolName, args: "", named: false });
|
|
413
|
-
const label = activity(payload.toolName, {});
|
|
432
|
+
const label = activity(payload.toolName, {}, input.lang);
|
|
414
433
|
if (label) {
|
|
415
434
|
input.onActivity?.(label);
|
|
416
435
|
}
|
|
@@ -422,7 +441,7 @@ export async function runArchitect(input) {
|
|
|
422
441
|
const path = s.args.match(/"path"\s*:\s*"([^"]+)"/)?.[1];
|
|
423
442
|
if (path) {
|
|
424
443
|
s.named = true;
|
|
425
|
-
const label = activity(s.tool, { path });
|
|
444
|
+
const label = activity(s.tool, { path }, input.lang);
|
|
426
445
|
if (label) {
|
|
427
446
|
input.onActivity?.(label);
|
|
428
447
|
}
|
|
@@ -430,7 +449,7 @@ export async function runArchitect(input) {
|
|
|
430
449
|
}
|
|
431
450
|
}
|
|
432
451
|
else if (chunk.type === "tool-call") {
|
|
433
|
-
const label = activity(payload.toolName, (payload.args ?? payload.input ?? {}));
|
|
452
|
+
const label = activity(payload.toolName, (payload.args ?? payload.input ?? {}), input.lang);
|
|
434
453
|
if (label) {
|
|
435
454
|
input.onActivity?.(label);
|
|
436
455
|
}
|
|
@@ -38,18 +38,18 @@ const SYSTEM = (wizardId) => [
|
|
|
38
38
|
].join("\n");
|
|
39
39
|
const BUILDING = /edit_wizard|write_wizard|write_file|delete_file|create_wizard/;
|
|
40
40
|
const ACTIVITY = [
|
|
41
|
-
[/get_authoring_guide/, "Liest die Bauanleitung"],
|
|
42
|
-
[/get_starter|list_starters/, "Sieht sich eine Vorlage an"],
|
|
43
|
-
[/get_wizard|list_files|read_file/, "Liest den Wizard"],
|
|
44
|
-
[/write_file|delete_file/, "Schreibt Dateien"],
|
|
45
|
-
[/edit_wizard|write_wizard/, "Baut den Wizard um"],
|
|
46
|
-
[/check_widget|preview/, "Prüft das Widget"],
|
|
47
|
-
[/start_test_run|get_test_run/, "Testet den Wizard"],
|
|
48
|
-
[/find_connectors|import_connector/, "Sucht einen Dienst"],
|
|
41
|
+
[/get_authoring_guide/, { de: "Liest die Bauanleitung", en: "Reading the building guide" }],
|
|
42
|
+
[/get_starter|list_starters/, { de: "Sieht sich eine Vorlage an", en: "Looking at a template" }],
|
|
43
|
+
[/get_wizard|list_files|read_file/, { de: "Liest den Wizard", en: "Reading the wizard" }],
|
|
44
|
+
[/write_file|delete_file/, { de: "Schreibt Dateien", en: "Writing files" }],
|
|
45
|
+
[/edit_wizard|write_wizard/, { de: "Baut den Wizard um", en: "Rebuilding the wizard" }],
|
|
46
|
+
[/check_widget|preview/, { de: "Prüft das Widget", en: "Checking the widget" }],
|
|
47
|
+
[/start_test_run|get_test_run/, { de: "Testet den Wizard", en: "Testing the wizard" }],
|
|
48
|
+
[/find_connectors|import_connector/, { de: "Sucht einen Dienst", en: "Looking for a service" }],
|
|
49
49
|
];
|
|
50
50
|
/** The activity for one tool call, naming the file or service it is about when it says. */
|
|
51
|
-
function activity(name, args) {
|
|
52
|
-
const label = ACTIVITY.find(([re]) => re.test(name))?.[1] ?? "Arbeitet";
|
|
51
|
+
function activity(name, args, lang = "en") {
|
|
52
|
+
const label = ACTIVITY.find(([re]) => re.test(name))?.[1][lang] ?? (lang === "de" ? "Arbeitet" : "Working");
|
|
53
53
|
const about = [args.path, args.query].find((v) => typeof v === "string" && v.trim());
|
|
54
54
|
return about ? `${label}: ${String(about)} …` : `${label} …`;
|
|
55
55
|
}
|
|
@@ -129,7 +129,7 @@ export async function subscriptionTurn(input) {
|
|
|
129
129
|
if (event.type === "stream_event") {
|
|
130
130
|
const e = event.event ?? {};
|
|
131
131
|
if (e.type === "content_block_start" && e.content_block?.type === "tool_use") {
|
|
132
|
-
input.onActivity(activity(String(e.content_block.name ?? ""), {}));
|
|
132
|
+
input.onActivity(activity(String(e.content_block.name ?? ""), {}, input.lang));
|
|
133
133
|
}
|
|
134
134
|
else if (e.type === "content_block_start" && e.content_block?.type === "thinking") {
|
|
135
135
|
thinking = "";
|
|
@@ -156,7 +156,7 @@ export async function subscriptionTurn(input) {
|
|
|
156
156
|
changed = true;
|
|
157
157
|
input.onBuilding();
|
|
158
158
|
}
|
|
159
|
-
input.onActivity(activity(name, block.input ?? {}));
|
|
159
|
+
input.onActivity(activity(name, block.input ?? {}, input.lang));
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
}
|
package/apps/runtime/dist/app.js
CHANGED
|
@@ -23,6 +23,7 @@ import { PLUGIN_NAME, pluginArchive, pluginMarketplace } from "./plugin.js";
|
|
|
23
23
|
import { apiRoutes } from "./routes/api.js";
|
|
24
24
|
import { browserReport, localDebugRoutes } from "./routes/debug.js";
|
|
25
25
|
import { internalRoutes } from "./routes/internal.js";
|
|
26
|
+
import { inLang, requestLang } from "./routes/lang.js";
|
|
26
27
|
import { marketplaceStudio } from "./routes/marketplace.js";
|
|
27
28
|
import { publicPluginRoutes } from "./routes/plugin-public.js";
|
|
28
29
|
import { pluginRoutes } from "./routes/plugins.js";
|
|
@@ -40,16 +41,16 @@ const app = new Hono();
|
|
|
40
41
|
const { serves } = env;
|
|
41
42
|
app.onError((err, c) => {
|
|
42
43
|
if (err instanceof ServiceError) {
|
|
43
|
-
return c.json(err.toJSON(), err.status);
|
|
44
|
+
return c.json(err.toJSON(requestLang(c)), err.status);
|
|
44
45
|
}
|
|
45
46
|
if (err instanceof ZodError) {
|
|
46
|
-
return c.json({ error: "Ungültige Eingabe", issues: err.issues }, 400);
|
|
47
|
+
return c.json({ error: inLang(c, "Ungültige Eingabe", "Invalid input"), issues: err.issues }, 400);
|
|
47
48
|
}
|
|
48
49
|
if (err instanceof HTTPException) {
|
|
49
50
|
return err.getResponse();
|
|
50
51
|
}
|
|
51
52
|
console.error(err);
|
|
52
|
-
return c.json({ error: "Interner Fehler" }, 500);
|
|
53
|
+
return c.json({ error: inLang(c, "Interner Fehler", "Internal error") }, 500);
|
|
53
54
|
});
|
|
54
55
|
// --- who may talk to this server at all ---------------------------------------------
|
|
55
56
|
// A runtime that runs alone listens on the loopback interface. A web page in the person's
|
|
@@ -203,7 +204,10 @@ if (serves.studio) {
|
|
|
203
204
|
return c.json({ error: "unauthorized" }, 401);
|
|
204
205
|
}
|
|
205
206
|
if ((await tenantStatus(user.tenantId)) === "suspended") {
|
|
206
|
-
return c.json({
|
|
207
|
+
return c.json({
|
|
208
|
+
error: inLang(c, "Dieses Konto ist gesperrt.", "This account is suspended."),
|
|
209
|
+
code: "suspended",
|
|
210
|
+
}, 403);
|
|
207
211
|
}
|
|
208
212
|
c.set("user", user);
|
|
209
213
|
return withTenant(user.tenantId, () => asRole(user.role, next));
|
|
@@ -220,7 +224,7 @@ if (serves.runner) {
|
|
|
220
224
|
// answers are text; an upload is a photo, a recording or a clip from a phone (routes/delivery.ts).
|
|
221
225
|
const tooLarge = (max) => bodyLimit({
|
|
222
226
|
maxSize: max,
|
|
223
|
-
onError: (c) => c.json({ error: "Das ist zu groß.", code: "too_large" }, 413),
|
|
227
|
+
onError: (c) => c.json({ error: inLang(c, "Das ist zu groß.", "That is too large."), code: "too_large" }, 413),
|
|
224
228
|
});
|
|
225
229
|
const answers = tooLarge(4 * 1024 * 1024);
|
|
226
230
|
const upload = tooLarge(48 * 1024 * 1024);
|
|
@@ -8,7 +8,7 @@ import { db, schema } from "../db/client.js";
|
|
|
8
8
|
import { env } from "../env.js";
|
|
9
9
|
import { detectHarnesses } from "../harness/index.js";
|
|
10
10
|
import { managed, publicPlans, tenantInfo, tenantUsage } from "../manage.js";
|
|
11
|
-
import { classWays } from "../models.js";
|
|
11
|
+
import { classWays, waysIn } from "../models.js";
|
|
12
12
|
import { pluginToolsOf } from "../plugins/registry.js";
|
|
13
13
|
import { earlierChats, readChat, searchChats } from "../services/assistant-chats.js";
|
|
14
14
|
import { asLang, marketplaceDetail, searchMarketplace } from "../services/marketplace.js";
|
|
@@ -298,7 +298,7 @@ export function knowsTools(ctx) {
|
|
|
298
298
|
execute: () => attempt(async () => {
|
|
299
299
|
ctx.doing(ctx.lang === "de" ? "Sieht die Modelle nach …" : "Looking up the models …");
|
|
300
300
|
return {
|
|
301
|
-
classes: await classWays(),
|
|
301
|
+
classes: waysIn(await classWays(), "en"),
|
|
302
302
|
clients: managed
|
|
303
303
|
? []
|
|
304
304
|
: (await detectHarnesses()).map((h) => ({
|
|
@@ -4,29 +4,30 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export const EXAMPLE_WIZARD = {
|
|
6
6
|
version: 1,
|
|
7
|
-
title: "X
|
|
8
|
-
description: "
|
|
7
|
+
title: "X post with an image",
|
|
8
|
+
description: "Turns a topic into a finished post with an image.",
|
|
9
9
|
avatar: "flame",
|
|
10
|
-
|
|
10
|
+
lang: "en",
|
|
11
|
+
intro: "Tell me briefly what it is about – I write the post and draw the image for it.",
|
|
11
12
|
steps: [
|
|
12
13
|
{
|
|
13
14
|
id: "about",
|
|
14
15
|
type: "page",
|
|
15
|
-
title: "
|
|
16
|
+
title: "What is it about?",
|
|
16
17
|
fields: [
|
|
17
18
|
{
|
|
18
19
|
id: "topic",
|
|
19
|
-
label: "
|
|
20
|
+
label: "Topic or occasion",
|
|
20
21
|
kind: "textarea",
|
|
21
22
|
required: true,
|
|
22
|
-
placeholder: "
|
|
23
|
+
placeholder: "e.g. We open our new studio in Vienna on Friday",
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
id: "tone",
|
|
26
|
-
label: "
|
|
27
|
+
label: "Tone",
|
|
27
28
|
kind: "select",
|
|
28
|
-
options: ["
|
|
29
|
-
default: "
|
|
29
|
+
options: ["Casual", "Professional", "Funny", "Inspiring"],
|
|
30
|
+
default: "Casual",
|
|
30
31
|
},
|
|
31
32
|
{ id: "link", label: "Link (optional)", kind: "url" },
|
|
32
33
|
],
|
|
@@ -34,9 +35,9 @@ export const EXAMPLE_WIZARD = {
|
|
|
34
35
|
{
|
|
35
36
|
id: "post",
|
|
36
37
|
type: "agent",
|
|
37
|
-
title: "
|
|
38
|
-
working: "
|
|
39
|
-
instructions: "
|
|
38
|
+
title: "Write the post",
|
|
39
|
+
working: "Writing the post …",
|
|
40
|
+
instructions: "Write a post for X (Twitter) on the topic: {{topic}}. Tone: {{tone}}. At most 270 characters including the link, and at most two hashtags. If a link is given ({{link}}), put it at the end. A strong first sentence. Output only the post.",
|
|
40
41
|
tools: [],
|
|
41
42
|
output: { format: "text" },
|
|
42
43
|
model: "standard",
|
|
@@ -44,17 +45,17 @@ export const EXAMPLE_WIZARD = {
|
|
|
44
45
|
{
|
|
45
46
|
id: "visual",
|
|
46
47
|
type: "generate",
|
|
47
|
-
title: "
|
|
48
|
-
working: "
|
|
48
|
+
title: "Draw the image",
|
|
49
|
+
working: "Drawing the image …",
|
|
49
50
|
asset: "image",
|
|
50
|
-
prompt: "
|
|
51
|
+
prompt: "A striking image to go with this post: {{steps.post}}. Topic: {{topic}}. No text in the image.",
|
|
51
52
|
options: { aspectRatio: "16:9", style: "modern editorial, clean, high contrast" },
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
id: "check",
|
|
55
56
|
type: "review",
|
|
56
|
-
title: "
|
|
57
|
-
description: "
|
|
57
|
+
title: "Does this work?",
|
|
58
|
+
description: "You can change the text directly or ask for a new variant.",
|
|
58
59
|
show: ["post", "visual"],
|
|
59
60
|
edit: true,
|
|
60
61
|
regenerate: true,
|
|
@@ -62,11 +63,11 @@ export const EXAMPLE_WIZARD = {
|
|
|
62
63
|
{
|
|
63
64
|
id: "done",
|
|
64
65
|
type: "result",
|
|
65
|
-
title: "
|
|
66
|
-
message: "
|
|
66
|
+
title: "Ready to post",
|
|
67
|
+
message: "Copy the text and download the image.",
|
|
67
68
|
deliverables: [
|
|
68
69
|
{ from: "post", label: "Post", formats: ["txt", "md"] },
|
|
69
|
-
{ from: "visual", label: "
|
|
70
|
+
{ from: "visual", label: "Image", formats: ["png"] },
|
|
70
71
|
],
|
|
71
72
|
},
|
|
72
73
|
],
|
|
@@ -13,6 +13,7 @@ type Wizard = {
|
|
|
13
13
|
description: string // one sentence
|
|
14
14
|
avatar: "round"|"drop"|"dome"|"flame"|"oval"|"bean"|"pebble"|"sprout"|"tower"|"wedge" // the engenty mascot shown to end users
|
|
15
15
|
intro?: string // one friendly sentence above the first page
|
|
16
|
+
lang?: "en"|"de" // the language the wizard's texts are in — ALWAYS set it to the language you write them in
|
|
16
17
|
lists?: List[] // tabular data the wizard KEEPS between runs — separately for each person who runs it, or for all with "shared"
|
|
17
18
|
connections?: Connection[] // accounts the PERSON connects during a run (their mailbox); kept for their next run
|
|
18
19
|
steps: Step[] // run top to bottom; the LAST step is always a "result"
|
|
@@ -148,7 +149,8 @@ type AgentStep = {
|
|
|
148
149
|
working?: string // shown while it runs ("Recherchiert im Web …")
|
|
149
150
|
}
|
|
150
151
|
// Every agent step also has, without listing them: list_read / list_write (the wizard's lists), files_list,
|
|
151
|
-
// read_document (PDF, scans and photos, Word, Excel, CSV, saved mails) and scan_documents (invoice / receipt fields, many at once)
|
|
152
|
+
// read_document (PDF, scans and photos, Word, Excel, CSV, saved mails) and scan_documents (invoice / receipt fields, many at once)
|
|
153
|
+
// — on an installed client only where there is something to read: tools or connections, uploads, lists or kept files;
|
|
152
154
|
// and where the space has Wissen: project_search (a question, "where" on its typed Kategorien, hits checked for relevance),
|
|
153
155
|
// project_list (by Kategorien), page_read / table_read / project_document (an item whole, by its path).
|
|
154
156
|
// "pages" brings: page_read / page_write — Markdown pages of the space the wizard keeps for every run (notes, a log
|
|
@@ -249,7 +251,7 @@ Templates (in instructions/prompt): {{fieldId}} · {{steps.stepId}} (whole outpu
|
|
|
249
251
|
A template may only use fields asked and steps run EARLIER.`;
|
|
250
252
|
export const PRINCIPLES = `
|
|
251
253
|
How good wizards look:
|
|
252
|
-
- For non-technical end users. Plain words, friendly titles phrased as questions ("
|
|
254
|
+
- For non-technical end users. Plain words, friendly titles phrased as questions ("What is the post for?").
|
|
253
255
|
- A page whose fields depend on an earlier answer uses "when" on its fields, never one copy of the page per answer and a branch to each copy. Choices that depend on what an earlier step found use "optionsFrom", never one page per possible result.
|
|
254
256
|
- Few pages, 1–5 fields each, one theme per page. Only ask what the result really needs; prefer selects with sensible defaults.
|
|
255
257
|
- Put a review step before anything expensive (video) and before the final result, so people can correct or regenerate.
|
|
@@ -240,12 +240,12 @@ export const imapConnector = {
|
|
|
240
240
|
kind: "api_key",
|
|
241
241
|
apiKey: {
|
|
242
242
|
fields: [
|
|
243
|
-
{ key: "email", label: "
|
|
244
|
-
{ key: "password", label: "
|
|
243
|
+
{ key: "email", label: "Email address", placeholder: "name@company.com" },
|
|
244
|
+
{ key: "password", label: "Password or app password", secret: true },
|
|
245
245
|
{
|
|
246
246
|
key: "host",
|
|
247
|
-
label: "IMAP
|
|
248
|
-
placeholder: "
|
|
247
|
+
label: "IMAP server",
|
|
248
|
+
placeholder: "leave empty for Gmail, GMX, iCloud …",
|
|
249
249
|
required: false,
|
|
250
250
|
},
|
|
251
251
|
{ key: "port", label: "Port", placeholder: "993", required: false },
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
1
2
|
import { eq } from "drizzle-orm";
|
|
2
3
|
import { db, schema } from "../db/client.js";
|
|
3
4
|
import { buildAuthorizationUrl, createOAuth2Pkce, exchangeAuthorizationCode, refreshAccessToken, } from "../engenty/connections-sdk/oauth2.js";
|
|
4
5
|
import { scopesForGroups, } from "../engenty/connections-sdk/types.js";
|
|
5
6
|
import { env } from "../env.js";
|
|
7
|
+
import { NotedError } from "../model-errors.js";
|
|
6
8
|
import { seal, unseal } from "../secrets/crypto.js";
|
|
7
9
|
import { deleteSecret, getSecret, putSecret } from "../store/index.js";
|
|
8
10
|
import { connectorFetch, MAIL_CONNECTORS, resolveEnv, usable, viaAccount } from "./builtin.js";
|
|
@@ -11,7 +13,18 @@ import { AccountConnectError, redeemTicket, refreshThroughAccount, startThroughA
|
|
|
11
13
|
/** The connectors a person can pick for each kind of connection. */
|
|
12
14
|
const CONNECTORS = { mail: MAIL_CONNECTORS };
|
|
13
15
|
const HINTS = {
|
|
14
|
-
imap:
|
|
16
|
+
imap: {
|
|
17
|
+
de: "Bei Gmail, iCloud, GMX und web.de brauchst du ein App-Passwort (in den Sicherheitseinstellungen deines Kontos), nicht dein normales Passwort.",
|
|
18
|
+
en: "Gmail, iCloud, GMX and web.de need an app password (in your account's security settings), not your normal password.",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
/** The built-in connectors' fields in German; their definitions say them in English. */
|
|
22
|
+
const GERMAN_FIELDS = {
|
|
23
|
+
imap: {
|
|
24
|
+
email: { label: "E-Mail-Adresse", placeholder: "name@firma.at" },
|
|
25
|
+
password: { label: "Passwort oder App-Passwort" },
|
|
26
|
+
host: { label: "IMAP-Server", placeholder: "leer lassen bei Gmail, GMX, iCloud …" },
|
|
27
|
+
},
|
|
15
28
|
};
|
|
16
29
|
/**
|
|
17
30
|
* What a connection asks the provider for: what reading needs, plus what each action the wizard
|
|
@@ -43,7 +56,7 @@ async function candidates(connection, projectId) {
|
|
|
43
56
|
}
|
|
44
57
|
return connection.kind ? CONNECTORS[connection.kind] : [];
|
|
45
58
|
}
|
|
46
|
-
export async function connectorOptions(connection, projectId) {
|
|
59
|
+
export async function connectorOptions(connection, projectId, lang = "en") {
|
|
47
60
|
const out = [];
|
|
48
61
|
for (const c of await candidates(connection, projectId)) {
|
|
49
62
|
if (!(await usable(c))) {
|
|
@@ -54,8 +67,10 @@ export async function connectorOptions(connection, projectId) {
|
|
|
54
67
|
id: c.id,
|
|
55
68
|
name: c.name,
|
|
56
69
|
auth: "api_key",
|
|
57
|
-
fields:
|
|
58
|
-
|
|
70
|
+
fields: lang === "de"
|
|
71
|
+
? c.auth.apiKey.fields.map((f) => ({ ...f, ...GERMAN_FIELDS[c.id]?.[f.key] }))
|
|
72
|
+
: c.auth.apiKey.fields,
|
|
73
|
+
hint: HINTS[c.id]?.[lang],
|
|
59
74
|
}
|
|
60
75
|
: { id: c.id, name: c.name, auth: "oauth2" });
|
|
61
76
|
}
|
|
@@ -68,7 +83,9 @@ async function connectorFor(connection, connectorId, projectId) {
|
|
|
68
83
|
}
|
|
69
84
|
return connector;
|
|
70
85
|
}
|
|
71
|
-
export async function connectionViews(connections, scope, projectId
|
|
86
|
+
export async function connectionViews(connections, scope, projectId,
|
|
87
|
+
/** The language the person reads the connectors' fields in. */
|
|
88
|
+
lang = "en") {
|
|
72
89
|
return Promise.all(connections.map(async (c) => {
|
|
73
90
|
const stored = await getSecret(scope, slot(c.id));
|
|
74
91
|
const only = c.connector ? (await candidates(c, projectId))[0] : undefined;
|
|
@@ -87,11 +104,11 @@ export async function connectionViews(connections, scope, projectId) {
|
|
|
87
104
|
: only && open(only)
|
|
88
105
|
? { connector: only.id, label: only.name, connectedAt: new Date(0).toISOString() }
|
|
89
106
|
: null,
|
|
90
|
-
connectors: await connectorOptions(c, projectId),
|
|
107
|
+
connectors: await connectorOptions(c, projectId, lang),
|
|
91
108
|
};
|
|
92
109
|
}));
|
|
93
110
|
}
|
|
94
|
-
export async function startOAuth(scope, projectId, runId, connection, connectorId) {
|
|
111
|
+
export async function startOAuth(scope, projectId, runId, connection, connectorId, lang) {
|
|
95
112
|
const connector = await connectorFor(connection, connectorId, projectId);
|
|
96
113
|
if (connector.auth.kind !== "oauth2") {
|
|
97
114
|
throw new Error(`${connector.name} is not connected by signing in.`);
|
|
@@ -106,6 +123,7 @@ export async function startOAuth(scope, projectId, runId, connection, connectorI
|
|
|
106
123
|
connectorId,
|
|
107
124
|
verifier: "",
|
|
108
125
|
exp: Date.now() + 15 * 60_000,
|
|
126
|
+
lang,
|
|
109
127
|
};
|
|
110
128
|
try {
|
|
111
129
|
return await startThroughAccount(scopesFor(connection, connector), seal(state), connectRedirectUri());
|
|
@@ -125,6 +143,7 @@ export async function startOAuth(scope, projectId, runId, connection, connectorI
|
|
|
125
143
|
connectorId,
|
|
126
144
|
verifier: pkce.codeVerifier,
|
|
127
145
|
exp: Date.now() + 15 * 60_000,
|
|
146
|
+
lang,
|
|
128
147
|
};
|
|
129
148
|
return buildAuthorizationUrl({
|
|
130
149
|
connector,
|
|
@@ -135,6 +154,11 @@ export async function startOAuth(scope, projectId, runId, connection, connectorI
|
|
|
135
154
|
resolveEnv,
|
|
136
155
|
});
|
|
137
156
|
}
|
|
157
|
+
/** The language a sign-in was started in, a wizard's or a plugin's; null where it names none. */
|
|
158
|
+
export function oauthStateLang(rawState) {
|
|
159
|
+
const lang = unseal(rawState)?.lang;
|
|
160
|
+
return lang === "de" || lang === "en" ? lang : null;
|
|
161
|
+
}
|
|
138
162
|
/** The run an OAuth callback belongs to; its tenant is looked up before the state is used. */
|
|
139
163
|
export function oauthStateRun(rawState) {
|
|
140
164
|
return unseal(rawState)?.runId ?? null;
|
|
@@ -146,7 +170,7 @@ export function oauthStateRun(rawState) {
|
|
|
146
170
|
export async function finishOAuth(answer, rawState) {
|
|
147
171
|
const state = unseal(rawState);
|
|
148
172
|
if (!state || state.exp < Date.now()) {
|
|
149
|
-
throw new Error("
|
|
173
|
+
throw new Error("The sign-in has expired. Please connect again.");
|
|
150
174
|
}
|
|
151
175
|
const run = await db.query.run.findFirst({ where: eq(schema.run.id, state.runId) });
|
|
152
176
|
const connection = run?.definition.connections?.find((c) => c.id === state.connectionId);
|
|
@@ -190,7 +214,7 @@ export async function connectWithCredentials(scope, projectId, connection, conne
|
|
|
190
214
|
for (const field of connector.auth.apiKey.fields) {
|
|
191
215
|
const value = String(values[field.key] ?? "").trim();
|
|
192
216
|
if (!value && field.required !== false) {
|
|
193
|
-
throw new ConnectError(`„${field.label}“ fehlt.`);
|
|
217
|
+
throw new ConnectError(bilingual(`„${field.label}“ fehlt.`, `“${field.label}” is missing.`));
|
|
194
218
|
}
|
|
195
219
|
if (value) {
|
|
196
220
|
credentials[field.key] = value.slice(0, 500);
|
|
@@ -213,22 +237,23 @@ export async function connectWithCredentials(scope, projectId, connection, conne
|
|
|
213
237
|
return account.label;
|
|
214
238
|
}
|
|
215
239
|
/** A connect attempt the person can fix (wrong password, unknown server). */
|
|
216
|
-
export class ConnectError extends
|
|
240
|
+
export class ConnectError extends NotedError {
|
|
217
241
|
}
|
|
218
242
|
function connectFailure(err) {
|
|
219
243
|
const e = err;
|
|
220
244
|
const host = e.imapHost ? ` (${e.imapHost})` : "";
|
|
221
245
|
if (e.authenticationFailed) {
|
|
222
|
-
const
|
|
223
|
-
return `Anmeldung abgelehnt${host}. Stimmen Adresse und (App-)Passwort? Bei Gmail und Google Workspace braucht es ein App-Passwort und aktiviertes IMAP.${server}
|
|
246
|
+
const answer = e.responseText?.slice(0, 160);
|
|
247
|
+
return bilingual(`Anmeldung abgelehnt${host}. Stimmen Adresse und (App-)Passwort? Bei Gmail und Google Workspace braucht es ein App-Passwort und aktiviertes IMAP.${answer ? ` Antwort des Servers: ${answer}` : ""}`, `Sign-in refused${host}. Are the address and (app) password right? Gmail and Google Workspace need an app password and IMAP switched on.${answer ? ` The server answered: ${answer}` : ""}`);
|
|
224
248
|
}
|
|
225
249
|
if (e.code === "ENOTFOUND") {
|
|
226
|
-
return `Den Mail-Server${host} gibt es nicht. Bitte den IMAP-Server eintragen, bei Gmail und Google Workspace imap.gmail.com
|
|
250
|
+
return bilingual(`Den Mail-Server${host} gibt es nicht. Bitte den IMAP-Server eintragen, bei Gmail und Google Workspace imap.gmail.com.`, `The mail server${host} does not exist. Please enter the IMAP server; for Gmail and Google Workspace it is imap.gmail.com.`);
|
|
227
251
|
}
|
|
228
252
|
if (e.code === "ETIMEDOUT" || e.code === "ETIMEOUT" || e.code === "ECONNREFUSED") {
|
|
229
|
-
return `Der Mail-Server${host} antwortet nicht. Bitte IMAP-Server und Port prüfen
|
|
253
|
+
return bilingual(`Der Mail-Server${host} antwortet nicht. Bitte IMAP-Server und Port prüfen.`, `The mail server${host} does not answer. Please check the IMAP server and port.`);
|
|
230
254
|
}
|
|
231
|
-
|
|
255
|
+
const detail = String(e.message ?? err).slice(0, 200);
|
|
256
|
+
return bilingual(`Verbinden hat nicht geklappt${host}: ${detail}`, `Connecting did not work${host}: ${detail}`);
|
|
232
257
|
}
|
|
233
258
|
export function disconnect(scope, connectionId) {
|
|
234
259
|
return deleteSecret(scope, slot(connectionId));
|
|
@@ -101,7 +101,7 @@ export function pluginStateTenant(rawState) {
|
|
|
101
101
|
export async function finishPluginOAuth(answer, rawState) {
|
|
102
102
|
const state = unseal(rawState);
|
|
103
103
|
if (!state?.plugin || state.exp < Date.now()) {
|
|
104
|
-
throw new Error("
|
|
104
|
+
throw new Error("The sign-in has expired. Please connect again.");
|
|
105
105
|
}
|
|
106
106
|
const connector = oauthConnector(state.connectorId);
|
|
107
107
|
if (connector.auth.kind !== "oauth2") {
|
|
@@ -222,7 +222,7 @@ export function pluginConnections(plugin) {
|
|
|
222
222
|
}
|
|
223
223
|
return out;
|
|
224
224
|
},
|
|
225
|
-
async start({ space, connector: connectorId, actions }) {
|
|
225
|
+
async start({ space, connector: connectorId, actions, lang }) {
|
|
226
226
|
const connector = oauthConnector(connectorId);
|
|
227
227
|
if (connector.auth.kind !== "oauth2") {
|
|
228
228
|
throw new Error("not an oauth connector");
|
|
@@ -236,6 +236,7 @@ export function pluginConnections(plugin) {
|
|
|
236
236
|
connectorId,
|
|
237
237
|
actions: [...new Set(actions)],
|
|
238
238
|
exp: Date.now() + 15 * 60_000,
|
|
239
|
+
lang,
|
|
239
240
|
};
|
|
240
241
|
if (await viaAccount(connector)) {
|
|
241
242
|
// The Manage-App keeps the PKCE verifier; ours stays empty.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
1
2
|
import { accountToken } from "../auth/account.js";
|
|
2
3
|
import { env } from "../env.js";
|
|
4
|
+
import { NotedError } from "../model-errors.js";
|
|
3
5
|
/*
|
|
4
6
|
* Google accounts on a local install without an OAuth client of its own: the Manage-App of the
|
|
5
7
|
* linked account runs the consent at Google with its client and hands the tokens over, and
|
|
@@ -7,7 +9,7 @@ import { env } from "../env.js";
|
|
|
7
9
|
*/
|
|
8
10
|
const base = () => `${env.local.accountUrl}/v1/connect`;
|
|
9
11
|
/** Something the person can mend or retry; the message is for them. */
|
|
10
|
-
export class AccountConnectError extends
|
|
12
|
+
export class AccountConnectError extends NotedError {
|
|
11
13
|
}
|
|
12
14
|
let offered = null;
|
|
13
15
|
/** Whether the Manage-App connects Google accounts; asked at most every ten minutes. */
|
|
@@ -24,7 +26,7 @@ export async function googleThroughAccount() {
|
|
|
24
26
|
async function call(path, body) {
|
|
25
27
|
const token = await accountToken();
|
|
26
28
|
if (!token) {
|
|
27
|
-
throw new AccountConnectError("Verbinde zuerst dein engenty-Konto (Einstellungen → Konto), dann lässt sich Google hier verbinden.");
|
|
29
|
+
throw new AccountConnectError(bilingual("Verbinde zuerst dein engenty-Konto (Einstellungen → Konto), dann lässt sich Google hier verbinden.", "Connect your engenty account first (Settings → Account); then Google can be connected here."));
|
|
28
30
|
}
|
|
29
31
|
const res = await fetch(`${base()}${path}`, {
|
|
30
32
|
method: "POST",
|
|
@@ -33,15 +35,15 @@ async function call(path, body) {
|
|
|
33
35
|
signal: AbortSignal.timeout(20_000),
|
|
34
36
|
}).catch(() => null);
|
|
35
37
|
if (!res) {
|
|
36
|
-
throw new AccountConnectError("Dein engenty-Konto ist gerade nicht erreichbar. Bitte später noch einmal.");
|
|
38
|
+
throw new AccountConnectError(bilingual("Dein engenty-Konto ist gerade nicht erreichbar. Bitte später noch einmal.", "Your engenty account cannot be reached right now. Please try again later."));
|
|
37
39
|
}
|
|
38
40
|
const data = (await res.json().catch(() => ({})));
|
|
39
41
|
if (!res.ok) {
|
|
40
42
|
throw new AccountConnectError(data.code === "invalid_grant"
|
|
41
|
-
? "Google nimmt diese Verbindung nicht mehr an. Bitte das Konto neu verbinden."
|
|
43
|
+
? bilingual("Google nimmt diese Verbindung nicht mehr an. Bitte das Konto neu verbinden.", "Google no longer accepts this connection. Please connect the account again.")
|
|
42
44
|
: data.code === "not_configured"
|
|
43
|
-
? "Google lässt sich über dein engenty-Konto gerade nicht verbinden."
|
|
44
|
-
: `Verbinden über dein engenty-Konto hat nicht geklappt (${data.code ?? res.status}).`);
|
|
45
|
+
? bilingual("Google lässt sich über dein engenty-Konto gerade nicht verbinden.", "Google cannot be connected through your engenty account right now.")
|
|
46
|
+
: bilingual(`Verbinden über dein engenty-Konto hat nicht geklappt (${data.code ?? res.status}).`, `Connecting through your engenty account did not work (${data.code ?? res.status}).`));
|
|
45
47
|
}
|
|
46
48
|
return data;
|
|
47
49
|
}
|
|
@@ -29,7 +29,7 @@ const { events } = await server.connections.call<{ events: unknown[] }>(account.
|
|
|
29
29
|
| `server.connections.` | Does |
|
|
30
30
|
|---|---|
|
|
31
31
|
| `available(ids)` | Of these connector ids, the ones a person can connect here now: `{ id, name, auth }`, and for `auth: "api_key"` the `fields` to ask for (`key`, `label`, `placeholder`, `required`, `secret`). An OAuth connector is offered once its client is set up (`GOOGLE_OAUTH_CLIENT_ID` …), and on a local install Google also through the linked account; a connector that takes keys (HubSpot, S3) always |
|
|
32
|
-
| `start({ space, connector, actions })` | An OAuth connector: the provider's sign-in page. Only the rights `actions` need are asked for |
|
|
32
|
+
| `start({ space, connector, actions, lang? })` | An OAuth connector: the provider's sign-in page. Only the rights `actions` need are asked for. `lang` (`"en"` or `"de"`) is the language of the page the person comes back to; without it, their browser's |
|
|
33
33
|
| `connect({ space, connector, actions, credentials })` | A connector that takes keys: the keys by field key, tried once at the provider, then kept sealed. Keys the provider refuses throw an error with the status `400` and the code `credentials`, whose message says what was wrong. Answers the connection |
|
|
34
34
|
| `list(space)`, `get(id)` | What is connected: `{ id, space, connector, label, actions, createdAt }`, the newest first. `label` is the account as the provider names it |
|
|
35
35
|
| `call(id, action, input)` | Runs one of the connector's actions with the account. A fresh token is fetched first when the old one runs out |
|
|
@@ -27,7 +27,7 @@ server.registerStarter({
|
|
|
27
27
|
| `id` | Lower case, digits and `-`, starting with a letter, at most 40 characters. Unique in the plugin |
|
|
28
28
|
| `title` | What the list of templates calls it |
|
|
29
29
|
| `description` | What it makes, in one or two sentences. Shown under the title |
|
|
30
|
-
| `language` | `"de"` or `"en"`, the language its texts are in. Default: `"
|
|
30
|
+
| `language` | `"de"` or `"en"`, the language its texts are in. Default: `"en"` |
|
|
31
31
|
| `definition` | A complete wizard definition: the JSON the studio keeps and `get_authoring_guide` describes |
|
|
32
32
|
| `files` | Files of the wizard's workspace by path: widgets, sample data. A string is written as UTF-8, a `Uint8Array` as it is |
|
|
33
33
|
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
5
6
|
import { generateText, Output } from "ai";
|
|
6
7
|
import PostalMime from "postal-mime";
|
|
7
8
|
import { z } from "zod";
|
|
@@ -11,9 +12,10 @@ import { splitMarkdownByPageBreaks } from "../engenty/doc-converter/page-break.j
|
|
|
11
12
|
import { LocalProvider } from "../engenty/doc-converter/providers/local/index.js";
|
|
12
13
|
import { invoiceSchema, receiptSchema } from "../engenty/document-scanner/schemas/index.js";
|
|
13
14
|
import { env } from "../env.js";
|
|
15
|
+
import { NotedError } from "../model-errors.js";
|
|
14
16
|
import { costOf, textModel } from "../models.js";
|
|
15
17
|
import { htmlToMarkdown } from "../render/convert.js";
|
|
16
|
-
export class UnreadableDocument extends
|
|
18
|
+
export class UnreadableDocument extends NotedError {
|
|
17
19
|
}
|
|
18
20
|
const DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
19
21
|
const XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
@@ -168,7 +170,7 @@ async function read(input, opts) {
|
|
|
168
170
|
const vision = new VisionProvider(opts.charge, opts.signal, opts.call);
|
|
169
171
|
if (mime.startsWith("image/")) {
|
|
170
172
|
if (!vision.canConvert(mime)) {
|
|
171
|
-
throw new UnreadableDocument(`Bilder vom Typ ${mime} kann ich nicht lesen (PNG, JPEG, WebP gehen).`);
|
|
173
|
+
throw new UnreadableDocument(bilingual(`Bilder vom Typ ${mime} kann ich nicht lesen (PNG, JPEG, WebP gehen).`, `I cannot read images of type ${mime} (PNG, JPEG and WebP work).`));
|
|
172
174
|
}
|
|
173
175
|
const result = await vision.convert(data, name, mime);
|
|
174
176
|
return { markdown: result.markdown, pages: 1, via: "vision" };
|
|
@@ -212,7 +214,7 @@ async function read(input, opts) {
|
|
|
212
214
|
if (mime === "application/json" || mime.startsWith("text/")) {
|
|
213
215
|
return { markdown: text(), pages: null, via: "text" };
|
|
214
216
|
}
|
|
215
|
-
throw new UnreadableDocument(`„${name}“ (${mime}) kann ich nicht lesen.`);
|
|
217
|
+
throw new UnreadableDocument(bilingual(`„${name}“ (${mime}) kann ich nicht lesen.`, `I cannot read “${name}” (${mime}).`));
|
|
216
218
|
}
|
|
217
219
|
// --- structured extraction -----------------------------------------------------
|
|
218
220
|
/** The scanner's schemas say nothing about the currency; SaaS invoices come in several. */
|