wizards 0.2.16 → 0.2.18
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/project-assistant.js +20 -2
- package/apps/runtime/dist/authoring/guide.js +6 -3
- package/apps/runtime/dist/credits/estimate.js +14 -8
- package/apps/runtime/dist/db/migrations/tenant/0008_run_event_note.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0009_knowledge.sql +104 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0008_snapshot.json +2019 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0009_snapshot.json +2837 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +14 -0
- package/apps/runtime/dist/db/schema.js +162 -8
- package/apps/runtime/dist/engine/events.js +14 -2
- package/apps/runtime/dist/engine/prepare.js +5 -2
- package/apps/runtime/dist/engine/requirements.js +108 -0
- package/apps/runtime/dist/engine/runner.js +2 -2
- package/apps/runtime/dist/engine/steps.js +21 -21
- package/apps/runtime/dist/engine/template.js +13 -10
- package/apps/runtime/dist/env.js +2 -0
- package/apps/runtime/dist/files/storage.js +15 -0
- package/apps/runtime/dist/models.js +38 -0
- package/apps/runtime/dist/plugins/loader.js +2 -0
- package/apps/runtime/dist/routes/studio.js +149 -8
- package/apps/runtime/dist/services/cloud.js +59 -1
- package/apps/runtime/dist/services/knowledge-model.js +558 -0
- package/apps/runtime/dist/services/knowledge.js +721 -0
- package/apps/runtime/dist/services/marketplace.js +2 -2
- package/apps/runtime/dist/services/project-files.js +114 -6
- package/apps/runtime/dist/services/project-index.js +632 -94
- package/apps/runtime/dist/services/relevance.js +90 -0
- package/apps/runtime/dist/services/space-categories.js +882 -0
- package/apps/runtime/dist/services/space-data.js +466 -38
- package/apps/runtime/dist/services/space-origin.js +164 -0
- package/apps/runtime/dist/services/spaces.js +155 -1
- package/apps/runtime/dist/tools/browser.js +12 -6
- package/apps/runtime/dist/tools/index.js +12 -6
- package/apps/runtime/dist/tools/mail.js +5 -2
- package/apps/runtime/dist/tools/pages.js +28 -23
- package/apps/runtime/dist/tools/project.js +132 -39
- package/apps/runtime/dist/tools/store.js +4 -4
- package/apps/runtime/dist/widgets/step.js +10 -4
- package/apps/web/dist/assets/{Account-BE_W101C.js → Account-Cp1cgqjV.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-DjKQJAos.js → ChangelogDialog-Chke1qUe.js} +1 -1
- package/apps/web/dist/assets/ChatPanel-DWrwewr6.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-Ckema8Ss.js +1 -0
- package/apps/web/dist/assets/EditorPage-XX6pfHk5.js +2 -0
- package/apps/web/dist/assets/{FlowPreview-DK4RZqTW.js → FlowPreview-DS545n78.js} +1 -1
- package/apps/web/dist/assets/{Harness-DcI94f1U.js → Harness-H3yXP-2L.js} +2 -2
- package/apps/web/dist/assets/{HomePage-BBNFgryu.js → HomePage-Bds84ShK.js} +1 -1
- package/apps/web/dist/assets/{ImportWizard-BoYqovdl.js → ImportWizard-0_NkA8aS.js} +1 -1
- package/apps/web/dist/assets/LocalRuntime-DnABzTOe.js +1 -0
- package/apps/web/dist/assets/{NewWizardPage-tKV2S4eK.js → NewWizardPage-BqP75V9K.js} +2 -2
- package/apps/web/dist/assets/ProjectPage-DbOvwOdZ.js +5 -0
- package/apps/web/dist/assets/PublicRunner-CtZ7vxl_.js +1 -0
- package/apps/web/dist/assets/RunnerView-xV5bWclu.js +70 -0
- package/apps/web/dist/assets/SettingsPage-DKt55zFh.js +1 -0
- package/apps/web/dist/assets/{SetupPage-qxHvlf87.js → SetupPage-c6mMD9zN.js} +1 -1
- package/apps/web/dist/assets/SharePage-DTKIbMj-.js +1 -0
- package/apps/web/dist/assets/{api-t9_w1XNe.js → api-CVG-xDEn.js} +2 -2
- package/apps/web/dist/assets/changelog-CXXT0zsp.js +1 -0
- package/apps/web/dist/assets/{esm-D0YVMTdo.js → esm-DLM3jdbm.js} +1 -1
- package/apps/web/dist/assets/index-1gmaxDSk.js +10 -0
- package/apps/web/dist/assets/index-Bwyehu0k.css +1 -0
- package/apps/web/dist/assets/{meta-BJHYbMh7.js → meta-m947x6uG.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-BqqUAvIY.js → settings-menu-DUY6__C3.js} +1 -1
- package/apps/web/dist/assets/ui-BPY2pIh0.js +1 -0
- package/apps/web/dist/index.html +4 -4
- package/apps/web/dist/mcp/flow.html +54 -54
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +164 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +10 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +25 -0
- package/node_modules/@engenty-wizards/shared/dist/knowledge.d.ts +199 -0
- package/node_modules/@engenty-wizards/shared/dist/knowledge.js +68 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +1 -16
- package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +149 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +77 -1
- package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/space-data.js +2 -1
- package/package.json +1 -1
- package/apps/web/dist/assets/ChatPanel-BWrcDs4s.js +0 -1
- package/apps/web/dist/assets/CreditsDialog-DUjR5xkl.js +0 -1
- package/apps/web/dist/assets/EditorPage-CrOqikP2.js +0 -2
- package/apps/web/dist/assets/LocalRuntime-BD8zGN8w.js +0 -1
- package/apps/web/dist/assets/ProjectPage-voR0hvzy.js +0 -4
- package/apps/web/dist/assets/PublicRunner-CYZE_hSi.js +0 -1
- package/apps/web/dist/assets/RunnerView-D_C7hn81.js +0 -70
- package/apps/web/dist/assets/SettingsPage-DguoPF_3.js +0 -1
- package/apps/web/dist/assets/SharePage-Cf3i7KB-.js +0 -1
- package/apps/web/dist/assets/changelog-kz0DmHY4.js +0 -1
- package/apps/web/dist/assets/index-BPpKz65x.css +0 -1
- package/apps/web/dist/assets/index-D5w3fW1p.js +0 -10
- package/apps/web/dist/assets/ui-BHXO_e0e.js +0 -1
|
@@ -57,6 +57,20 @@
|
|
|
57
57
|
"when": 1791287441521,
|
|
58
58
|
"tag": "0007_space_data",
|
|
59
59
|
"breakpoints": true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"idx": 8,
|
|
63
|
+
"version": "6",
|
|
64
|
+
"when": 1791307814904,
|
|
65
|
+
"tag": "0008_run_event_note",
|
|
66
|
+
"breakpoints": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"idx": 9,
|
|
70
|
+
"version": "6",
|
|
71
|
+
"when": 1791311715390,
|
|
72
|
+
"tag": "0009_knowledge",
|
|
73
|
+
"breakpoints": true
|
|
60
74
|
}
|
|
61
75
|
]
|
|
62
76
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sql } from "drizzle-orm";
|
|
2
|
-
import { blob, index, integer, primaryKey, sqliteTable, text, uniqueIndex, } from "drizzle-orm/sqlite-core";
|
|
2
|
+
import { blob, index, integer, primaryKey, real, sqliteTable, text, uniqueIndex, } from "drizzle-orm/sqlite-core";
|
|
3
3
|
const now = sql `(unixepoch() * 1000)`;
|
|
4
4
|
const createdAt = () => integer("created_at", { mode: "timestamp_ms" }).notNull().default(now);
|
|
5
5
|
const updatedAt = () => integer("updated_at", { mode: "timestamp_ms" }).notNull().default(now);
|
|
@@ -129,6 +129,8 @@ export const runEvent = sqliteTable("run_event", {
|
|
|
129
129
|
stepId: text("step_id"),
|
|
130
130
|
type: text("type", { enum: ["step_started", "step_done", "tool", "info", "error"] }).notNull(),
|
|
131
131
|
message: text("message").notNull(),
|
|
132
|
+
/** The message as a note of `RUN_NOTES`, for a runner in another language. */
|
|
133
|
+
note: text("note", { mode: "json" }).$type(),
|
|
132
134
|
/** A picture of the run the event is about; shown while the step works. */
|
|
133
135
|
assetId: text("asset_id"),
|
|
134
136
|
createdAt: createdAt(),
|
|
@@ -194,14 +196,23 @@ export const projectFile = sqliteTable("project_file", {
|
|
|
194
196
|
chars: integer("chars"),
|
|
195
197
|
/** What the index holds of a document; `embeddings` includes the keywords. */
|
|
196
198
|
indexed: text("indexed", { enum: ["embeddings", "keywords"] }),
|
|
199
|
+
/** A plugin's file (`server.spaceData.putFile`): the plugin, its key and what it calls it. */
|
|
200
|
+
origin: text("origin"),
|
|
201
|
+
originKey: text("origin_key"),
|
|
202
|
+
originLabel: text("origin_label"),
|
|
197
203
|
createdAt: createdAt(),
|
|
198
204
|
updatedAt: updatedAt(),
|
|
199
|
-
}, (t) => [
|
|
205
|
+
}, (t) => [
|
|
206
|
+
index("project_file_project").on(t.projectId, t.kind, t.position),
|
|
207
|
+
uniqueIndex("project_file_origin").on(t.projectId, t.origin, t.originKey),
|
|
208
|
+
]);
|
|
200
209
|
/**
|
|
201
|
-
* The
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
210
|
+
* The index of Wissen: one unit per section of a page, row of a table, file, summary of a
|
|
211
|
+
* Kategorie's value, passage of a document read before pages, and passage of a plugin's text
|
|
212
|
+
* (`server.index`). Each unit's text starts with where it stands and its Kategorien. Keywords
|
|
213
|
+
* live in two FTS5 tables, words (`project_chunk_fts`) and trigrams (`project_chunk_tri`), which
|
|
214
|
+
* triggers keep in step (see the migrations); `embedding` is the unit's vector as float32
|
|
215
|
+
* bytes, compared with libSQL's `vector_distance_cos`.
|
|
205
216
|
*/
|
|
206
217
|
export const projectChunk = sqliteTable("project_chunk", {
|
|
207
218
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
@@ -210,6 +221,23 @@ export const projectChunk = sqliteTable("project_chunk", {
|
|
|
210
221
|
.references(() => project.id, { onDelete: "cascade" }),
|
|
211
222
|
/** The document the passage is of; null for a plugin's text. */
|
|
212
223
|
fileId: text("file_id").references(() => projectFile.id, { onDelete: "cascade" }),
|
|
224
|
+
/** What the unit is of: a page, a row (with its table), a table (FAQ format: a row), a value. */
|
|
225
|
+
pageId: text("page_id").references(() => spacePage.id, {
|
|
226
|
+
onDelete: "cascade",
|
|
227
|
+
}),
|
|
228
|
+
rowId: text("row_id").references(() => spaceTableRow.id, {
|
|
229
|
+
onDelete: "cascade",
|
|
230
|
+
}),
|
|
231
|
+
tableId: text("table_id").references(() => spaceTable.id, {
|
|
232
|
+
onDelete: "cascade",
|
|
233
|
+
}),
|
|
234
|
+
valueId: text("value_id").references(() => spaceCategoryValue.id, {
|
|
235
|
+
onDelete: "cascade",
|
|
236
|
+
}),
|
|
237
|
+
/** A section's headings, outermost first, joined by " › ". */
|
|
238
|
+
heading: text("heading"),
|
|
239
|
+
/** The page of the original a section starts on (`<!-- S. 3 -->`). */
|
|
240
|
+
sheet: integer("sheet"),
|
|
213
241
|
/** A plugin's text: the plugin, its key for the text, its title and where the studio shows it. */
|
|
214
242
|
plugin: text("plugin"),
|
|
215
243
|
ref: text("ref"),
|
|
@@ -224,6 +252,10 @@ export const projectChunk = sqliteTable("project_chunk", {
|
|
|
224
252
|
index("project_chunk_project").on(t.projectId, t.model),
|
|
225
253
|
index("project_chunk_file").on(t.fileId),
|
|
226
254
|
index("project_chunk_ref").on(t.projectId, t.plugin, t.ref),
|
|
255
|
+
index("project_chunk_page").on(t.pageId),
|
|
256
|
+
index("project_chunk_row").on(t.rowId),
|
|
257
|
+
index("project_chunk_table").on(t.tableId),
|
|
258
|
+
index("project_chunk_value").on(t.valueId),
|
|
227
259
|
]);
|
|
228
260
|
// --- The space's data -------------------------------------------------------
|
|
229
261
|
// Tables and pages a space keeps, its own or one of its wizards'. Shared by everyone who works
|
|
@@ -245,12 +277,30 @@ export const spaceTable = sqliteTable("space_table", {
|
|
|
245
277
|
/** The column a row is matched on, as the list says. */
|
|
246
278
|
keyColumn: text("key_column"),
|
|
247
279
|
title: text("title").notNull(),
|
|
280
|
+
/** Its name in a path (`tables/<slug>`), unique among the space's tables. */
|
|
281
|
+
slug: text("slug"),
|
|
282
|
+
/** `faq`: a question and its answer per row. */
|
|
283
|
+
format: text("format", { enum: ["faq"] }),
|
|
248
284
|
columns: text("columns", { mode: "json" }).$type().notNull(),
|
|
285
|
+
/**
|
|
286
|
+
* Written by a plugin (`server.spaceData`): the plugin, its key for the item and what it
|
|
287
|
+
* calls where it came from ("Tagesberichte"). Null: made in the studio or uploaded.
|
|
288
|
+
*/
|
|
289
|
+
origin: text("origin"),
|
|
290
|
+
originKey: text("origin_key"),
|
|
291
|
+
originLabel: text("origin_label"),
|
|
292
|
+
/** A person changed what its origin wrote: the origin no longer writes it. */
|
|
293
|
+
kept: integer("kept", { mode: "boolean" }).notNull().default(false),
|
|
294
|
+
/** Why a person should look at it ("hardly readable"); null when nothing is in doubt. */
|
|
295
|
+
review: text("review"),
|
|
296
|
+
/** The file it was read from, kept as the evidence. */
|
|
297
|
+
fileId: text("file_id").references(() => projectFile.id, { onDelete: "set null" }),
|
|
249
298
|
createdAt: createdAt(),
|
|
250
299
|
updatedAt: updatedAt(),
|
|
251
300
|
}, (t) => [
|
|
252
301
|
index("space_table_project").on(t.projectId, t.wizardId),
|
|
253
302
|
uniqueIndex("space_table_list").on(t.wizardId, t.list),
|
|
303
|
+
uniqueIndex("space_table_origin").on(t.projectId, t.origin, t.originKey),
|
|
254
304
|
]);
|
|
255
305
|
/** A row of a table: cells by column id. Rows stand in the order they were added. */
|
|
256
306
|
export const spaceTableRow = sqliteTable("space_table_row", {
|
|
@@ -267,7 +317,10 @@ export const spaceTableRow = sqliteTable("space_table_row", {
|
|
|
267
317
|
index("space_table_row_table").on(t.tableId, t.createdAt),
|
|
268
318
|
index("space_table_row_key").on(t.tableId, t.key),
|
|
269
319
|
]);
|
|
270
|
-
/**
|
|
320
|
+
/**
|
|
321
|
+
* A page: a title and markdown, what the editor shows and an agent reads and writes. A page of
|
|
322
|
+
* the space's own may have sub-pages: a long document split along its own headings.
|
|
323
|
+
*/
|
|
271
324
|
export const spacePage = sqliteTable("space_page", {
|
|
272
325
|
id: text("id").primaryKey(),
|
|
273
326
|
tenantId: text("tenant_id").notNull(),
|
|
@@ -275,11 +328,112 @@ export const spacePage = sqliteTable("space_page", {
|
|
|
275
328
|
.notNull()
|
|
276
329
|
.references(() => project.id, { onDelete: "cascade" }),
|
|
277
330
|
wizardId: text("wizard_id").references(() => wizard.id, { onDelete: "cascade" }),
|
|
331
|
+
parentId: text("parent_id").references(() => spacePage.id, {
|
|
332
|
+
onDelete: "cascade",
|
|
333
|
+
}),
|
|
334
|
+
/** Its place among its parent's sub-pages. */
|
|
335
|
+
position: integer("position").notNull().default(0),
|
|
278
336
|
title: text("title").notNull(),
|
|
337
|
+
/** Its name in a path (`pages/<parent>/<slug>`), unique among its siblings. */
|
|
338
|
+
slug: text("slug"),
|
|
279
339
|
markdown: text("markdown").notNull().default(""),
|
|
340
|
+
/**
|
|
341
|
+
* Written by a plugin (`server.spaceData`): the plugin, its key for the item and what it
|
|
342
|
+
* calls where it came from ("Tagesberichte"). Null: made in the studio or uploaded.
|
|
343
|
+
*/
|
|
344
|
+
origin: text("origin"),
|
|
345
|
+
originKey: text("origin_key"),
|
|
346
|
+
originLabel: text("origin_label"),
|
|
347
|
+
/** A person changed what its origin wrote: the origin no longer writes it. */
|
|
348
|
+
kept: integer("kept", { mode: "boolean" }).notNull().default(false),
|
|
349
|
+
/** Why a person should look at it ("hardly readable"); null when nothing is in doubt. */
|
|
350
|
+
review: text("review"),
|
|
351
|
+
/** The file it was read from, kept as the evidence. */
|
|
352
|
+
fileId: text("file_id").references(() => projectFile.id, { onDelete: "set null" }),
|
|
280
353
|
createdAt: createdAt(),
|
|
281
354
|
updatedAt: updatedAt(),
|
|
282
|
-
}, (t) => [
|
|
355
|
+
}, (t) => [
|
|
356
|
+
index("space_page_project").on(t.projectId, t.wizardId),
|
|
357
|
+
index("space_page_parent").on(t.parentId, t.position),
|
|
358
|
+
uniqueIndex("space_page_origin").on(t.projectId, t.origin, t.originKey),
|
|
359
|
+
]);
|
|
360
|
+
// --- Kategorien -----------------------------------------------------------------
|
|
361
|
+
// Typed properties that run across Wissen: set on pages, rows, tables and files, any number of
|
|
362
|
+
// each, so a step filters before it searches.
|
|
363
|
+
/** A Kategorie of the space: a name, a type and, for a choice, the values it has met. */
|
|
364
|
+
export const spaceCategory = sqliteTable("space_category", {
|
|
365
|
+
id: text("id").primaryKey(),
|
|
366
|
+
tenantId: text("tenant_id").notNull(),
|
|
367
|
+
projectId: text("project_id")
|
|
368
|
+
.notNull()
|
|
369
|
+
.references(() => project.id, { onDelete: "cascade" }),
|
|
370
|
+
name: text("name").notNull(),
|
|
371
|
+
type: text("type", { enum: ["choice", "date", "number", "text", "boolean"] }).notNull(),
|
|
372
|
+
/** A number's unit: `h`, `€`, `kg`. */
|
|
373
|
+
unit: text("unit"),
|
|
374
|
+
/** A choice: several values per item. */
|
|
375
|
+
multiple: integer("multiple", { mode: "boolean" }).notNull().default(false),
|
|
376
|
+
/** A choice whose values are ranked (niedrig, mittel, hoch): their position is the rank. */
|
|
377
|
+
ordered: integer("ordered", { mode: "boolean" }).notNull().default(false),
|
|
378
|
+
/** Where its values come from, said in a few words ("Kopfzeile", "Spalte WLL"). */
|
|
379
|
+
hint: text("hint"),
|
|
380
|
+
/** The plugin that brought it; null: made in the studio. */
|
|
381
|
+
origin: text("origin"),
|
|
382
|
+
/** Proposed, not yet taken: shown to be confirmed, never filled. */
|
|
383
|
+
proposed: integer("proposed", { mode: "boolean" }).notNull().default(false),
|
|
384
|
+
/** A column of a table whose cells are its values for the table's rows. */
|
|
385
|
+
tableId: text("table_id").references(() => spaceTable.id, { onDelete: "set null" }),
|
|
386
|
+
columnId: text("column_id"),
|
|
387
|
+
position: integer("position").notNull().default(0),
|
|
388
|
+
createdAt: createdAt(),
|
|
389
|
+
updatedAt: updatedAt(),
|
|
390
|
+
}, (t) => [uniqueIndex("space_category_name").on(t.projectId, t.name)]);
|
|
391
|
+
/** A value of a choice: grows as items arrive; its summary is the Übersicht of its items. */
|
|
392
|
+
export const spaceCategoryValue = sqliteTable("space_category_value", {
|
|
393
|
+
id: text("id").primaryKey(),
|
|
394
|
+
categoryId: text("category_id")
|
|
395
|
+
.notNull()
|
|
396
|
+
.references(() => spaceCategory.id, { onDelete: "cascade" }),
|
|
397
|
+
value: text("value").notNull(),
|
|
398
|
+
position: integer("position").notNull().default(0),
|
|
399
|
+
/** The Übersicht: Markdown a model wrote of the value's items, when it last did. */
|
|
400
|
+
summary: text("summary"),
|
|
401
|
+
summaryAt: integer("summary_at", { mode: "timestamp_ms" }),
|
|
402
|
+
createdAt: createdAt(),
|
|
403
|
+
}, (t) => [uniqueIndex("space_category_value_value").on(t.categoryId, t.value)]);
|
|
404
|
+
/**
|
|
405
|
+
* A Kategorie set on an item: exactly one of page, row, table and file. A choice points to its
|
|
406
|
+
* value; the other types keep theirs typed: a date as milliseconds, a number, a short text, yes
|
|
407
|
+
* or no. A choice with several values has a row per value.
|
|
408
|
+
*/
|
|
409
|
+
export const spaceItemCategory = sqliteTable("space_item_category", {
|
|
410
|
+
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
411
|
+
projectId: text("project_id")
|
|
412
|
+
.notNull()
|
|
413
|
+
.references(() => project.id, { onDelete: "cascade" }),
|
|
414
|
+
categoryId: text("category_id")
|
|
415
|
+
.notNull()
|
|
416
|
+
.references(() => spaceCategory.id, { onDelete: "cascade" }),
|
|
417
|
+
pageId: text("page_id").references(() => spacePage.id, { onDelete: "cascade" }),
|
|
418
|
+
rowId: text("row_id").references(() => spaceTableRow.id, { onDelete: "cascade" }),
|
|
419
|
+
tableId: text("table_id").references(() => spaceTable.id, { onDelete: "cascade" }),
|
|
420
|
+
fileId: text("file_id").references(() => projectFile.id, { onDelete: "cascade" }),
|
|
421
|
+
valueId: text("value_id").references(() => spaceCategoryValue.id, { onDelete: "cascade" }),
|
|
422
|
+
text: text("text"),
|
|
423
|
+
num: real("num"),
|
|
424
|
+
at: integer("at"),
|
|
425
|
+
bool: integer("bool", { mode: "boolean" }),
|
|
426
|
+
/** Who set it: a person, the item's origin (a source's structure), or a model reading it. */
|
|
427
|
+
by: text("by", { enum: ["person", "origin", "model"] })
|
|
428
|
+
.notNull()
|
|
429
|
+
.default("person"),
|
|
430
|
+
}, (t) => [
|
|
431
|
+
index("space_item_category_category").on(t.categoryId, t.valueId),
|
|
432
|
+
index("space_item_category_page").on(t.pageId),
|
|
433
|
+
index("space_item_category_row").on(t.rowId),
|
|
434
|
+
index("space_item_category_table").on(t.tableId),
|
|
435
|
+
index("space_item_category_file").on(t.fileId),
|
|
436
|
+
]);
|
|
283
437
|
// --- The wizard's store ------------------------------------------------------
|
|
284
438
|
// What a wizard keeps between runs for one person. `holder` is that person: "u:<userId>" for a
|
|
285
439
|
// signed-in admin, "v:<visitorId>" for an end user on a shared link.
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { AsyncResource } from "node:async_hooks";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
+
import { noteText } from "@engenty-wizards/shared/run";
|
|
3
4
|
import { and, desc, eq, gt } from "drizzle-orm";
|
|
4
5
|
import { db, schema } from "../db/client.js";
|
|
5
6
|
const bus = new EventEmitter();
|
|
6
7
|
bus.setMaxListeners(0);
|
|
7
|
-
export async function emitEvent(runId, stepId, type,
|
|
8
|
+
export async function emitEvent(runId, stepId, type, said, asset) {
|
|
9
|
+
const note = typeof said === "string" ? null : said;
|
|
10
|
+
const message = note ? noteText(note, "de") : said;
|
|
8
11
|
const [row] = await db
|
|
9
12
|
.insert(schema.runEvent)
|
|
10
|
-
.values({
|
|
13
|
+
.values({
|
|
14
|
+
runId,
|
|
15
|
+
stepId,
|
|
16
|
+
type,
|
|
17
|
+
message: message.slice(0, 2000),
|
|
18
|
+
note,
|
|
19
|
+
assetId: asset ?? null,
|
|
20
|
+
})
|
|
11
21
|
.returning();
|
|
12
22
|
const event = {
|
|
13
23
|
id: row.id,
|
|
@@ -15,6 +25,7 @@ export async function emitEvent(runId, stepId, type, message, asset) {
|
|
|
15
25
|
stepId: row.stepId,
|
|
16
26
|
type: row.type,
|
|
17
27
|
message: row.message,
|
|
28
|
+
note: row.note ?? null,
|
|
18
29
|
asset: row.assetId,
|
|
19
30
|
};
|
|
20
31
|
bus.emit(runId, { runId, event });
|
|
@@ -41,6 +52,7 @@ export async function recentEvents(runId, afterId = 0, limit = 40) {
|
|
|
41
52
|
stepId: r.stepId,
|
|
42
53
|
type: r.type,
|
|
43
54
|
message: r.message,
|
|
55
|
+
note: r.note ?? null,
|
|
44
56
|
asset: r.assetId,
|
|
45
57
|
}));
|
|
46
58
|
}
|
|
@@ -28,7 +28,7 @@ export async function prepareInputs(ctx) {
|
|
|
28
28
|
delete values[field.id];
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
|
-
await ctx.emit("info",
|
|
31
|
+
await ctx.emit("info", { code: "listen", params: { label: field.label } });
|
|
32
32
|
try {
|
|
33
33
|
const transcript = await transcribeAudio({
|
|
34
34
|
bytes: new Uint8Array(found.data),
|
|
@@ -45,7 +45,10 @@ export async function prepareInputs(ctx) {
|
|
|
45
45
|
}
|
|
46
46
|
// The step goes on without the words; the next one tries again.
|
|
47
47
|
console.error(`[run ${ctx.runId}] transcribe ${field.id}`, err);
|
|
48
|
-
await ctx.emit("info",
|
|
48
|
+
await ctx.emit("info", {
|
|
49
|
+
code: "listenFailed",
|
|
50
|
+
params: { label: field.label },
|
|
51
|
+
});
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -100,6 +100,114 @@ export function unavoidableFrom(def, from, known) {
|
|
|
100
100
|
};
|
|
101
101
|
return new Set(def.steps.filter((_, i) => !reachesEnd(i)).map((s) => s.id));
|
|
102
102
|
}
|
|
103
|
+
/** Whether a rule matches given what is known: true, false, or null = it may go either way. */
|
|
104
|
+
function ruleHolds(rule, facts) {
|
|
105
|
+
const k = facts[rule.when.field];
|
|
106
|
+
const value = rule.when.value;
|
|
107
|
+
switch (rule.when.op) {
|
|
108
|
+
case "empty":
|
|
109
|
+
return k?.filled === undefined ? (k?.is !== undefined ? false : null) : !k.filled;
|
|
110
|
+
case "notEmpty":
|
|
111
|
+
return k?.filled === undefined ? (k?.is !== undefined ? true : null) : k.filled;
|
|
112
|
+
case "equals":
|
|
113
|
+
return k?.is !== undefined
|
|
114
|
+
? k.is === String(value)
|
|
115
|
+
: k?.not.includes(String(value))
|
|
116
|
+
? false
|
|
117
|
+
: null;
|
|
118
|
+
case "notEquals":
|
|
119
|
+
return k?.is !== undefined
|
|
120
|
+
? k.is !== String(value)
|
|
121
|
+
: k?.not.includes(String(value))
|
|
122
|
+
? true
|
|
123
|
+
: null;
|
|
124
|
+
case "in": {
|
|
125
|
+
const list = Array.isArray(value) ? value.map(String) : [];
|
|
126
|
+
if (k?.is !== undefined) {
|
|
127
|
+
return list.includes(k.is);
|
|
128
|
+
}
|
|
129
|
+
return list.every((v) => k?.not.includes(v)) ? false : null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** What is known once a rule did (`held`) or did not match. */
|
|
134
|
+
function learn(rule, held, facts) {
|
|
135
|
+
const field = rule.when.field;
|
|
136
|
+
const k = { ...(facts[field] ?? { not: [] }) };
|
|
137
|
+
k.not = [...k.not];
|
|
138
|
+
const value = String(rule.when.value);
|
|
139
|
+
const op = rule.when.op;
|
|
140
|
+
if ((op === "equals" && held) || (op === "notEquals" && !held)) {
|
|
141
|
+
k.is = value;
|
|
142
|
+
k.filled = true;
|
|
143
|
+
}
|
|
144
|
+
else if ((op === "equals" && !held) || (op === "notEquals" && held)) {
|
|
145
|
+
k.not.push(value);
|
|
146
|
+
}
|
|
147
|
+
else if (op === "in" && !held && Array.isArray(rule.when.value)) {
|
|
148
|
+
k.not.push(...rule.when.value.map(String));
|
|
149
|
+
}
|
|
150
|
+
else if (op === "empty" || op === "notEmpty") {
|
|
151
|
+
k.filled = (op === "notEmpty") === held;
|
|
152
|
+
}
|
|
153
|
+
return { ...facts, [field]: k };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The most a run can add up to along one way through the wizard. A branch remembers what it
|
|
157
|
+
* implies of the answers, so ways that contradict themselves (the 720p clips and the 480p
|
|
158
|
+
* ones) are not added up. Steps in `closed` are not passed (no model for them here); where that
|
|
159
|
+
* leaves no way to the end, they count again. A branch back to an earlier step is not followed
|
|
160
|
+
* twice.
|
|
161
|
+
*/
|
|
162
|
+
export function mostAlongOnePath(def, cost, closed = new Set()) {
|
|
163
|
+
const index = new Map(def.steps.map((s, i) => [s.id, i]));
|
|
164
|
+
const end = def.steps.length;
|
|
165
|
+
const walk = (avoid) => {
|
|
166
|
+
const best = (i, facts, seen) => {
|
|
167
|
+
if (i >= end) {
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
const step = def.steps[i];
|
|
171
|
+
if (i < 0 || avoid.has(step.id) || seen.has(i)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const passed = new Set(seen).add(i);
|
|
175
|
+
let most = null;
|
|
176
|
+
const consider = (j, known) => {
|
|
177
|
+
const rest = best(j, known, passed);
|
|
178
|
+
if (rest !== null && (most === null || rest > most)) {
|
|
179
|
+
most = rest;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
// The rules are tried in order; the first that matches decides.
|
|
183
|
+
let known = facts;
|
|
184
|
+
let decided = false;
|
|
185
|
+
for (const rule of step.next ?? []) {
|
|
186
|
+
const holds = ruleHolds(rule, known);
|
|
187
|
+
if (holds === false) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
consider(rule.goto === "end" ? end : (index.get(rule.goto) ?? -1), learn(rule, true, known));
|
|
191
|
+
if (holds === true) {
|
|
192
|
+
decided = true;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
known = learn(rule, false, known);
|
|
196
|
+
}
|
|
197
|
+
if (!decided) {
|
|
198
|
+
consider(i + 1, known);
|
|
199
|
+
}
|
|
200
|
+
return most === null ? null : most + cost(step);
|
|
201
|
+
};
|
|
202
|
+
return best(0, {}, new Set());
|
|
203
|
+
};
|
|
204
|
+
return walk(closed) ?? walk(new Set()) ?? def.steps.reduce((sum, s) => sum + cost(s), 0);
|
|
205
|
+
}
|
|
206
|
+
/** The steps that call a class this runtime cannot serve. */
|
|
207
|
+
export async function stepsWithoutModel(def) {
|
|
208
|
+
const missing = await missingClasses();
|
|
209
|
+
return new Set(def.steps.filter((s) => stepClasses(s).some((cls) => missing.has(cls))).map((s) => s.id));
|
|
210
|
+
}
|
|
103
211
|
/** The classes this wizard needs that this runtime cannot serve; empty = every step can run. */
|
|
104
212
|
export async function missingModels(def) {
|
|
105
213
|
const unavoidable = unavoidableSteps(def);
|
|
@@ -20,7 +20,7 @@ import { askPerson, clearStaleAsk, unattended } from "./asks.js";
|
|
|
20
20
|
import { emitEvent, recentEvents, signalChanged } from "./events.js";
|
|
21
21
|
import { readPageInput } from "./input.js";
|
|
22
22
|
import { pushRun } from "./push.js";
|
|
23
|
-
import { blockingMessage, closedChoices, missingModels, openValue } from "./requirements.js";
|
|
23
|
+
import { blockingMessage, closedChoices, missingModels, openValue, stepsWithoutModel, } from "./requirements.js";
|
|
24
24
|
import { releaseResources, resourcesFor } from "./resources.js";
|
|
25
25
|
import { runAutomaticStep } from "./steps.js";
|
|
26
26
|
import { resolveRef } from "./template.js";
|
|
@@ -161,7 +161,7 @@ export async function createRun(input) {
|
|
|
161
161
|
throw new ModelUnavailableError(blocked);
|
|
162
162
|
}
|
|
163
163
|
// The run's expected cost is held before it starts; what it really costs is booked per call.
|
|
164
|
-
const estimate = await estimateRun(input.wizardId, input.version, input.definition);
|
|
164
|
+
const estimate = await estimateRun(input.wizardId, input.version, input.definition, await stepsWithoutModel(input.definition));
|
|
165
165
|
if (!(await reserveForRun(id, estimate.reserve))) {
|
|
166
166
|
throw new NoCreditsError();
|
|
167
167
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { allFields, dataRef, isDecisionStep, templateRefs, } from "@engenty-wizards/shared/definition";
|
|
1
|
+
import { allFields, dataRef, isDecisionStep, LOCALES, templateRefs, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
2
2
|
import { Agent } from "@mastra/core/agent";
|
|
3
3
|
import { generateText, Output } from "ai";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -6,6 +6,7 @@ import { extFor, loadAsset, loadAssetText } from "../files/storage.js";
|
|
|
6
6
|
import { generateImageMedia, generateSpeechMedia, generateVideoMedia, } from "../media/generate.js";
|
|
7
7
|
import { markMedia } from "../media/marking.js";
|
|
8
8
|
import { attachTools, costOf, isHarnessVendor, textModel } from "../models.js";
|
|
9
|
+
import { knowledgeBlock } from "../services/knowledge.js";
|
|
9
10
|
import { buildStepTools } from "../tools/index.js";
|
|
10
11
|
import { spaceContextOf } from "../tools/plugin.js";
|
|
11
12
|
import { personUploads } from "../tools/store.js";
|
|
@@ -13,8 +14,13 @@ import { runWidgetStep } from "../widgets/step.js";
|
|
|
13
14
|
import { prepareInputs } from "./prepare.js";
|
|
14
15
|
import { answersAsText, colorLine, factLines, renderTemplate, resolveRef, } from "./template.js";
|
|
15
16
|
import { StepError } from "./types.js";
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/** Today's date as the wizard's people write it. */
|
|
18
|
+
function today(ctx) {
|
|
19
|
+
return new Date().toLocaleDateString(LOCALES[wizardLang(ctx.def)], {
|
|
20
|
+
year: "numeric",
|
|
21
|
+
month: "long",
|
|
22
|
+
day: "numeric",
|
|
23
|
+
});
|
|
18
24
|
}
|
|
19
25
|
function brandBlock(ctx) {
|
|
20
26
|
const b = ctx.scope.brand;
|
|
@@ -26,16 +32,6 @@ function brandBlock(ctx) {
|
|
|
26
32
|
].filter(Boolean);
|
|
27
33
|
return parts.length ? `# WHO THIS WIZARD BELONGS TO\n${parts.join("\n")}` : "";
|
|
28
34
|
}
|
|
29
|
-
/** The project's documents, for a step that has the tools to search and read them. */
|
|
30
|
-
function documentsBlock(ctx) {
|
|
31
|
-
const docs = ctx.projectFiles.filter((f) => f.kind === "document").slice(0, 40);
|
|
32
|
-
if (!docs.length) {
|
|
33
|
-
return "";
|
|
34
|
-
}
|
|
35
|
-
return `# THE PROJECT'S DOCUMENTS\nKnowledge the wizard's owner gave for all wizards. Find passages with project_search, read a whole document with project_document. Use them when the task touches what they cover.\n${docs
|
|
36
|
-
.map((d) => `- ${d.name}${d.description ? ` — ${d.description}` : ""}`)
|
|
37
|
-
.join("\n")}`;
|
|
38
|
-
}
|
|
39
35
|
/** Previous output of a step, for a revision. */
|
|
40
36
|
async function previousResult(ctx, stepId) {
|
|
41
37
|
const out = ctx.state.outputs[stepId];
|
|
@@ -166,9 +162,10 @@ export async function runAgentStep(step, ctx) {
|
|
|
166
162
|
: "Finish with everything the result needs in your final answer; it is turned into structured data afterwards.";
|
|
167
163
|
const system = [
|
|
168
164
|
GROUND_RULES,
|
|
169
|
-
`Today is ${today()}.`,
|
|
165
|
+
`Today is ${today(ctx)}.`,
|
|
170
166
|
brandBlock(ctx),
|
|
171
|
-
|
|
167
|
+
// Wissen: what the space holds for its wizards, and how to find and read it.
|
|
168
|
+
await knowledgeBlock(ctx.project.id),
|
|
172
169
|
// What the space's plugins hold for its wizards: a wiki, questions and answers.
|
|
173
170
|
...(await spaceContextOf(ctx)).blocks,
|
|
174
171
|
formatHint,
|
|
@@ -221,7 +218,7 @@ export async function runAgentStep(step, ctx) {
|
|
|
221
218
|
// Working through a mailbox or a row of portals takes many small tool calls.
|
|
222
219
|
const maxSteps = step.tools.includes("browser") || step.connections?.length ? 60 : 20;
|
|
223
220
|
for (const [i, photo] of photos.entries()) {
|
|
224
|
-
await ctx.emit("info",
|
|
221
|
+
await ctx.emit("info", { code: "photo", params: { n: i + 1, total: photos.length } }, photo.id);
|
|
225
222
|
}
|
|
226
223
|
const message = photos.length
|
|
227
224
|
? [
|
|
@@ -381,7 +378,7 @@ async function writeHtml(step, ctx, kind) {
|
|
|
381
378
|
].join("\n");
|
|
382
379
|
const system = [
|
|
383
380
|
GROUND_RULES,
|
|
384
|
-
`Today is ${today()}.`,
|
|
381
|
+
`Today is ${today(ctx)}.`,
|
|
385
382
|
brandBlock(ctx),
|
|
386
383
|
guide,
|
|
387
384
|
images.length
|
|
@@ -474,8 +471,8 @@ async function runMediaStep(step, ctx, asset) {
|
|
|
474
471
|
const todo = entries.filter((e) => !keep(e));
|
|
475
472
|
if (asset === "video") {
|
|
476
473
|
await ctx.emit("info", todo.length > 1
|
|
477
|
-
?
|
|
478
|
-
:
|
|
474
|
+
? { code: "clipsRendering", params: { count: todo.length } }
|
|
475
|
+
: { code: "videoRendering" });
|
|
479
476
|
}
|
|
480
477
|
let finished = 0;
|
|
481
478
|
const note = ctx.state.notes[step.id];
|
|
@@ -496,7 +493,10 @@ async function runMediaStep(step, ctx, asset) {
|
|
|
496
493
|
.join("\n")
|
|
497
494
|
: await visualPrompt(step, ctx, entry);
|
|
498
495
|
if (entry.reference && todo.length > 1) {
|
|
499
|
-
await ctx.emit("info",
|
|
496
|
+
await ctx.emit("info", {
|
|
497
|
+
code: asset === "image" ? "editing" : "filming",
|
|
498
|
+
params: { n: entry.index + 1, total: entries.length },
|
|
499
|
+
}, entry.reference);
|
|
500
500
|
}
|
|
501
501
|
const reference = await loadReference(entry.reference, ctx);
|
|
502
502
|
const media = asset === "image"
|
|
@@ -529,7 +529,7 @@ async function runMediaStep(step, ctx, asset) {
|
|
|
529
529
|
});
|
|
530
530
|
finished++;
|
|
531
531
|
if (todo.length > 1) {
|
|
532
|
-
await ctx.emit("info",
|
|
532
|
+
await ctx.emit("info", { code: "batchDone", params: { done: finished, total: todo.length } }, asset === "image" ? ref.id : undefined);
|
|
533
533
|
}
|
|
534
534
|
return { index: entry.index, prompt, ref };
|
|
535
535
|
});
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { allFields, isAudioValue, isLocationValue, itemsTotals, locationText, } from "@engenty-wizards/shared/definition";
|
|
1
|
+
import { allFields, isAudioValue, isLocationValue, itemsTotals, LOCALES, locationText, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
2
2
|
import { listAsMarkdown } from "@engenty-wizards/shared/store";
|
|
3
3
|
const TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
4
|
-
function money(n, currency) {
|
|
4
|
+
function money(n, currency, locale) {
|
|
5
5
|
try {
|
|
6
|
-
return new Intl.NumberFormat(
|
|
6
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(n);
|
|
7
7
|
}
|
|
8
8
|
catch {
|
|
9
9
|
return `${n.toFixed(2)} ${currency}`;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
/** Line items as a Markdown table with their totals — what a model needs to write a document. */
|
|
13
|
-
export function itemsAsMarkdown(field, value, values) {
|
|
13
|
+
export function itemsAsMarkdown(field, value, values, lang = "de") {
|
|
14
|
+
const locale = LOCALES[lang];
|
|
14
15
|
const totals = itemsTotals(field, value, values);
|
|
15
16
|
const cols = field.columns ?? [];
|
|
16
17
|
if (!totals.rows.length) {
|
|
@@ -18,12 +19,14 @@ export function itemsAsMarkdown(field, value, values) {
|
|
|
18
19
|
}
|
|
19
20
|
const head = `| ${cols.map((c) => c.label).join(" | ")} | Amount |\n|${cols.map(() => " --- ").join("|")}| ---: |`;
|
|
20
21
|
const body = totals.rows.map((r) => `| ${cols
|
|
21
|
-
.map((c) => c.kind === "money"
|
|
22
|
-
|
|
22
|
+
.map((c) => c.kind === "money"
|
|
23
|
+
? money(Number(r[c.id]) || 0, totals.currency, locale)
|
|
24
|
+
: String(r[c.id] ?? ""))
|
|
25
|
+
.join(" | ")} | ${money(r.amount, totals.currency, locale)} |`);
|
|
23
26
|
const sums = [
|
|
24
|
-
`Net: ${money(totals.net, totals.currency)}`,
|
|
25
|
-
totals.vatRate ? `VAT ${totals.vatRate}%: ${money(totals.vat, totals.currency)}` : null,
|
|
26
|
-
`Total: ${money(totals.gross, totals.currency)}`,
|
|
27
|
+
`Net: ${money(totals.net, totals.currency, locale)}`,
|
|
28
|
+
totals.vatRate ? `VAT ${totals.vatRate}%: ${money(totals.vat, totals.currency, locale)}` : null,
|
|
29
|
+
`Total: ${money(totals.gross, totals.currency, locale)}`,
|
|
27
30
|
].filter(Boolean);
|
|
28
31
|
return [head, ...body, "", ...sums].join("\n");
|
|
29
32
|
}
|
|
@@ -118,7 +121,7 @@ export function resolveRef(ref, scope) {
|
|
|
118
121
|
const value = state.values[head];
|
|
119
122
|
if (field?.kind === "items") {
|
|
120
123
|
if (!rest.length) {
|
|
121
|
-
return itemsAsMarkdown(field, value, state.values);
|
|
124
|
+
return itemsAsMarkdown(field, value, state.values, wizardLang(def));
|
|
122
125
|
}
|
|
123
126
|
const totals = itemsTotals(field, value, state.values);
|
|
124
127
|
return totals[rest[0]];
|
package/apps/runtime/dist/env.js
CHANGED
|
@@ -158,6 +158,8 @@ export const env = {
|
|
|
158
158
|
falKey: str("FAL_KEY"),
|
|
159
159
|
elevenlabsKey: str("ELEVENLABS_API_KEY"),
|
|
160
160
|
replicateKey: str("REPLICATE_API_TOKEN"),
|
|
161
|
+
/** TypeSafe's own API, for the classifier that checks what a search found (Jev). */
|
|
162
|
+
typesafeKey: str("TYPESAFE_API_KEY"),
|
|
161
163
|
ollamaUrl: str("OLLAMA_URL", "http://127.0.0.1:11434/v1"),
|
|
162
164
|
/** What each model class runs on when this runtime resolves classes itself (own keys, Ollama). */
|
|
163
165
|
models: {
|
|
@@ -77,6 +77,21 @@ export async function inlineAssetRefs(html) {
|
|
|
77
77
|
}
|
|
78
78
|
return out;
|
|
79
79
|
}
|
|
80
|
+
/** Gives an asset new content under the id it has: what points to it keeps pointing to it. */
|
|
81
|
+
export async function replaceAsset(id, input) {
|
|
82
|
+
const row = await db.query.asset.findFirst({ where: eq(schema.asset.id, id) });
|
|
83
|
+
if (!row) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const bytes = Buffer.from(input.data);
|
|
87
|
+
const file = `${id}-${nanoid(6)}.${extFor(input.mime)}`;
|
|
88
|
+
await objects.put(assetKey(file), bytes);
|
|
89
|
+
await db
|
|
90
|
+
.update(schema.asset)
|
|
91
|
+
.set({ mime: input.mime, name: input.name, path: file, size: bytes.byteLength })
|
|
92
|
+
.where(eq(schema.asset.id, id));
|
|
93
|
+
await objects.remove(assetKey(row.path));
|
|
94
|
+
}
|
|
80
95
|
/** Deletes one asset: its file and its row. */
|
|
81
96
|
export async function removeAsset(id) {
|
|
82
97
|
const row = await db.query.asset.findFirst({ where: eq(schema.asset.id, id) });
|