wizards 0.5.12 → 0.6.0
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/authoring/guide.js +63 -7
- package/apps/runtime/dist/credits/estimate.js +1 -1
- package/apps/runtime/dist/db/control-schema.js +10 -0
- package/apps/runtime/dist/db/migrations/control/0010_tan_snowbird.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0010_snapshot.json +600 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +7 -0
- package/apps/runtime/dist/db/migrations/tenant/0016_even_trish_tilby.sql +12 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0016_snapshot.json +3326 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +7 -0
- package/apps/runtime/dist/db/schema.js +20 -0
- package/apps/runtime/dist/engine/requirements.js +1 -1
- package/apps/runtime/dist/engine/runner.js +72 -7
- package/apps/runtime/dist/engine/steps.js +169 -126
- package/apps/runtime/dist/model-errors.js +20 -7
- package/apps/runtime/dist/render/downloads.js +16 -3
- package/apps/runtime/dist/render/shaped.js +137 -0
- package/apps/runtime/dist/routes/runs.js +228 -25
- package/apps/runtime/dist/routes/studio.js +41 -3
- package/apps/runtime/dist/services/brand.js +2 -1
- package/apps/runtime/dist/services/cloud.js +1 -0
- package/apps/runtime/dist/services/spaces.js +1 -0
- package/apps/runtime/dist/services/translations.js +303 -0
- package/apps/runtime/dist/services/wizards.js +3 -1
- package/apps/runtime/dist/texts.js +2 -0
- package/apps/runtime/dist/widgets/runtime.js +25 -2
- package/apps/runtime/dist/widgets/step.js +2 -0
- package/apps/web/dist/assets/AboutDialog-xOyf_juP.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-BZ8i3wt_.js → ChangelogDialog-vNG0Smoo.js} +1 -1
- package/apps/web/dist/assets/ChatView-BrDkurRe.js +10 -0
- package/apps/web/dist/assets/EditorPage-97U-JYKn.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-DG23aI89.js → FlowPreview-DA0sHtr7.js} +1 -1
- package/apps/web/dist/assets/{Harness-CFh04TZ3.js → Harness-Y9E7o83r.js} +2 -2
- package/apps/web/dist/assets/{NewWizardChannels-Bmo5yRCv.js → NewWizardChannels-D6v1iDOo.js} +1 -1
- package/apps/web/dist/assets/NewWizardPage-BzaAPMUK.js +7 -0
- package/apps/web/dist/assets/OnboardingPage-9xZAkhuO.js +1 -0
- package/apps/web/dist/assets/ProjectPage-dQ7wwxD2.js +1 -0
- package/apps/web/dist/assets/PublicRunner-CKa8M0Qd.js +1 -0
- package/apps/web/dist/assets/{ReadOnly-C_jD7KjW.js → ReadOnly-Bqk14yPD.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-DsfZUojZ.js +4 -0
- package/apps/web/dist/assets/RunnerView-D0JQ1fgM.js +1 -0
- package/apps/web/dist/assets/SettingsPage-Dzu6Fl6b.js +1 -0
- package/apps/web/dist/assets/SetupPage-D2Kl-8NR.js +1 -0
- package/apps/web/dist/assets/SharePage-QSZxUlPt.js +1 -0
- package/apps/web/dist/assets/changelog-XlAAw5Gv.js +1 -0
- package/apps/web/dist/assets/{esm-Dzqe3TT4.js → esm-DqRvG-PU.js} +1 -1
- package/apps/web/dist/assets/index-BhVJwBz7.css +1 -0
- package/apps/web/dist/assets/index-mwmrgVFT.js +14 -0
- package/apps/web/dist/assets/{lock-a8rDnyg5.js → lock-D_KjsGfU.js} +1 -1
- package/apps/web/dist/assets/{phone-CitA6oZn.js → phone-YRxkPQk6.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-COgM8CgB.js → settings-menu-CQgehwEX.js} +1 -1
- package/apps/web/dist/assets/ui-DDUCc_4v.js +137 -0
- package/apps/web/dist/assets/{upload-CcKFwoPd.js → upload-DDo14e2p.js} +1 -1
- package/apps/web/dist/index.html +3 -3
- package/apps/web/dist/mcp/flow.html +58 -55
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +5 -2
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +206 -3
- package/node_modules/@engenty-wizards/shared/dist/definition.js +140 -18
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +54 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +68 -4
- package/node_modules/@engenty-wizards/shared/dist/run.js +26 -8
- package/node_modules/@engenty-wizards/shared/dist/runner-texts.d.ts +546 -0
- package/node_modules/@engenty-wizards/shared/dist/runner-texts.js +523 -0
- package/node_modules/@engenty-wizards/shared/dist/runners.js +1 -1
- package/node_modules/@engenty-wizards/shared/dist/shape.d.ts +102 -0
- package/node_modules/@engenty-wizards/shared/dist/shape.js +318 -0
- package/node_modules/@engenty-wizards/shared/dist/surface.d.ts +33 -7
- package/node_modules/@engenty-wizards/shared/dist/surface.js +263 -20
- package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +11 -2
- package/node_modules/@engenty-wizards/shared/dist/texts.js +16 -8
- package/node_modules/@engenty-wizards/shared/dist/translation.d.ts +23 -0
- package/node_modules/@engenty-wizards/shared/dist/translation.js +154 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/AboutDialog-DvkE9J4h.js +0 -1
- package/apps/web/dist/assets/ChatView-DIDIR0mb.js +0 -10
- package/apps/web/dist/assets/EditorPage-BYysznX3.js +0 -3
- package/apps/web/dist/assets/NewWizardPage-4n9-ZmHt.js +0 -7
- package/apps/web/dist/assets/OnboardingPage-DpZv9qEO.js +0 -1
- package/apps/web/dist/assets/ProjectPage-IQb9RFlm.js +0 -1
- package/apps/web/dist/assets/PublicRunner-Da0klKj-.js +0 -1
- package/apps/web/dist/assets/RunDrawer-FwLK67Yn.js +0 -4
- package/apps/web/dist/assets/RunnerView-BBuphCkK.js +0 -1
- package/apps/web/dist/assets/SettingsPage-CZx52Ubk.js +0 -1
- package/apps/web/dist/assets/SetupPage-B-o7VhbI.js +0 -1
- package/apps/web/dist/assets/SharePage-k2OMGOWU.js +0 -1
- package/apps/web/dist/assets/changelog-9oSWVuP-.js +0 -1
- package/apps/web/dist/assets/index-93fPRTxr.js +0 -14
- package/apps/web/dist/assets/index-DaKjB676.css +0 -1
- package/apps/web/dist/assets/ui-CeZNNuaK.js +0 -134
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { noteText } from "@engenty-wizards/shared/run";
|
|
2
|
+
import { serverText } from "./texts.js";
|
|
2
3
|
/**
|
|
3
|
-
* A failure the person reads, given as a note of `RUN_NOTES`
|
|
4
|
-
* one: a run shows it in the wizard's language, a response in the asker's, and the
|
|
5
|
-
* the English line for the log. Free text has
|
|
4
|
+
* A failure the person reads, given as a note of `RUN_NOTES` or a line of the server's table
|
|
5
|
+
* where it has one: a run shows it in the wizard's language, a response in the asker's, and the
|
|
6
|
+
* message keeps the English line for the log. Free text has neither.
|
|
6
7
|
*/
|
|
7
8
|
export class NotedError extends Error {
|
|
8
9
|
note;
|
|
10
|
+
line;
|
|
9
11
|
constructor(said) {
|
|
10
|
-
super(typeof said === "string"
|
|
11
|
-
|
|
12
|
+
super(typeof said === "string"
|
|
13
|
+
? said
|
|
14
|
+
: "server" in said
|
|
15
|
+
? serverText("en", said.server, said.params)
|
|
16
|
+
: noteText(said, "en"));
|
|
17
|
+
this.note = typeof said === "string" || "server" in said ? null : said;
|
|
18
|
+
this.line = typeof said !== "string" && "server" in said ? said : null;
|
|
12
19
|
}
|
|
13
20
|
}
|
|
14
21
|
/** A class has nothing to run on: no key, no account, no installed client — the person can fix it in the settings. */
|
|
@@ -22,8 +29,14 @@ export function noteOf(err) {
|
|
|
22
29
|
export function saidOf(err) {
|
|
23
30
|
return noteOf(err) ?? err?.message ?? String(err);
|
|
24
31
|
}
|
|
25
|
-
/** A failure in a language: its note where it has one, else its message as it is. */
|
|
32
|
+
/** A failure in a language: its note or server line where it has one, else its message as it is. */
|
|
26
33
|
export function errorText(err, lang) {
|
|
27
34
|
const note = noteOf(err);
|
|
28
|
-
|
|
35
|
+
if (note) {
|
|
36
|
+
return noteText(note, lang);
|
|
37
|
+
}
|
|
38
|
+
if (err instanceof NotedError && err.line) {
|
|
39
|
+
return serverText(lang, err.line.server, err.line.params);
|
|
40
|
+
}
|
|
41
|
+
return err?.message ?? String(err);
|
|
29
42
|
}
|
|
@@ -7,6 +7,7 @@ import { extFor, inlineAssetRefs, loadAsset, loadAssetText, saveAsset } from "..
|
|
|
7
7
|
import { widgetMp4, widgetPdf, widgetPng } from "../widgets/render.js";
|
|
8
8
|
import { allTables, firstTable, htmlToDocx, htmlToMarkdown, htmlToPdf, htmlToPng, jsonToMarkdown, MIME, markdownToHtml, tablesToXlsx, tableToCsv, } from "./convert.js";
|
|
9
9
|
import { guardHtml } from "./guard.js";
|
|
10
|
+
import { shapedCsvRows, shapedMarkdown, shapedTables } from "./shaped.js";
|
|
10
11
|
export function slug(s) {
|
|
11
12
|
return (s
|
|
12
13
|
.toLowerCase()
|
|
@@ -155,8 +156,14 @@ export async function renderDownload(step, output, format, baseName) {
|
|
|
155
156
|
}
|
|
156
157
|
const text = output.text ?? "";
|
|
157
158
|
if (step.output.format === "json") {
|
|
158
|
-
|
|
159
|
-
const
|
|
159
|
+
// A step with a schema: its lists by their labels, the tree as headings.
|
|
160
|
+
const shape = step.output.schema;
|
|
161
|
+
const data = (output.json ?? {});
|
|
162
|
+
const rows = shape ? shapedCsvRows(shape, data) : firstTable(output.json);
|
|
163
|
+
const tables = shape ? shapedTables(shape, data) : allTables(output.json);
|
|
164
|
+
const md = shape
|
|
165
|
+
? `# ${baseName}\n\n${shapedMarkdown(shape, data)}`
|
|
166
|
+
: jsonAsMarkdown(output.json);
|
|
160
167
|
switch (format) {
|
|
161
168
|
case "json":
|
|
162
169
|
return {
|
|
@@ -171,7 +178,13 @@ export async function renderDownload(step, output, format, baseName) {
|
|
|
171
178
|
? { data: await tablesToXlsx(tables), mime: MIME.xlsx, filename: file("xlsx") }
|
|
172
179
|
: null;
|
|
173
180
|
case "md":
|
|
174
|
-
return { data:
|
|
181
|
+
return { data: md, mime: MIME.md, filename: file("md") };
|
|
182
|
+
case "pdf":
|
|
183
|
+
return {
|
|
184
|
+
data: await htmlToPdf(markdownToHtml(md, baseName)),
|
|
185
|
+
mime: MIME.pdf,
|
|
186
|
+
filename: file("pdf"),
|
|
187
|
+
};
|
|
175
188
|
default:
|
|
176
189
|
return null;
|
|
177
190
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { labelOf, titleKey, } from "@engenty-wizards/shared/shape";
|
|
2
|
+
const isEntry = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
3
|
+
/** A value that fits in a cell or a line: no lists of entries, no objects. */
|
|
4
|
+
const flat = (s) => s.kind !== "object" && s.kind !== "table" && !(s.kind === "list" && s.of.kind === "object");
|
|
5
|
+
/** An entry's title: its first short text. */
|
|
6
|
+
function titleOf(shape, entry) {
|
|
7
|
+
const key = titleKey(shape);
|
|
8
|
+
return key ? String(entry[key] ?? "") : "";
|
|
9
|
+
}
|
|
10
|
+
/** A flat value as words. */
|
|
11
|
+
function words(s, value, root, data) {
|
|
12
|
+
if (value === null || value === undefined) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
switch (s.kind) {
|
|
16
|
+
case "yesno":
|
|
17
|
+
return value ? (s.yes ?? "✓") : (s.no ?? "–");
|
|
18
|
+
case "number":
|
|
19
|
+
return `${value}${s.unit ? ` ${s.unit}` : ""}`;
|
|
20
|
+
case "ref": {
|
|
21
|
+
const target = root.fields[s.to];
|
|
22
|
+
const list = Array.isArray(data[s.to]) ? data[s.to] : [];
|
|
23
|
+
const hit = list.find((e) => isEntry(e) && e.id === value);
|
|
24
|
+
return target?.kind === "list" && target.of.kind === "object" && isEntry(hit)
|
|
25
|
+
? titleOf(target.of, hit) || String(value)
|
|
26
|
+
: String(value);
|
|
27
|
+
}
|
|
28
|
+
case "list":
|
|
29
|
+
return Array.isArray(value) ? value.map((v) => words(s.of, v, root, data)).join(", ") : "";
|
|
30
|
+
default:
|
|
31
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** One row of a sheet: flat values by label; a list of entries as their titles. */
|
|
35
|
+
function rowOf(shape, entry, root, data) {
|
|
36
|
+
const row = {};
|
|
37
|
+
for (const [k, f] of Object.entries(shape.fields)) {
|
|
38
|
+
const v = entry[k];
|
|
39
|
+
if (f.kind === "list" && f.of.kind === "object") {
|
|
40
|
+
const of = f.of;
|
|
41
|
+
row[labelOf(k, f)] = Array.isArray(v)
|
|
42
|
+
? v
|
|
43
|
+
.map((e) => (isEntry(e) ? titleOf(of, e) : ""))
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join("; ")
|
|
46
|
+
: "";
|
|
47
|
+
}
|
|
48
|
+
else if (f.kind === "number" && typeof v === "number") {
|
|
49
|
+
row[labelOf(k, f)] = v;
|
|
50
|
+
}
|
|
51
|
+
else if (flat(f)) {
|
|
52
|
+
row[labelOf(k, f)] = words(f, v, root, data);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return row;
|
|
56
|
+
}
|
|
57
|
+
/** One sheet per top-level list, named by its label. */
|
|
58
|
+
export function shapedTables(shape, data) {
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const [k, f] of Object.entries(shape.fields)) {
|
|
61
|
+
const value = Array.isArray(data[k]) ? data[k] : [];
|
|
62
|
+
if (f.kind !== "list" || !value.length) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const of = f.of;
|
|
66
|
+
const rows = of.kind === "object"
|
|
67
|
+
? value.filter(isEntry).map((e) => rowOf(of, e, shape, data))
|
|
68
|
+
: value.map((v) => ({ [labelOf(k, f)]: words(of, v, shape, data) }));
|
|
69
|
+
out.push({ name: labelOf(k, f), rows });
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** The rows of the first top-level list whose entries are flat: what a CSV holds. */
|
|
74
|
+
export function shapedCsvRows(shape, data) {
|
|
75
|
+
for (const [k, f] of Object.entries(shape.fields)) {
|
|
76
|
+
if (f.kind === "list" &&
|
|
77
|
+
f.of.kind === "object" &&
|
|
78
|
+
Object.values(f.of.fields).every(flat) &&
|
|
79
|
+
Array.isArray(data[k])) {
|
|
80
|
+
const of = f.of;
|
|
81
|
+
return data[k].filter(isEntry).map((e) => rowOf(of, e, shape, data));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const cellText = (v) => String(v ?? "")
|
|
87
|
+
.replace(/\|/g, "\\|")
|
|
88
|
+
.replace(/\n/g, " ");
|
|
89
|
+
/** The tree as Markdown: a heading per part and per entry with lists of its own, tables for flat entries. */
|
|
90
|
+
export function shapedMarkdown(shape, data) {
|
|
91
|
+
return objectMarkdown(shape, data, 2, shape, data);
|
|
92
|
+
}
|
|
93
|
+
const h = (n) => "#".repeat(Math.min(6, n));
|
|
94
|
+
function objectMarkdown(shape, value, depth, root, data) {
|
|
95
|
+
const parts = [];
|
|
96
|
+
const short = [];
|
|
97
|
+
// An entry under its title as a heading says neither the title nor its id again.
|
|
98
|
+
const skip = shape === root ? [] : ["id", titleKey(shape)];
|
|
99
|
+
for (const [k, f] of Object.entries(shape.fields)) {
|
|
100
|
+
const v = value[k];
|
|
101
|
+
const label = labelOf(k, f);
|
|
102
|
+
if (v === undefined || v === null || v === "" || skip.includes(k)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (f.kind === "text" && f.long) {
|
|
106
|
+
parts.push(`${h(depth)} ${label}\n\n${String(v)}`);
|
|
107
|
+
}
|
|
108
|
+
else if (f.kind === "list" && Array.isArray(v)) {
|
|
109
|
+
parts.push(`${h(depth)} ${label}\n\n${listMarkdown(f, v, depth + 1, root, data)}`);
|
|
110
|
+
}
|
|
111
|
+
else if (flat(f)) {
|
|
112
|
+
short.push(`**${label}:** ${words(f, v, root, data)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return [short.join(" \n"), ...parts].filter(Boolean).join("\n\n");
|
|
116
|
+
}
|
|
117
|
+
function listMarkdown(f, value, depth, root, data) {
|
|
118
|
+
const of = f.of;
|
|
119
|
+
if (of.kind !== "object") {
|
|
120
|
+
return value.map((v) => `- ${words(of, v, root, data)}`).join("\n");
|
|
121
|
+
}
|
|
122
|
+
const entries = value.filter(isEntry);
|
|
123
|
+
if (Object.values(of.fields).every(flat)) {
|
|
124
|
+
const cols = Object.entries(of.fields);
|
|
125
|
+
return [
|
|
126
|
+
`| ${cols.map(([c, s]) => cellText(labelOf(c, s))).join(" | ")} |`,
|
|
127
|
+
`| ${cols.map(() => "---").join(" | ")} |`,
|
|
128
|
+
...entries.map((e) => `| ${cols.map(([c, s]) => cellText(words(s, e[c], root, data))).join(" | ")} |`),
|
|
129
|
+
].join("\n");
|
|
130
|
+
}
|
|
131
|
+
return entries
|
|
132
|
+
.map((e, i) => {
|
|
133
|
+
const title = titleOf(of, e) || `${of.label ?? f.label ?? ""} ${i + 1}`.trim();
|
|
134
|
+
return `${h(depth)} ${title}\n\n${objectMarkdown(of, e, depth + 1, root, data)}`;
|
|
135
|
+
})
|
|
136
|
+
.join("\n\n");
|
|
137
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FORMATS, LANGS, LIST_FORMATS, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
|
|
1
|
+
import { FORMATS, isLang, LANG_CODE, LANGS, LIST_FORMATS, TEXT_CLASSES, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
2
2
|
import { TableColumnValueError } from "@engenty-wizards/shared/engenty/data-tables";
|
|
3
3
|
import { noteText } from "@engenty-wizards/shared/run";
|
|
4
|
+
import { machineTranslated, translateDefinition, } from "@engenty-wizards/shared/translation";
|
|
4
5
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
5
6
|
import { and, eq } from "drizzle-orm";
|
|
6
7
|
import { Hono } from "hono";
|
|
@@ -20,7 +21,7 @@ import { fieldErrorsIn } from "../engine/input.js";
|
|
|
20
21
|
import { fromOwner } from "../engine/owner.js";
|
|
21
22
|
import { pushAvailable, setPushDevice } from "../engine/push.js";
|
|
22
23
|
import { liveResources } from "../engine/resources.js";
|
|
23
|
-
import { cancel, createRun, goBack, NoCreditsError, projectIdOf, RunConflict, RunInputError, retry, reviewStep, runView, submitPage, talkAllowed, } from "../engine/runner.js";
|
|
24
|
+
import { cancel, createRun, editOutput, goBack, NoCreditsError, projectIdOf, RunConflict, RunInputError, retry, reviewStep, runView, submitPage, talkAllowed, } from "../engine/runner.js";
|
|
24
25
|
import { basePath, env } from "../env.js";
|
|
25
26
|
import { loadAsset, saveAsset } from "../files/storage.js";
|
|
26
27
|
import { reverseGeocode } from "../geocode.js";
|
|
@@ -35,6 +36,7 @@ import { brandView } from "../services/brand.js";
|
|
|
35
36
|
import { ServiceError } from "../services/errors.js";
|
|
36
37
|
import { publicRunners, servePageRunner } from "../services/runners.js";
|
|
37
38
|
import { sharedRun, shareImage, shareRun, shareView, unshareRun } from "../services/shares.js";
|
|
39
|
+
import { isLangCode, storedTranslation, translateResult, translationFor, uiTexts, } from "../services/translations.js";
|
|
38
40
|
import { isIconFile, wizardAvatarSvg, wizardIcon } from "../services/wizard-icon.js";
|
|
39
41
|
import { clearStore, deleteRows, listRows, listSecrets, readStoreFile, StoreError, saveRows, scopeOf, storeFiles, updateRow, } from "../store/index.js";
|
|
40
42
|
import { tenantOfLink, tenantOfRun, tenantStatus, tokenOfCode, visitorOverLimit, } from "../tenants/control.js";
|
|
@@ -108,6 +110,8 @@ function visitorId(c, create, embedded = false) {
|
|
|
108
110
|
* the owner's AI app with its run ticket, or — on the read-only file routes — a signed link
|
|
109
111
|
* handed out to the owner's MCP client.
|
|
110
112
|
*/
|
|
113
|
+
/** A view's kept state stays small: ticks and choices, not data. */
|
|
114
|
+
const MAX_VIEW_STATE = 32_000;
|
|
111
115
|
async function accessibleRun(c, runId, opts = {}) {
|
|
112
116
|
const run = await db.query.run.findFirst({ where: eq(schema.run.id, runId) });
|
|
113
117
|
if (!run) {
|
|
@@ -199,6 +203,50 @@ async function pageRunner(c, rest) {
|
|
|
199
203
|
: null;
|
|
200
204
|
return served ?? c.json(notFoundAnswer(c), 404);
|
|
201
205
|
}
|
|
206
|
+
/** A wizard's link with its published version, or null where it has none. */
|
|
207
|
+
async function publishedWizard(token) {
|
|
208
|
+
const w = await db.query.wizard.findFirst({
|
|
209
|
+
where: eq(schema.wizard.shareToken, token ?? ""),
|
|
210
|
+
});
|
|
211
|
+
if (!w || w.publishedVersion === null) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const version = await db.query.wizardVersion.findFirst({
|
|
215
|
+
where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
|
|
216
|
+
});
|
|
217
|
+
return version ? { w, def: version.definition, version: w.publishedVersion } : null;
|
|
218
|
+
}
|
|
219
|
+
/** The author offers the wizard translated, and the language is not the one it is written in. */
|
|
220
|
+
function wantsTranslation(w, def, lang) {
|
|
221
|
+
return w.translate && lang !== wizardLang(def);
|
|
222
|
+
}
|
|
223
|
+
/** What a wizard's page shows before a run: in the viewer's language where `as` is given. */
|
|
224
|
+
async function publicBody(c, w, original, as) {
|
|
225
|
+
const translated = as ? translateDefinition(original, as.memory) : null;
|
|
226
|
+
const def = translated?.def ?? original;
|
|
227
|
+
const reason = await wizardUnavailable(w);
|
|
228
|
+
return {
|
|
229
|
+
token: w.shareToken,
|
|
230
|
+
title: def.title,
|
|
231
|
+
description: def.description,
|
|
232
|
+
avatar: def.avatar,
|
|
233
|
+
intro: def.intro ?? null,
|
|
234
|
+
brand: await brandView(w.projectId),
|
|
235
|
+
turnstileSiteKey: env.turnstile.siteKey || null,
|
|
236
|
+
available: !reason,
|
|
237
|
+
unavailableReason: reason && noteText(reason, requestLang(c)),
|
|
238
|
+
runners: await publicRunners(w, original),
|
|
239
|
+
lang: wizardLang(original),
|
|
240
|
+
translatable: w.translate,
|
|
241
|
+
translation: as && translated
|
|
242
|
+
? {
|
|
243
|
+
lang: as.lang,
|
|
244
|
+
labels: translated.labels,
|
|
245
|
+
machine: machineTranslated(as.memory, original),
|
|
246
|
+
}
|
|
247
|
+
: null,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
202
250
|
export const publicRoutes = new Hono()
|
|
203
251
|
// A wizard's ID, typed into the mobile app: answers its share token.
|
|
204
252
|
.get("/codes/:code", async (c) => {
|
|
@@ -220,30 +268,59 @@ export const publicRoutes = new Hono()
|
|
|
220
268
|
.use("/wizards/:token/*", wizardLink)
|
|
221
269
|
.use("/logos/:id", logoLink)
|
|
222
270
|
.get("/wizards/:token", async (c) => {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
});
|
|
226
|
-
if (!w || w.publishedVersion === null) {
|
|
271
|
+
const found = await publishedWizard(c.req.param("token"));
|
|
272
|
+
if (!found) {
|
|
227
273
|
return c.json(notFoundAnswer(c), 404);
|
|
228
274
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
275
|
+
// The page in the viewer's language where it is kept; making it is the POST below.
|
|
276
|
+
const lang = c.req.query("lang");
|
|
277
|
+
const memory = isLangCode(lang) && wantsTranslation(found.w, found.def, lang)
|
|
278
|
+
? await storedTranslation(found.w.id, lang)
|
|
279
|
+
: null;
|
|
280
|
+
return c.json(await publicBody(c, found.w, found.def, lang && memory ? { lang, memory } : null));
|
|
281
|
+
})
|
|
282
|
+
// The wizard in the viewer's language: translated now where it is not kept yet.
|
|
283
|
+
.post("/wizards/:token/translation", async (c) => {
|
|
284
|
+
const found = await publishedWizard(c.req.param("token"));
|
|
285
|
+
if (!found) {
|
|
286
|
+
return c.json(notFoundAnswer(c), 404);
|
|
287
|
+
}
|
|
288
|
+
const { lang } = z
|
|
289
|
+
.object({ lang: z.string().regex(LANG_CODE) })
|
|
290
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
291
|
+
if (!wantsTranslation(found.w, found.def, lang)) {
|
|
292
|
+
return refuse(c, { code: "translationOff" }, 403);
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const memory = await translationFor(found.w.id, found.def, lang, found.version);
|
|
296
|
+
return c.json(await publicBody(c, found.w, found.def, { lang, memory }));
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
300
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
301
|
+
}
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
// The runner's own words in a language outside LANGS, booked to this wizard's owner when new.
|
|
306
|
+
.get("/wizards/:token/texts/:lang", async (c) => {
|
|
307
|
+
const found = await publishedWizard(c.req.param("token"));
|
|
308
|
+
const lang = c.req.param("lang");
|
|
309
|
+
if (!found || !isLangCode(lang) || isLang(lang)) {
|
|
310
|
+
return c.json(notFoundAnswer(c), 404);
|
|
311
|
+
}
|
|
312
|
+
if (!wantsTranslation(found.w, found.def, lang)) {
|
|
313
|
+
return refuse(c, { code: "translationOff" }, 403);
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
return c.json(await uiTexts(lang));
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
320
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
321
|
+
}
|
|
322
|
+
throw err;
|
|
323
|
+
}
|
|
247
324
|
})
|
|
248
325
|
.post("/wizards/:token/runs", async (c) => {
|
|
249
326
|
const w = await db.query.wizard.findFirst({
|
|
@@ -262,6 +339,8 @@ export const publicRoutes = new Hono()
|
|
|
262
339
|
embedded: z.boolean().optional(),
|
|
263
340
|
// The website page that embeds the wizard: shown with the result.
|
|
264
341
|
origin: z.string().max(2000).optional(),
|
|
342
|
+
// The language the viewer reads the wizard in, translated on request.
|
|
343
|
+
lang: z.string().regex(LANG_CODE).optional(),
|
|
265
344
|
})
|
|
266
345
|
.parse(await c.req.json().catch(() => ({})));
|
|
267
346
|
const ip = clientIp(c);
|
|
@@ -286,6 +365,11 @@ export const publicRoutes = new Hono()
|
|
|
286
365
|
visitorId: vid,
|
|
287
366
|
ipHash,
|
|
288
367
|
origin: body.embedded ? webAddress(body.origin) : null,
|
|
368
|
+
lang: body.lang &&
|
|
369
|
+
wantsTranslation(w, version.definition, body.lang) &&
|
|
370
|
+
(await storedTranslation(w.id, body.lang))
|
|
371
|
+
? body.lang
|
|
372
|
+
: null,
|
|
289
373
|
});
|
|
290
374
|
return c.json({ runId });
|
|
291
375
|
}
|
|
@@ -478,6 +562,54 @@ export const runRoutes = new Hono()
|
|
|
478
562
|
catch (err) {
|
|
479
563
|
return commandError(c, err);
|
|
480
564
|
}
|
|
565
|
+
})
|
|
566
|
+
// What the person did in a step's view — a checklist's ticks, a widget's state — kept with the
|
|
567
|
+
// run, so the next visit shows it. Only while the run waits or is done: a running step owns
|
|
568
|
+
// the state then.
|
|
569
|
+
.put("/:id/views/:stepId", async (c) => {
|
|
570
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
571
|
+
const stepId = c.req.param("stepId");
|
|
572
|
+
const output = run?.state.outputs[stepId];
|
|
573
|
+
if (!run || !output) {
|
|
574
|
+
return c.json(notFoundAnswer(c), 404);
|
|
575
|
+
}
|
|
576
|
+
if (run.status !== "waiting_input" && run.status !== "done") {
|
|
577
|
+
return c.json({ error: "busy" }, 409);
|
|
578
|
+
}
|
|
579
|
+
const { state } = z
|
|
580
|
+
.object({ state: z.record(z.string(), z.unknown()) })
|
|
581
|
+
.parse(await c.req.json());
|
|
582
|
+
if (JSON.stringify(state).length > MAX_VIEW_STATE) {
|
|
583
|
+
return c.json({ error: "too large" }, 413);
|
|
584
|
+
}
|
|
585
|
+
await db
|
|
586
|
+
.update(schema.run)
|
|
587
|
+
.set({
|
|
588
|
+
state: {
|
|
589
|
+
...run.state,
|
|
590
|
+
outputs: { ...run.state.outputs, [stepId]: { ...output, view: state } },
|
|
591
|
+
},
|
|
592
|
+
})
|
|
593
|
+
.where(eq(schema.run.id, run.id));
|
|
594
|
+
return c.json({ ok: true });
|
|
595
|
+
})
|
|
596
|
+
// A json step's data, edited in the studio by someone of the team the run belongs to.
|
|
597
|
+
.put("/:id/outputs/:stepId", async (c) => {
|
|
598
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
599
|
+
const user = await principalOf(c);
|
|
600
|
+
if (!run || !user || user.tenantId !== run.tenantId) {
|
|
601
|
+
return c.json(notFoundAnswer(c), 404);
|
|
602
|
+
}
|
|
603
|
+
const { json } = z
|
|
604
|
+
.object({ json: z.record(z.string(), z.unknown()) })
|
|
605
|
+
.parse(await c.req.json());
|
|
606
|
+
try {
|
|
607
|
+
await editOutput(run.id, c.req.param("stepId"), json);
|
|
608
|
+
return c.json({ ok: true });
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
return commandError(c, err);
|
|
612
|
+
}
|
|
481
613
|
})
|
|
482
614
|
.post("/:id/reviews/:stepId", async (c) => {
|
|
483
615
|
const run = await accessibleRun(c, c.req.param("id"));
|
|
@@ -486,7 +618,13 @@ export const runRoutes = new Hono()
|
|
|
486
618
|
}
|
|
487
619
|
const action = z
|
|
488
620
|
.discriminatedUnion("type", [
|
|
489
|
-
z.object({
|
|
621
|
+
z.object({
|
|
622
|
+
type: z.literal("accept"),
|
|
623
|
+
// A text, or the data of a json step (checked against its schema).
|
|
624
|
+
edits: z
|
|
625
|
+
.record(z.string(), z.union([z.string(), z.record(z.string(), z.unknown())]))
|
|
626
|
+
.optional(),
|
|
627
|
+
}),
|
|
490
628
|
z.object({
|
|
491
629
|
type: z.literal("regenerate"),
|
|
492
630
|
target: z.string(),
|
|
@@ -536,6 +674,71 @@ export const runRoutes = new Hono()
|
|
|
536
674
|
}
|
|
537
675
|
await cancel(run.id);
|
|
538
676
|
return c.json({ ok: true });
|
|
677
|
+
})
|
|
678
|
+
// The viewer reads the run in another language from now on: null for the wizard's own.
|
|
679
|
+
.post("/:id/lang", async (c) => {
|
|
680
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
681
|
+
if (!run) {
|
|
682
|
+
return c.json(notFoundAnswer(c), 404);
|
|
683
|
+
}
|
|
684
|
+
const { lang } = z
|
|
685
|
+
.object({ lang: z.string().regex(LANG_CODE).nullable() })
|
|
686
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
687
|
+
const w = await db.query.wizard.findFirst({ where: eq(schema.wizard.id, run.wizardId) });
|
|
688
|
+
const own = !lang || lang === wizardLang(run.definition);
|
|
689
|
+
if (!(own || (w && wantsTranslation(w, run.definition, lang)))) {
|
|
690
|
+
return refuse(c, { code: "translationOff" }, 403);
|
|
691
|
+
}
|
|
692
|
+
if (!own) {
|
|
693
|
+
try {
|
|
694
|
+
await translationFor(run.wizardId, run.definition, lang, run.version);
|
|
695
|
+
}
|
|
696
|
+
catch (err) {
|
|
697
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
698
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
699
|
+
}
|
|
700
|
+
throw err;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
await db
|
|
704
|
+
.update(schema.run)
|
|
705
|
+
.set({ lang: own ? null : lang })
|
|
706
|
+
.where(eq(schema.run.id, run.id));
|
|
707
|
+
signalChanged(run.id);
|
|
708
|
+
return c.json({ ok: true });
|
|
709
|
+
})
|
|
710
|
+
// A finished result's text in the language the viewer reads the run in, kept on the run.
|
|
711
|
+
.post("/:id/translate-result", async (c) => {
|
|
712
|
+
const run = await accessibleRun(c, c.req.param("id"));
|
|
713
|
+
if (!run) {
|
|
714
|
+
return c.json(notFoundAnswer(c), 404);
|
|
715
|
+
}
|
|
716
|
+
const { stepId } = z.object({ stepId: z.string() }).parse(await c.req.json().catch(() => ({})));
|
|
717
|
+
const text = run.state.outputs[stepId]?.text;
|
|
718
|
+
if (!(run.lang && text)) {
|
|
719
|
+
return c.json(notFoundAnswer(c), 404);
|
|
720
|
+
}
|
|
721
|
+
if (!run.state.translated?.[run.lang]?.[stepId]) {
|
|
722
|
+
let translated;
|
|
723
|
+
try {
|
|
724
|
+
translated = await translateResult(text, run.lang, wizardLang(run.definition));
|
|
725
|
+
}
|
|
726
|
+
catch (err) {
|
|
727
|
+
if (err instanceof ServiceError || err instanceof ModelUnavailableError) {
|
|
728
|
+
return refuse(c, { code: "translationFailed" }, 502);
|
|
729
|
+
}
|
|
730
|
+
throw err;
|
|
731
|
+
}
|
|
732
|
+
const fresh = await db.query.run.findFirst({ where: eq(schema.run.id, run.id) });
|
|
733
|
+
const state = fresh?.state ?? run.state;
|
|
734
|
+
state.translated = {
|
|
735
|
+
...state.translated,
|
|
736
|
+
[run.lang]: { ...state.translated?.[run.lang], [stepId]: translated },
|
|
737
|
+
};
|
|
738
|
+
await db.update(schema.run).set({ state }).where(eq(schema.run.id, run.id));
|
|
739
|
+
signalChanged(run.id);
|
|
740
|
+
}
|
|
741
|
+
return c.json({ ok: true });
|
|
539
742
|
})
|
|
540
743
|
// A live conversation on the run: the secret the browser opens the session with.
|
|
541
744
|
.post("/:id/talk", async (c) => {
|
|
@@ -1,11 +1,12 @@
|
|
|
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 { LANGS, MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
|
|
4
|
+
import { LANGS, MODEL_CLASSES, TEXT_CLASSES, wizardLang, } 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
8
|
import { note } from "@engenty-wizards/shared/run";
|
|
9
|
+
import { wizardTexts } from "@engenty-wizards/shared/translation";
|
|
9
10
|
import { PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKAGE_MIME, } from "@engenty-wizards/shared/wizard-package";
|
|
10
11
|
import { generateText } from "ai";
|
|
11
12
|
import { Hono } from "hono";
|
|
@@ -58,6 +59,7 @@ import { runnersOf, wizardRunners } from "../services/runners.js";
|
|
|
58
59
|
import { listResults, listRuns, startTestRun } from "../services/runs.js";
|
|
59
60
|
import { addCategoryValue, categoryRow, categoryValue, deleteCategory, deleteCategoryValue, mergeCategoryValues, orderCategoryValues, putCategory, renameCategoryValue, setItemCategory, updateCategory, } from "../services/space-categories.js";
|
|
60
61
|
import { addTableRow, createPage, createTable, deletePage, deleteTable, deleteTableRows, getPage, getTable, listSpaceData, orderSubPages, updatePage, updateTable, updateTableRow, } from "../services/space-data.js";
|
|
62
|
+
import { deleteTranslation, editTranslation, isLangCode, publishedDefinition, translationsOf, } from "../services/translations.js";
|
|
61
63
|
import { draftWidgetPreview } from "../services/widgets.js";
|
|
62
64
|
import { createWizard, deleteWizard, draftChanges, duplicateWizard, listWizards, ownedWizard, rotateShareLink, updateWizardSettings, wizardMessages, wizardState, writableWizard, writeDraft, } from "../services/wizards.js";
|
|
63
65
|
import { readSetting, writeSetting } from "../settings.js";
|
|
@@ -66,7 +68,7 @@ import { currentTenant } from "../tenants/tenant.js";
|
|
|
66
68
|
import { serverText } from "../texts.js";
|
|
67
69
|
import { applyUpdate, updateStatus } from "../update.js";
|
|
68
70
|
import { byteRange } from "./delivery.js";
|
|
69
|
-
import { requestLang, say } from "./lang.js";
|
|
71
|
+
import { notFoundAnswer, requestLang, say } from "./lang.js";
|
|
70
72
|
/** The model sources a test call has worked on: the setup is done for those, not for the others. */
|
|
71
73
|
async function testedSources() {
|
|
72
74
|
const tested = await readSetting("setup-done");
|
|
@@ -1008,16 +1010,52 @@ export const studio = new Hono()
|
|
|
1008
1010
|
dailyRunLimit: z.number().int().min(1).max(10_000).optional(),
|
|
1009
1011
|
projectId: z.string().optional(),
|
|
1010
1012
|
runners: z.object({ default: z.string(), enabled: z.array(z.string()).max(20) }).optional(),
|
|
1013
|
+
translate: z.boolean().optional(),
|
|
1011
1014
|
})
|
|
1012
1015
|
.parse(await c.req.json());
|
|
1013
1016
|
await updateWizardSettings(c.get("user").id, c.req.param("id"), patch);
|
|
1014
1017
|
// The copy in the cloud of a linked account is shared the same way.
|
|
1015
1018
|
if (patch.shareEnabled !== undefined ||
|
|
1016
1019
|
patch.dailyRunLimit !== undefined ||
|
|
1017
|
-
patch.runners !== undefined
|
|
1020
|
+
patch.runners !== undefined ||
|
|
1021
|
+
patch.translate !== undefined) {
|
|
1018
1022
|
await pushSharing(c.get("user").id, c.req.param("id"));
|
|
1019
1023
|
}
|
|
1020
1024
|
return c.json({ ok: true });
|
|
1025
|
+
})
|
|
1026
|
+
// The languages the published wizard is kept in, each original text with its translation.
|
|
1027
|
+
.get("/wizards/:id/translations", async (c) => {
|
|
1028
|
+
const w = await ownedWizard(c.get("user").id, c.req.param("id"));
|
|
1029
|
+
const def = await publishedDefinition(w);
|
|
1030
|
+
const kept = await translationsOf(w.id);
|
|
1031
|
+
return c.json({
|
|
1032
|
+
lang: def ? wizardLang(def) : wizardLang(w.draft),
|
|
1033
|
+
texts: def ? wizardTexts(def) : [],
|
|
1034
|
+
translations: kept.map((t) => ({
|
|
1035
|
+
lang: t.lang,
|
|
1036
|
+
texts: t.texts,
|
|
1037
|
+
updatedAt: t.updatedAt.toISOString(),
|
|
1038
|
+
})),
|
|
1039
|
+
});
|
|
1040
|
+
})
|
|
1041
|
+
// The author's own lines for a language: they replace what a model wrote.
|
|
1042
|
+
.put("/wizards/:id/translations/:lang", async (c) => {
|
|
1043
|
+
const w = await writableWizard(c.get("user").id, c.req.param("id"));
|
|
1044
|
+
const def = await publishedDefinition(w);
|
|
1045
|
+
const lang = c.req.param("lang");
|
|
1046
|
+
if (!(def && isLangCode(lang))) {
|
|
1047
|
+
return c.json(notFoundAnswer(c), 404);
|
|
1048
|
+
}
|
|
1049
|
+
const { lines } = z
|
|
1050
|
+
.object({ lines: z.record(z.string(), z.string().max(4000)) })
|
|
1051
|
+
.parse(await c.req.json());
|
|
1052
|
+
await editTranslation(w.id, def, lang, lines);
|
|
1053
|
+
return c.json({ ok: true });
|
|
1054
|
+
})
|
|
1055
|
+
.delete("/wizards/:id/translations/:lang", async (c) => {
|
|
1056
|
+
const w = await writableWizard(c.get("user").id, c.req.param("id"));
|
|
1057
|
+
await deleteTranslation(w.id, c.req.param("lang"));
|
|
1058
|
+
return c.json({ ok: true });
|
|
1021
1059
|
})
|
|
1022
1060
|
// The wizard's ID for the mobile app, beside its link.
|
|
1023
1061
|
.get("/wizards/:id/code", async (c) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GRADIENT_TYPES, gradientCss, mainLogo, } from "@engenty-wizards/shared/projects";
|
|
1
|
+
import { GRADIENT_TYPES, gradientCss, mainLogo, runTheme, } from "@engenty-wizards/shared/projects";
|
|
2
2
|
import { note } from "@engenty-wizards/shared/run";
|
|
3
3
|
import { generateText, Output } from "ai";
|
|
4
4
|
import { and, asc, eq } from "drizzle-orm";
|
|
@@ -24,6 +24,7 @@ export async function brandView(projectId) {
|
|
|
24
24
|
name: p?.brand.name ?? "",
|
|
25
25
|
accent: p?.brand.colors?.[0]?.value ?? null,
|
|
26
26
|
logoUrl: logo ? `/api/public/logos/${logo}` : null,
|
|
27
|
+
theme: runTheme(p?.brand ?? {}),
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
const HEX = /^#[0-9a-f]{6}$/i;
|
|
@@ -292,6 +292,7 @@ export async function pushSharing(userId, wizardId) {
|
|
|
292
292
|
await call("PATCH", `/spaces/${w.projectId}/wizards/${w.id}`, {
|
|
293
293
|
shareEnabled: w.shareEnabled,
|
|
294
294
|
dailyRunLimit: w.dailyRunLimit,
|
|
295
|
+
translate: w.translate,
|
|
295
296
|
...(w.runners ? { runners: w.runners } : {}),
|
|
296
297
|
}).catch((err) => console.error("[cloud] sharing not sent:", err.message));
|
|
297
298
|
}
|
|
@@ -69,6 +69,7 @@ export const syncWizardSchema = z.object({
|
|
|
69
69
|
});
|
|
70
70
|
export const syncSettingsSchema = z.object({
|
|
71
71
|
shareEnabled: z.boolean().optional(),
|
|
72
|
+
translate: z.boolean().optional(),
|
|
72
73
|
dailyRunLimit: z.number().int().min(1).max(10_000).optional(),
|
|
73
74
|
});
|
|
74
75
|
export const checkSchema = z.object({
|