wizards 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/apps/runtime/dist/agents/studio-assistant.js +79 -7
  2. package/apps/runtime/dist/assistant/runs.js +24 -0
  3. package/apps/runtime/dist/docs/user/assistant.md +25 -7
  4. package/apps/runtime/dist/env.js +5 -0
  5. package/apps/runtime/dist/manage.js +4 -4
  6. package/apps/runtime/dist/models.js +6 -0
  7. package/apps/runtime/dist/routes/studio.js +100 -39
  8. package/apps/runtime/dist/services/assistant-chats.js +49 -6
  9. package/apps/web/dist/assets/{AboutDialog-C0dN86zd.js → AboutDialog-ClV4gNiW.js} +1 -1
  10. package/apps/web/dist/assets/{ChangelogDialog-Cv81t7tj.js → ChangelogDialog-DsSEkayM.js} +1 -1
  11. package/apps/web/dist/assets/ChatView-BdYCnDLk.js +10 -0
  12. package/apps/web/dist/assets/EditorPage-7PZzkNBT.js +3 -0
  13. package/apps/web/dist/assets/{FlowPreview-D4dp1MWS.js → FlowPreview-C_x5Kc2j.js} +1 -1
  14. package/apps/web/dist/assets/{Harness-v7r0e6vK.js → Harness-CvvtSTZC.js} +2 -2
  15. package/apps/web/dist/assets/{NewWizardChannels-Be-7VhBi.js → NewWizardChannels-g1qCd_tQ.js} +1 -1
  16. package/apps/web/dist/assets/{NewWizardPage-C8hPvrK8.js → NewWizardPage-B3hswj5r.js} +2 -2
  17. package/apps/web/dist/assets/ProjectPage-CFDncRxT.js +1 -0
  18. package/apps/web/dist/assets/{PublicRunner-DeCpVfZP.js → PublicRunner-C9N5r-sT.js} +1 -1
  19. package/apps/web/dist/assets/{ReadOnly-B-UCgnqW.js → ReadOnly-C1UcqOlS.js} +1 -1
  20. package/apps/web/dist/assets/RunDrawer-B8YzFvgz.js +4 -0
  21. package/apps/web/dist/assets/RunnerView-ii-PP5c-.js +1 -0
  22. package/apps/web/dist/assets/SettingsPage-ClEP5Q0t.js +1 -0
  23. package/apps/web/dist/assets/{SetupPage-9CH3qXdH.js → SetupPage-CDFzo6LT.js} +1 -1
  24. package/apps/web/dist/assets/{SharePage-B778xUFo.js → SharePage-BV2S0g84.js} +1 -1
  25. package/apps/web/dist/assets/changelog-wZI-UaUi.js +1 -0
  26. package/apps/web/dist/assets/{circle-alert-DLvC9ePT.js → circle-alert-D-Q6DK7b.js} +1 -1
  27. package/apps/web/dist/assets/{esm-BJskogLD.js → esm-CTna4iL0.js} +1 -1
  28. package/apps/web/dist/assets/index-DUjJx-uk.css +1 -0
  29. package/apps/web/dist/assets/index-DnQgOKOQ.js +15 -0
  30. package/apps/web/dist/assets/{lock-DR7NakPn.js → lock-DwGN_KOC.js} +1 -1
  31. package/apps/web/dist/assets/{phone-BORh3jfv.js → phone-XIVIe17d.js} +1 -1
  32. package/apps/web/dist/assets/{settings-menu-DV5D91TD.js → settings-menu-JuSRq6NP.js} +1 -1
  33. package/apps/web/dist/assets/{ui-BOCXzzHi.js → ui-DrjXmHdO.js} +2 -2
  34. package/apps/web/dist/assets/{upload-B3yv8gsk.js → upload-CDxTpAqx.js} +1 -1
  35. package/apps/web/dist/index.html +3 -3
  36. package/apps/web/dist/mcp/flow.html +3 -3
  37. package/node_modules/@engenty-wizards/shared/dist/assistant.d.ts +62 -1
  38. package/node_modules/@engenty-wizards/shared/dist/assistant.js +10 -0
  39. package/package.json +1 -1
  40. package/apps/web/dist/assets/ChatView-uogJ3nht.js +0 -10
  41. package/apps/web/dist/assets/EditorPage-n8YuTh6j.js +0 -3
  42. package/apps/web/dist/assets/ProjectPage-B686ArYy.js +0 -1
  43. package/apps/web/dist/assets/RunDrawer-C6pzi8AJ.js +0 -4
  44. package/apps/web/dist/assets/RunnerView-Deenymq0.js +0 -1
  45. package/apps/web/dist/assets/SettingsPage-CK6L0yEe.js +0 -1
  46. package/apps/web/dist/assets/changelog-DCJo2mbx.js +0 -1
  47. package/apps/web/dist/assets/index-D6hUM8NU.css +0 -1
  48. package/apps/web/dist/assets/index-Df6UxElH.js +0 -12
@@ -6,7 +6,7 @@ import { nanoid } from "nanoid";
6
6
  import { z } from "zod";
7
7
  import { knowsTools } from "../assistant/knows.js";
8
8
  import { PRIMER } from "../assistant/primer.js";
9
- import { attachTools, costOf, gatewayTools, textModel } from "../models.js";
9
+ import { attachTools, contextWindowOf, costOf, gatewayTools, textModel, usageOf, } from "../models.js";
10
10
  import { ServiceError } from "../services/errors.js";
11
11
  import { knowledgeCounts } from "../services/knowledge.js";
12
12
  import { addProjectFileFromUrl, projectFileContent, projectFiles, updateProjectFile, } from "../services/project-files.js";
@@ -16,8 +16,12 @@ import { categoryRows } from "../services/space-categories.js";
16
16
  import { currentTenant } from "../tenants/tenant.js";
17
17
  import { assistantPageOf, assistantToolsOf } from "../tools/plugin.js";
18
18
  import { readWebsite } from "./website.js";
19
+ /** How a message written meanwhile reaches the model. */
20
+ export function signalText(taken) {
21
+ return `The person wrote this while you were working. What they asked before still stands unless this changes it:\n\n${taken.map((s) => s.message).join("\n\n")}`;
22
+ }
19
23
  const MAX_STEPS = 24;
20
- const SYSTEM = `You are the assistant of the engenty wizards studio. You stand on every page of the studio except the wizard editor, which has a chat of its own: the space's pages, the settings, the plugins' pages. You help the person who works here understand the app, set it up and find their way.
24
+ const SYSTEM = `You are Goose, the assistant of the engenty wizards studio. You stand on every page of the studio except the wizard editor, which has a chat of its own: the space's pages, the settings, the plugins' pages. You help the person who works here understand the app, set it up and find their way.
21
25
 
22
26
  You talk to the person. Answer in their language, briefly and warmly: a few sentences, a short list where it helps. Never mention JSON, ids, keys or tool names. Call it a space (German „Space“), never a project: the tools say project, the app says space.
23
27
 
@@ -641,12 +645,29 @@ function partOf(route) {
641
645
  const m = route.match(/^\/space\/(info|knowledge|data|results)/);
642
646
  return m ? m[1] : route === "/space" ? "info" : null;
643
647
  }
648
+ /** How large each tool is in the prompt: its description and its schema, in characters. */
649
+ function toolSizes(tools) {
650
+ return Object.entries(tools)
651
+ .map(([name, tool]) => {
652
+ let schema = "";
653
+ try {
654
+ schema = JSON.stringify(tool?.inputSchema ? z.toJSONSchema(tool.inputSchema, { unrepresentable: "any" }) : {});
655
+ }
656
+ catch {
657
+ // A provider's own tool (web search) has no schema of ours.
658
+ }
659
+ return { name, chars: String(tool?.description ?? "").length + schema.length };
660
+ })
661
+ .sort((a, b) => b.chars - a.chars);
662
+ }
644
663
  /** One turn of the studio assistant. */
645
664
  export async function runStudioAssistant(input) {
646
- let changed = false;
665
+ // How often the space or a plugin's data changed: a part of the answer changed something when
666
+ // this grew during it.
667
+ let changes = 0;
647
668
  const resolved = await textModel("high");
648
669
  const wrote = () => {
649
- changed = true;
670
+ changes++;
650
671
  input.onChanged?.();
651
672
  };
652
673
  const cards = [];
@@ -746,6 +767,31 @@ export async function runStudioAssistant(input) {
746
767
  ]
747
768
  .filter(Boolean)
748
769
  .join("\n\n");
770
+ input.onPrompt?.({
771
+ model: resolved.ref,
772
+ system: SYSTEM,
773
+ context,
774
+ history: history.messages.map((m) => ({ role: m.role, chars: m.content.length })),
775
+ tools: toolSizes(tools),
776
+ });
777
+ let reply = "";
778
+ // Where the part the result holds begins: a message written meanwhile cuts the answer there.
779
+ const from = { reply: 0, cards: 0, toolCalls: 0, changes: 0 };
780
+ const cut = () => {
781
+ const part = {
782
+ reply: reply.slice(from.reply).trim(),
783
+ changed: changes > from.changes,
784
+ cards: cards.slice(from.cards),
785
+ toolCalls: toolCalls.slice(from.toolCalls),
786
+ };
787
+ Object.assign(from, {
788
+ reply: reply.length,
789
+ cards: cards.length,
790
+ toolCalls: toolCalls.length,
791
+ changes,
792
+ });
793
+ return part;
794
+ };
749
795
  const stream = await agent.stream([
750
796
  ...history.messages.map((m) => m.role === "user"
751
797
  ? { role: "user", content: m.content }
@@ -755,11 +801,37 @@ export async function runStudioAssistant(input) {
755
801
  maxSteps: MAX_STEPS,
756
802
  abortSignal: input.signal,
757
803
  modelSettings: { maxOutputTokens: 8000 },
804
+ // What the person wrote meanwhile comes before the next step, after the tools' results.
805
+ prepareStep: async ({ stepNumber, messageList }) => {
806
+ const taken = stepNumber > 0 ? (input.takeSignals?.() ?? []) : [];
807
+ if (!taken.length) {
808
+ return;
809
+ }
810
+ await input.onSignal?.(taken, cut());
811
+ messageList.add({ role: "user", content: signalText(taken) }, "input");
812
+ return { messageList };
813
+ },
758
814
  });
759
- let reply = "";
815
+ const window = contextWindowOf(resolved.ref);
816
+ // A step lasts from the end of the one before – its tools' work included – to its own end.
817
+ let stepStarted = Date.now();
760
818
  for await (const chunk of stream.fullStream) {
761
819
  const payload = chunk.payload ?? chunk;
762
- if (chunk.type === "text-delta") {
820
+ if (chunk.type === "step-finish" && input.onStep) {
821
+ const usage = usageOf(payload.output?.usage);
822
+ input.onStep({
823
+ model: resolved.ref,
824
+ inputTokens: usage.inputTokens ?? 0,
825
+ outputTokens: usage.outputTokens ?? 0,
826
+ cachedInputTokens: usage.cachedInputTokens ?? 0,
827
+ ms: Date.now() - stepStarted,
828
+ costUsd: costOf(resolved, payload.output?.usage),
829
+ tools: (payload.output?.toolCalls ?? []).map((c) => c.payload?.toolName ?? c.toolName ?? ""),
830
+ window,
831
+ });
832
+ stepStarted = Date.now();
833
+ }
834
+ else if (chunk.type === "text-delta") {
763
835
  const text = payload.text ?? payload.delta ?? "";
764
836
  reply += text;
765
837
  input.onText?.(text);
@@ -780,5 +852,5 @@ export async function runStudioAssistant(input) {
780
852
  }
781
853
  }
782
854
  const usage = (await stream.totalUsage) ?? (await stream.usage);
783
- return { reply: reply.trim(), changed, cards, toolCalls, costUsd: costOf(resolved, usage) };
855
+ return { ...cut(), costUsd: costOf(resolved, usage) };
784
856
  }
@@ -0,0 +1,24 @@
1
+ const running = new Map();
2
+ export function startRun(runId, userId) {
3
+ running.set(runId, { userId, inbox: [] });
4
+ }
5
+ export function endRun(runId) {
6
+ running.delete(runId);
7
+ }
8
+ /** Puts a message into the person's running turn; false when that turn is over. */
9
+ export function signalRun(userId, runId, signal) {
10
+ const run = running.get(runId);
11
+ if (!run || run.userId !== userId) {
12
+ return false;
13
+ }
14
+ run.inbox.push(signal);
15
+ return true;
16
+ }
17
+ /** The messages that came in since the turn last looked, oldest first. */
18
+ export function takeSignals(runId) {
19
+ const run = running.get(runId);
20
+ if (!run?.inbox.length) {
21
+ return [];
22
+ }
23
+ return run.inbox.splice(0);
24
+ }
@@ -1,10 +1,10 @@
1
1
  ---
2
- title: The assistant
2
+ title: Goose, the assistant
3
3
  description: The chat at the bottom of every studio page. It knows the app, sees the page you are on, shows you where things are and fills the space in.
4
- version: 0.3.2
4
+ version: 0.3.7
5
5
  ---
6
6
 
7
- The assistant stands at the bottom of every page of the studio but the editor, which has a
7
+ Goose, the assistant, stands at the bottom of every page of the studio but the editor, which has a
8
8
  conversation of its own. It thinks on the High class of Text & reasoning: your subscription,
9
9
  your keys or credits ([Models and account](./models.md)).
10
10
 
@@ -26,20 +26,38 @@ a value into a field for you; you check it and save. It does not press buttons.
26
26
  | | |
27
27
  |---|---|
28
28
  | At the bottom | The default. Click the field or the engenty, and the chat rises above it |
29
- | Small | The engenty at the lower right; a click opens the chat beside it. Data and Results keep it small |
29
+ | Closed | ✕ in the header: the engenty at the lower right; a click opens the chat where it last stood. Data and Results start closed |
30
30
  | Sidebar | Beside the page, on a window 1,520 pixels wide or more |
31
31
 
32
32
  The buttons in the chat's header switch between them; the studio remembers your choice.
33
33
 
34
+ At the bottom, the chat moves out of the way of what it covers: drag it by the grip before its
35
+ name (or the engenty, while it is folded). It stays where you put it. Near the bottom it snaps to
36
+ the middle or the right; near the right side, higher up, it becomes the sidebar, and the
37
+ sidebar's grip pulls it out again. A dashed outline shows where it will snap. The handle on top of
38
+ the chat sets how tall the thread is; a click on it folds the chat away.
39
+
40
+ ## Writing while it works
41
+
42
+ You can write on while Goose answers. What you send waits above the field:
43
+
44
+ | | |
45
+ |---|---|
46
+ | Enter | Goose reads it before its next step, and keeps what you asked before unless the message changes it |
47
+ | ⌘↩ (Ctrl+↩) | It goes out as the next question once the answer is done |
48
+ | Send now | Stops the answer and sends it at once |
49
+
50
+ The stop button stands where Send does while the field is empty.
51
+
34
52
  ## One chat
35
53
 
36
54
  You have one chat, the same on every page and in every space, after a reload and on another
37
55
  device. Nobody else in the team sees it.
38
56
 
39
- - **New chat** (the menu at the chat's name) closes the chat with a short summary and starts an
40
- empty one. The assistant keeps the summaries of your last chats in mind.
57
+ - **New chat** (the pen in the header, or the menu at the chat's name) clears the chat at once and
58
+ sums the old one up meanwhile. Goose keeps the summaries of your last chats in mind.
41
59
  - **Earlier chats** lists the closed ones to read. Ask about something you discussed before, and
42
- the assistant searches them.
60
+ Goose searches them.
43
61
 
44
62
  ## Live chat
45
63
 
@@ -152,6 +152,11 @@ export const env = {
152
152
  /** Encrypts connected accounts and kept browser sessions; falls back to the app secret. */
153
153
  storeKey: str("STORE_ENC_KEY"),
154
154
  devLogin: process.env.NODE_ENV !== "production" && str("DEV_LOGIN") === "1",
155
+ /**
156
+ * `1`: the studio assistant's developer mode may be switched on here (steps, tools, prompt,
157
+ * the turns' events). A managed runtime offers it to the platform's admins instead.
158
+ */
159
+ assistantDev: str("ASSISTANT_DEV") === "1",
155
160
  /**
156
161
  * The Manage-App this runtime belongs to. Set = people sign in there, tenants come from its
157
162
  * tokens and model calls go through its gateway. Empty = the runtime runs alone with one tenant.
@@ -110,15 +110,15 @@ export function tenantMembers(id) {
110
110
  return call("GET", `/v1/tenants/${encodeURIComponent(id)}/members`);
111
111
  }
112
112
  const users = new Map();
113
- /** A person's own feature switches, as an admin set them at the Manage-App; cached a minute. */
114
- export async function userFeatures(userId) {
113
+ /** What the Manage-App says of a person beyond their token; cached a minute. */
114
+ export async function userInfo(userId) {
115
115
  const hit = users.get(userId);
116
116
  if (hit && Date.now() - hit.at < 60_000) {
117
117
  return hit.value;
118
118
  }
119
119
  const value = await call("GET", `/v1/users/${encodeURIComponent(userId)}`)
120
- .then((u) => u.features ?? {})
121
- .catch(() => hit?.value ?? {});
120
+ .then((u) => ({ features: u.features ?? {}, admin: u.admin === true }))
121
+ .catch(() => hit?.value ?? { features: {}, admin: false });
122
122
  users.set(userId, { at: Date.now(), value });
123
123
  return value;
124
124
  }
@@ -1013,6 +1013,12 @@ function priceOf(ref) {
1013
1013
  return (catalog.get(provider === null || provider === "gateway" ? id : `${provider}/${id}`)?.pricing ??
1014
1014
  {});
1015
1015
  }
1016
+ /** A model's context window in tokens, as the AI Gateway's catalog gives it; null where unknown. */
1017
+ export function contextWindowOf(ref) {
1018
+ const { provider, id } = parse(ref);
1019
+ const entry = catalog.get(provider === null || provider === "gateway" ? id : `${provider}/${id}`);
1020
+ return typeof entry?.context_window === "number" ? entry.context_window : null;
1021
+ }
1016
1022
  const n = (v, fallback) => {
1017
1023
  const x = Number(v);
1018
1024
  return Number.isFinite(x) ? x : fallback;
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { ASSISTANT_LAYOUTS, ASSISTANT_PAGE_BYTES, assistantTurnSchema, } from "@engenty-wizards/shared/assistant";
3
+ import { ASSISTANT_LAYOUTS, ASSISTANT_PAGE_BYTES, assistantSignalSchema, assistantTurnSchema, } from "@engenty-wizards/shared/assistant";
4
4
  import { MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
5
5
  import { CATEGORY_TYPES, whereSchema } from "@engenty-wizards/shared/knowledge";
6
6
  import { PROJECT_FILE_KINDS } from "@engenty-wizards/shared/projects";
@@ -11,10 +11,11 @@ import { Hono } from "hono";
11
11
  import { streamSSE } from "hono/streaming";
12
12
  import { nanoid } from "nanoid";
13
13
  import { z } from "zod";
14
- import { runStudioAssistant } from "../agents/studio-assistant.js";
14
+ import { runStudioAssistant, signalText } from "../agents/studio-assistant.js";
15
15
  import { chatEngine, setChatEngine, subscriptionClients } from "../agents/subscription.js";
16
16
  import { scanColors, scanFonts, scanImages, scanLogos } from "../agents/website.js";
17
17
  import { answerBrowser, awaitBrowser } from "../assistant/browser.js";
18
+ import { endRun, signalRun, startRun, takeSignals } from "../assistant/runs.js";
18
19
  import { accountOverview, linkedAccount, startLink, unlink } from "../auth/account.js";
19
20
  import { createLocalKey, deleteLocalKey, listLocalKeys } from "../auth/keys.js";
20
21
  import { appStates, connectApps } from "../cli/connect.js";
@@ -29,7 +30,7 @@ import { appleStatus } from "../harness/apple.js";
29
30
  import { freshAuth, loginEnv } from "../harness/env.js";
30
31
  import { detectHarness, detectHarnesses, HARNESS_IDS, harness, installSpec, } from "../harness/index.js";
31
32
  import { closeTerminal, openTerminal, resizeTerminal, subscribeTerminal, writeTerminal, } from "../harness/terminal.js";
32
- import { managed, tenantInfo, tenantMembers, userFeatures } from "../manage.js";
33
+ import { managed, tenantInfo, tenantMembers, userInfo } from "../manage.js";
33
34
  import { seenApps } from "../mcp/seen.js";
34
35
  import { generateImageMedia, generateSpeechMedia, generateVideoMedia } from "../media/generate.js";
35
36
  import { transcribeAudio } from "../media/transcribe.js";
@@ -190,6 +191,10 @@ async function testClass(cls, lang) {
190
191
  }
191
192
  }
192
193
  /** The size of an inline terminal, as the page lays it out. */
194
+ /** Whether the studio assistant's developer mode is offered to this person. */
195
+ async function assistantDevFor(user) {
196
+ return managed ? (await userInfo(user.id)).admin : env.assistantDev;
197
+ }
193
198
  const terminalSize = z.object({
194
199
  cols: z.number().int().min(10).max(500).optional(),
195
200
  rows: z.number().int().min(4).max(200).optional(),
@@ -203,7 +208,7 @@ export const studio = new Hono()
203
208
  const profile = await userProfile(user);
204
209
  // What the Manage-App says of the tenant: its plan and feature switches; and of the person.
205
210
  const info = managed ? await tenantInfo(user.tenantId).catch(() => null) : null;
206
- const own = managed ? await userFeatures(user.id) : null;
211
+ const person = managed ? await userInfo(user.id) : null;
207
212
  return c.json({
208
213
  user: { id: user.id, name: profile.name, email: profile.email, image: user.image ?? null },
209
214
  /** What the person says about themselves; name and e-mail are the account's when managed. */
@@ -215,7 +220,9 @@ export const studio = new Hono()
215
220
  plan: info?.plan ?? null,
216
221
  },
217
222
  /** The feature switches: the code's defaults, what the Manage-App says of the team and the person, this install's file. */
218
- features: features(managed ? { ...info?.features, ...own } : overview?.tenant.features),
223
+ features: features(managed ? { ...info?.features, ...person?.features } : overview?.tenant.features),
224
+ /** The studio assistant's developer mode may be switched on: ASSISTANT_DEV here, the platform's admins when managed. */
225
+ assistantDev: await assistantDevFor(user),
219
226
  mode: managed ? "managed" : "local",
220
227
  /** The tenant's balance; null where the runtime resolves models itself. */
221
228
  credits: managed ? await balanceCredits() : null,
@@ -317,6 +324,12 @@ export const studio = new Hono()
317
324
  }
318
325
  const voice = await generateSpeechMedia({ text, abortSignal: c.req.raw.signal });
319
326
  return c.body(new Uint8Array(voice.bytes), 200, { "content-type": voice.mime });
327
+ })
328
+ // A message written while a turn runs: the turn takes it before its next step or after its answer.
329
+ .post("/assistant/run/:runId/signal", async (c) => {
330
+ const body = assistantSignalSchema.parse(await c.req.json());
331
+ const taken = signalRun(c.get("user").id, c.req.param("runId"), body);
332
+ return c.json({ ok: taken }, taken ? 200 : 404);
320
333
  })
321
334
  // A browser tool's result: the turn that waits for it goes on.
322
335
  .post("/assistant/tool/:callId", async (c) => {
@@ -339,53 +352,100 @@ export const studio = new Hono()
339
352
  ? { route: body.page.route, title: body.page.title, view: body.page.view }
340
353
  : (body.page ?? null);
341
354
  const chat = await openChat(user);
342
- const history = await turnHistory(user, chat);
355
+ const debug = Boolean(body.debug) && (await assistantDevFor(user));
356
+ let history = await turnHistory(user, chat);
343
357
  const runId = nanoid(10);
344
358
  const userMessageId = await addMessage(chat.id, {
345
359
  role: "user",
346
360
  content: body.message,
347
361
  page: page ? { route: page.route, title: page.title } : null,
348
362
  });
363
+ startRun(runId, user.id);
349
364
  return streamSSE(c, async (stream) => {
350
365
  const abort = new AbortController();
351
366
  stream.onAbort(() => abort.abort());
352
367
  const send = (event, data) => stream.writeSSE({ event, data: JSON.stringify(data) });
353
- const messageId = nanoid(14);
368
+ let messageId = nanoid(14);
354
369
  await send("run_started", { threadId: chat.id, runId, messageId: userMessageId });
370
+ // The answer's text since the last message written meanwhile.
355
371
  let reply = "";
372
+ let changed = false;
373
+ let steps = 0;
374
+ /** What the answer said and did up to messages written meanwhile; null when nothing. */
375
+ const storePart = async (before) => {
376
+ changed ||= before.changed;
377
+ return before.reply || before.toolCalls.length || before.cards.length
378
+ ? await addMessage(chat.id, { role: "assistant", content: before.reply, ...before })
379
+ : null;
380
+ };
381
+ /** The messages written meanwhile, after the part of the answer before them. */
382
+ const storeSignals = async (taken, answerId) => {
383
+ for (const [i, s] of taken.entries()) {
384
+ const stored = await addMessage(chat.id, { role: "user", content: s.message });
385
+ await send("signal", {
386
+ ref: s.ref,
387
+ messageId: stored,
388
+ content: s.message,
389
+ answerId: i === 0 ? answerId : null,
390
+ });
391
+ }
392
+ reply = "";
393
+ messageId = nanoid(14);
394
+ };
356
395
  try {
357
- const result = await runStudioAssistant({
358
- user,
359
- chat,
360
- history,
361
- project,
362
- writable,
363
- message: body.message,
364
- page,
365
- browserTools: body.tools,
366
- lang: body.lang,
367
- signal: abort.signal,
368
- onText: (delta) => {
369
- reply += delta;
370
- void send("text", { messageId, delta });
371
- },
372
- onActivity: (label) => void send("activity", { label }),
373
- onChanged: () => void send("changed", {}),
374
- onCard: (card) => void send("card", card),
375
- onToolCall: (call) => void send("tool_call", call),
376
- onToolResult: (result) => void send("tool_result", result),
377
- callBrowser: (call) => awaitBrowser(user.id, call.toolCallId, abort.signal),
378
- });
379
- reply =
380
- result.reply || (result.changed ? (body.lang === "de" ? "Erledigt." : "Done.") : "");
381
- const stored = await addMessage(chat.id, {
382
- role: "assistant",
383
- content: reply,
384
- cards: result.cards,
385
- toolCalls: result.toolCalls,
386
- changed: result.changed,
387
- });
388
- await send("done", { messageId: stored, reply, changed: result.changed });
396
+ let message = body.message;
397
+ for (;;) {
398
+ const result = await runStudioAssistant({
399
+ user,
400
+ chat,
401
+ history,
402
+ project,
403
+ writable,
404
+ message,
405
+ page,
406
+ browserTools: body.tools,
407
+ lang: body.lang,
408
+ signal: abort.signal,
409
+ onText: (delta) => {
410
+ reply += delta;
411
+ void send("text", { messageId, delta });
412
+ },
413
+ onActivity: (label) => void send("activity", { label }),
414
+ onChanged: () => void send("changed", {}),
415
+ onCard: (card) => void send("card", card),
416
+ onToolCall: (call) => void send("tool_call", call),
417
+ onToolResult: (result) => void send("tool_result", result),
418
+ callBrowser: (call) => awaitBrowser(user.id, call.toolCallId, abort.signal),
419
+ takeSignals: () => takeSignals(runId),
420
+ onSignal: async (taken, before) => storeSignals(taken, await storePart(before)),
421
+ ...(debug
422
+ ? {
423
+ onPrompt: (prompt) => void send("prompt", prompt),
424
+ onStep: (step) => void send("step", { ...step, step: ++steps }),
425
+ }
426
+ : {}),
427
+ });
428
+ // Written after the answer's last step: the turn goes on with it.
429
+ const left = takeSignals(runId);
430
+ if (!left.length || abort.signal.aborted) {
431
+ changed ||= result.changed;
432
+ reply = result.reply || (changed ? (body.lang === "de" ? "Erledigt." : "Done.") : "");
433
+ const stored = await addMessage(chat.id, {
434
+ role: "assistant",
435
+ content: reply,
436
+ cards: result.cards,
437
+ toolCalls: result.toolCalls,
438
+ changed: result.changed,
439
+ });
440
+ await send("done", { messageId: stored, reply, changed });
441
+ break;
442
+ }
443
+ // The next answer knows this one: it is stored before the history is read again.
444
+ const answerId = await storePart(result);
445
+ history = await turnHistory(user, chat);
446
+ await storeSignals(left, answerId);
447
+ message = signalText(left);
448
+ }
389
449
  }
390
450
  catch (err) {
391
451
  console.error("[studio-assistant]", err);
@@ -400,6 +460,7 @@ export const studio = new Hono()
400
460
  : "Something went wrong. Please try again.",
401
461
  });
402
462
  }
463
+ endRun(runId);
403
464
  // Older messages fold into the chat's running summary once enough have piled up.
404
465
  void foldChat(chat.id).catch((err) => console.error("[studio-assistant] fold:", err));
405
466
  });
@@ -9,8 +9,8 @@ import { ServiceError } from "./errors.js";
9
9
  import { trigramQuery } from "./project-index.js";
10
10
  /**
11
11
  * The studio assistant's chats: one open chat per person and tenant, the same on every page and
12
- * space. "New chat" closes it with a dense summary; every turn of the next one carries the
13
- * summaries of the last chats, and older ones are found with `searchChats`.
12
+ * space. "New chat" closes it at once and writes its dense summary meanwhile; every turn of the
13
+ * next one carries the summaries of the last chats, and older ones are found with `searchChats`.
14
14
  */
15
15
  /** Messages of the open chat a turn sends as they are; older ones live in its running summary. */
16
16
  export const TURN_MESSAGES = 12;
@@ -74,6 +74,7 @@ export async function addMessage(chatId, message) {
74
74
  }
75
75
  /** What a turn of the open chat carries besides the new message. */
76
76
  export async function turnHistory(user, chat) {
77
+ await closedSummaries(user);
77
78
  const all = await messagesOf(chat.id);
78
79
  const earlier = await db.query.assistantChat.findMany({
79
80
  where: and(mine(user), isNotNull(schema.assistantChat.closedAt)),
@@ -81,8 +82,11 @@ export async function turnHistory(user, chat) {
81
82
  limit: EARLIER_IN_TURN,
82
83
  });
83
84
  return {
85
+ // A part of an answer that only called tools (before a message written meanwhile) has no
86
+ // text: models refuse empty messages.
84
87
  messages: all
85
88
  .slice(chat.folded)
89
+ .filter((m) => m.content.trim())
86
90
  .slice(-TURN_MESSAGES)
87
91
  .map(({ role, content }) => ({ role, content })),
88
92
  running: chat.summary,
@@ -170,9 +174,45 @@ export async function foldChat(chatId) {
170
174
  .set({ summary, folded: upTo })
171
175
  .where(and(eq(schema.assistantChat.id, chat.id), eq(schema.assistantChat.folded, chat.folded)));
172
176
  }
177
+ /** Summaries of chats "New chat" closed, still being written: by chat id, with whose chat it is. */
178
+ const summarising = new Map();
179
+ const whose = (user) => `${user.tenantId}:${user.id}`;
180
+ /** Writes the summary and the title of a closed chat. */
181
+ function summariseClosed(user, chat) {
182
+ const running = summarising.get(chat.id);
183
+ if (running) {
184
+ return running.done;
185
+ }
186
+ const done = (async () => {
187
+ const all = await messagesOf(chat.id);
188
+ const { title, summary } = await summarise(all.slice(chat.folded), chat.summary);
189
+ await db
190
+ .update(schema.assistantChat)
191
+ .set({ ...(title ? { title } : {}), summary })
192
+ .where(eq(schema.assistantChat.id, chat.id));
193
+ })()
194
+ .catch((err) => console.error("[assistant] summary of a closed chat:", err.message))
195
+ .finally(() => summarising.delete(chat.id));
196
+ summarising.set(chat.id, { user: whose(user), done });
197
+ return done;
198
+ }
199
+ /**
200
+ * Waits for the summaries of the person's closed chats: those being written, and any a stopped
201
+ * runtime left unwritten.
202
+ */
203
+ export async function closedSummaries(user) {
204
+ const unwritten = await db.query.assistantChat.findMany({
205
+ where: and(mine(user), isNotNull(schema.assistantChat.closedAt), isNull(schema.assistantChat.summary)),
206
+ });
207
+ await Promise.all([
208
+ ...unwritten.map((chat) => summariseClosed(user, chat)),
209
+ ...[...summarising.values()].filter((s) => s.user === whose(user)).map((s) => s.done),
210
+ ]);
211
+ }
173
212
  /**
174
- * "New chat": the open chat is summarised and closed (kept to read under "Earlier chats"), and
175
- * an empty one opens. An open chat without messages stays as it is.
213
+ * "New chat": the open chat is closed at once (kept to read under "Earlier chats", titled by its
214
+ * first question until its summary is written) and an empty one opens. An open chat without
215
+ * messages stays as it is.
176
216
  */
177
217
  export async function newChat(user) {
178
218
  const chat = await openChat(user);
@@ -180,11 +220,12 @@ export async function newChat(user) {
180
220
  if (!all.length) {
181
221
  return { id: chat.id, messages: [] };
182
222
  }
183
- const { title, summary } = await summarise(all.slice(chat.folded), chat.summary);
223
+ const firstAsk = all.find((m) => m.role === "user")?.content ?? "";
184
224
  await db
185
225
  .update(schema.assistantChat)
186
- .set({ title, summary, closedAt: new Date() })
226
+ .set({ title: firstAsk.replace(/\s+/g, " ").trim().slice(0, 60), closedAt: new Date() })
187
227
  .where(eq(schema.assistantChat.id, chat.id));
228
+ void summariseClosed(user, chat);
188
229
  const next = await openChat(user);
189
230
  return { id: next.id, messages: [] };
190
231
  }
@@ -199,6 +240,8 @@ export async function earlierChats(user, limit = 50) {
199
240
  id: c.id,
200
241
  title: c.title ?? "",
201
242
  summary: c.summary ?? "",
243
+ /** Its summary is still being written. */
244
+ summarising: summarising.has(c.id),
202
245
  startedAt: c.createdAt.toISOString(),
203
246
  closedAt: c.closedAt.toISOString(),
204
247
  }));
@@ -1 +1 @@
1
- import{r as e}from"./rolldown-runtime-hePW80VL.js";import{n as t,t as n}from"./jsx-runtime-BNakU3Ej.js";import{t as r}from"./logo-C4V2_3Ar.js";import{t as i}from"./preload-helper-CNEI7N0N.js";import{Ct as a,Dt as o,Tt as s,Zt as c,bt as l,ft as u,ht as d,jt as f,kt as p,mt as m,nn as h,pt as g}from"./ui-BOCXzzHi.js";import{ft as _,pt as v,wt as y}from"./index-Df6UxElH.js";import{c as b,f as x}from"./settings-menu-DV5D91TD.js";import{ReleaseNotes as S,loadReleases as C,releaseDate as w,releaseLabel as T}from"./ChangelogDialog-Cv81t7tj.js";var E=e(t(),1),D=[{name:`engenty wizards`,description:{de:`KI-Wizards aus einer Beschreibung, mit einem Link geteilt.`,en:`AI wizards built from a description, shared with a link.`},homepage:`https://github.com/engenty/wizards`,license:`FSL-1.1-MIT`},{name:`engenty`,description:{de:`Konnektoren, Datentabellen, Suche und die Engenties stammen aus dem engenty-Framework.`,en:`Connectors, data tables, search and the engenties come from the engenty framework.`},homepage:`https://github.com/engenty/engenty`,license:`FSL-1.1-MIT`}],O=[{name:`Mastra`,description:{de:`Agent-Framework hinter den KI-Schritten.`,en:`Agent framework behind the AI steps.`},homepage:`https://github.com/mastra-ai/mastra`,license:`Apache-2.0`},{name:`AI SDK`,description:{de:`Modell-Aufrufe für Text, Bild, Video und Sprache.`,en:`Model calls for text, image, video and speech.`},homepage:`https://github.com/vercel/ai`,license:`Apache-2.0`},{name:`Model Context Protocol`,description:{de:`MCP-Server zum Bauen von Wizards, MCP-Client für Werkzeuge der Schritte.`,en:`MCP server for building wizards, MCP client for the steps' tools.`},homepage:`https://github.com/modelcontextprotocol/typescript-sdk`,license:`MIT`},{name:`Hono`,description:{de:`HTTP-API der Runtime.`,en:`The runtime's HTTP API.`},homepage:`https://github.com/honojs/hono`,license:`MIT`},{name:`Drizzle ORM`,description:{de:`Schema und Migrationen der Datenbanken.`,en:`Schema and migrations of the databases.`},homepage:`https://github.com/drizzle-team/drizzle-orm`,license:`Apache-2.0`},{name:`libSQL`,description:{de:`Eine Datenbank je Mandant, lokal als Datei oder bei Turso.`,en:`One database per tenant, a local file or at Turso.`},homepage:`https://github.com/tursodatabase/libsql`,license:`MIT`},{name:`Playwright`,description:{de:`Browser-Schritte sowie PDF- und PNG-Export.`,en:`Browser steps and the PDF and PNG export.`},homepage:`https://github.com/microsoft/playwright`,license:`Apache-2.0`},{name:`agentOS`,description:{de:`Sandbox für Code-Schritte ohne Docker.`,en:`Sandbox for code steps without Docker.`},homepage:`https://github.com/rivet-dev/agentos`,license:`Apache-2.0`},{name:`React`,description:{de:`UI-Bibliothek der Web-App.`,en:`UI library of the web app.`},homepage:`https://github.com/facebook/react`,license:`MIT`},{name:`Vite`,description:{de:`Build-Werkzeug der Web-App.`,en:`Build tool of the web app.`},homepage:`https://github.com/vitejs/vite`,license:`MIT`},{name:`Tailwind CSS`,description:{de:`Styling der Oberfläche.`,en:`Styling of the interface.`},homepage:`https://github.com/tailwindlabs/tailwindcss`,license:`MIT`},{name:`React Flow`,description:{de:`Das Diagramm der Schritte im Editor.`,en:`The diagram of steps in the editor.`},homepage:`https://github.com/xyflow/xyflow`,license:`MIT`},{name:`TanStack Query`,description:{de:`Server-Daten in der Web-App.`,en:`Server data in the web app.`},homepage:`https://github.com/TanStack/query`,license:`MIT`},{name:`xterm.js`,description:{de:`Das Terminal auf der Seite, in dem sich KI-Clients anmelden.`,en:`The terminal on the page where AI clients sign in.`},homepage:`https://github.com/xtermjs/xterm.js`,license:`MIT`},{name:`Lucide`,description:{de:`Icons.`,en:`Icons.`},homepage:`https://github.com/lucide-icons/lucide`,license:`ISC`},{name:`Geist & Geist Mono`,description:{de:`Schrift der Oberfläche.`,en:`Typeface of the interface.`},homepage:`https://github.com/vercel/geist-font`,license:`OFL-1.1`},{name:`Space Grotesk`,description:{de:`Schrift der Überschriften.`,en:`Typeface of the headings.`},homepage:`https://github.com/floriankarsten/space-grotesk`,license:`OFL-1.1`},{name:`git-cliff`,description:{de:`Erzeugt den Changelog aus den Commits.`,en:`Generates the changelog from the commits.`},homepage:`https://github.com/orhun/git-cliff`,license:`MIT OR Apache-2.0`}],k=e(n(),1),A=(e,...t)=>t.some(t=>t?.toLowerCase().includes(e));function j({name:e,homepage:t,version:n,license:r,description:i}){return(0,k.jsxs)(`li`,{className:`py-2.5`,children:[(0,k.jsxs)(`div`,{className:`flex flex-wrap items-baseline gap-x-2.5 gap-y-0.5`,children:[t?(0,k.jsxs)(`a`,{href:t,target:`_blank`,rel:`noreferrer`,className:`inline-flex items-center gap-1 font-medium text-[0.875rem] text-ink underline-offset-4 hover:underline`,children:[e,(0,k.jsx)(c,{"aria-hidden":!0,className:`size-3 text-ink-4`})]}):(0,k.jsx)(`span`,{className:`font-medium text-[0.875rem] text-ink`,children:e}),n?(0,k.jsx)(`span`,{className:`text-[0.75rem] text-ink-3 tabular-nums`,children:n}):null,(0,k.jsx)(`span`,{className:`text-[0.75rem] text-ink-3`,children:r})]}),i?(0,k.jsx)(`p`,{className:`mt-0.5 text-[0.8125rem] text-ink-3`,children:i}):null]})}function M({title:e,hint:t,children:n}){return(0,k.jsxs)(`section`,{className:`mt-6`,children:[(0,k.jsx)(`h3`,{className:`border-border-soft border-b pb-2 font-display font-semibold text-[1rem]`,children:e}),t?(0,k.jsx)(`p`,{className:`mt-2 text-[0.8125rem] text-ink-3`,children:t}):null,n]})}function N({credits:e}){return(0,k.jsx)(`ul`,{className:`divide-y divide-border-soft`,children:e.map(e=>(0,k.jsx)(j,{name:e.name,homepage:e.homepage,license:e.license,description:e.description[o]},e.name))})}function P({label:e,packages:t}){return(0,k.jsxs)(`div`,{className:`mt-4`,children:[(0,k.jsx)(`h4`,{className:`font-medium text-[0.75rem] text-ink-3 tracking-wide`,children:e}),(0,k.jsx)(`ul`,{className:`divide-y divide-border-soft`,children:t.map(e=>(0,k.jsx)(j,{...e},e.name))})]})}function F({active:e}){let[t,n]=(0,E.useState)(null),[r,a]=(0,E.useState)(``);(0,E.useEffect)(()=>{if(!e||t)return;let r=!1;return i(()=>import(`./oss-credits-BQQX0kvk.js`).then(e=>{r||n(e.default)}),[]),()=>{r=!0}},[e,t]),(0,E.useEffect)(()=>{e||a(``)},[e]);let o=r.trim().toLowerCase(),s=e=>o?e.filter(e=>A(o,e.name,e.license,e.description.de,e.description.en)):e,c=e=>o?e.filter(e=>A(o,e.name,e.license,e.version)):e,u=s(D),f=s(O),m=t?[{id:`shared`,label:p(`about.creditsShared`),packages:c(t.shared)},...t.groups.map(e=>({id:e.id,label:e.label,packages:c(e.packages)}))].filter(e=>e.packages.length>0):[],h=u.length===0&&f.length===0&&m.length===0;return(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`p`,{className:`mb-4 text-[0.875rem] text-ink-3`,children:p(`about.creditsHint`)}),(0,k.jsx)(d,{type:`search`,value:r,placeholder:p(`about.search`),onChange:e=>a(e.target.value)}),u.length>0?(0,k.jsx)(M,{title:p(`about.creditsProduct`),children:(0,k.jsx)(N,{credits:u})}):null,f.length>0?(0,k.jsx)(M,{title:p(`about.creditsBuiltWith`),children:(0,k.jsx)(N,{credits:f})}):null,t===null?(0,k.jsx)(`div`,{className:`flex justify-center py-10 text-ink-3`,children:(0,k.jsx)(l,{})}):m.length>0?(0,k.jsx)(M,{title:p(`about.creditsDeps`),hint:o?void 0:p(`about.creditsDepsHint`),children:m.map(e=>(0,k.jsx)(P,{label:e.label,packages:e.packages},e.id))}):null,t!==null&&h?(0,k.jsx)(g,{children:p(`about.noMatches`)}):null]})}var I=[`new`,`earlier`,`credits`],L=e=>p(e===`new`?`about.tab.new`:e===`earlier`?`about.tab.earlier`:`about.credits`),R=e=>e===1?p(`about.change`):p(`about.changes`,{n:e});function z({open:e,onClose:t,me:n}){let i=n?.mode===`local`,{data:o,latest:c}=v(i&&e),[d,g]=(0,E.useState)(null),[D,O]=(0,E.useState)(`new`),A=x(I.indexOf(D)),j=o?.newer&&c?c:y;(0,E.useEffect)(()=>{if(!e)return;let t=!1;return g(null),C(j===`0.3.7`?void 0:j).then(e=>{t||g(e)}),()=>{t=!0}},[e,j]);let M=d?.find(e=>e.version===`v${j}`)??d?.[0]??null,N=d?.filter(e=>e!==M)??[],P=i?o?.kind??null:`cloud`;return(0,k.jsxs)(u,{open:e,onClose:t,wide:!0,height:`tall`,children:[(0,k.jsx)(`div`,{className:`-mt-2 -mr-2 sticky top-0 z-10 flex h-0 justify-end`,children:(0,k.jsx)(m,{label:p(`common.close`),onClick:t,children:(0,k.jsx)(f,{className:`size-4`})})}),(0,k.jsxs)(`div`,{className:`flex flex-col items-center pt-4 text-center`,children:[(0,k.jsx)(r,{size:96}),(0,k.jsxs)(`div`,{className:`mt-4 font-display font-semibold text-[1.5rem] tracking-tight`,children:[`engenty`,(0,k.jsx)(`span`,{className:`text-ember`,children:`.`}),(0,k.jsx)(`span`,{className:`ml-1.5 font-normal text-ink-3`,children:`wizards`})]}),(0,k.jsxs)(`div`,{className:`mt-1 text-[0.9375rem] text-ink-3 tabular-nums`,children:[p(`about.version`,{v:y}),P?` · ${p(`about.kind.${P}`)}`:``]})]}),i?(0,k.jsx)(`div`,{className:`mt-5`,children:(0,k.jsx)(_,{enabled:e})}):null,(0,k.jsx)(`div`,{className:`mt-6 flex justify-center`,children:(0,k.jsx)(a,{value:D,onChange:O,options:I.map(e=>({id:e,label:L(e)})),label:p(`about.title`)})}),(0,k.jsxs)(`div`,{className:s(`mt-5`,b(A)),children:[D===`new`?d===null?(0,k.jsx)(`div`,{className:`flex justify-center py-6 text-ink-3`,children:(0,k.jsx)(l,{})}):M?(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`h3`,{className:`flex items-baseline gap-3 border-border-soft border-b pb-2 font-display font-semibold text-[1rem]`,children:[T(M),(0,k.jsx)(`span`,{className:`font-normal font-sans text-[0.8125rem] text-ink-3 tabular-nums`,children:w(M)})]}),(0,k.jsx)(`div`,{className:`mt-3`,children:(0,k.jsx)(S,{release:M})})]}):(0,k.jsx)(`p`,{className:`text-[0.875rem] text-ink-3`,children:p(`about.changelogEmpty`)}):null,D===`earlier`?(0,k.jsx)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:N.map(e=>(0,k.jsxs)(`details`,{className:`group/r py-2`,children:[(0,k.jsxs)(`summary`,{className:`flex cursor-pointer list-none items-baseline gap-3 text-[0.875rem] [&::-webkit-details-marker]:hidden`,children:[(0,k.jsx)(h,{className:`size-3.5 shrink-0 self-center text-ink-4 transition group-open/r:rotate-90`}),(0,k.jsx)(`span`,{className:`font-medium text-ink tabular-nums`,children:T(e)}),(0,k.jsx)(`span`,{className:`text-[0.8125rem] text-ink-3 tabular-nums`,children:w(e)}),(0,k.jsx)(`span`,{className:`ml-auto text-[0.8125rem] text-ink-4`,children:R(e.commits.length)})]}),(0,k.jsx)(`div`,{className:`mt-2 pl-6`,children:(0,k.jsx)(S,{release:e})})]},e.version??`unreleased`))}):null,D===`credits`?(0,k.jsx)(F,{active:!0}):null]},D)]})}export{z as AboutDialog};
1
+ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{n as t,t as n}from"./jsx-runtime-BNakU3Ej.js";import{t as r}from"./logo-C4V2_3Ar.js";import{t as i}from"./preload-helper-CNEI7N0N.js";import{$t as a,At as o,Et as s,Mt as c,Ot as l,gt as u,ht as d,in as f,mt as p,pt as m,wt as h,xt as g}from"./ui-DrjXmHdO.js";import{ft as _,pt as v,wt as y}from"./index-DnQgOKOQ.js";import{c as b,f as x}from"./settings-menu-JuSRq6NP.js";import{ReleaseNotes as S,loadReleases as C,releaseDate as w,releaseLabel as T}from"./ChangelogDialog-DsSEkayM.js";var E=e(t(),1),D=[{name:`engenty wizards`,description:{de:`KI-Wizards aus einer Beschreibung, mit einem Link geteilt.`,en:`AI wizards built from a description, shared with a link.`},homepage:`https://github.com/engenty/wizards`,license:`FSL-1.1-MIT`},{name:`engenty`,description:{de:`Konnektoren, Datentabellen, Suche und die Engenties stammen aus dem engenty-Framework.`,en:`Connectors, data tables, search and the engenties come from the engenty framework.`},homepage:`https://github.com/engenty/engenty`,license:`FSL-1.1-MIT`}],O=[{name:`Mastra`,description:{de:`Agent-Framework hinter den KI-Schritten.`,en:`Agent framework behind the AI steps.`},homepage:`https://github.com/mastra-ai/mastra`,license:`Apache-2.0`},{name:`AI SDK`,description:{de:`Modell-Aufrufe für Text, Bild, Video und Sprache.`,en:`Model calls for text, image, video and speech.`},homepage:`https://github.com/vercel/ai`,license:`Apache-2.0`},{name:`Model Context Protocol`,description:{de:`MCP-Server zum Bauen von Wizards, MCP-Client für Werkzeuge der Schritte.`,en:`MCP server for building wizards, MCP client for the steps' tools.`},homepage:`https://github.com/modelcontextprotocol/typescript-sdk`,license:`MIT`},{name:`Hono`,description:{de:`HTTP-API der Runtime.`,en:`The runtime's HTTP API.`},homepage:`https://github.com/honojs/hono`,license:`MIT`},{name:`Drizzle ORM`,description:{de:`Schema und Migrationen der Datenbanken.`,en:`Schema and migrations of the databases.`},homepage:`https://github.com/drizzle-team/drizzle-orm`,license:`Apache-2.0`},{name:`libSQL`,description:{de:`Eine Datenbank je Mandant, lokal als Datei oder bei Turso.`,en:`One database per tenant, a local file or at Turso.`},homepage:`https://github.com/tursodatabase/libsql`,license:`MIT`},{name:`Playwright`,description:{de:`Browser-Schritte sowie PDF- und PNG-Export.`,en:`Browser steps and the PDF and PNG export.`},homepage:`https://github.com/microsoft/playwright`,license:`Apache-2.0`},{name:`agentOS`,description:{de:`Sandbox für Code-Schritte ohne Docker.`,en:`Sandbox for code steps without Docker.`},homepage:`https://github.com/rivet-dev/agentos`,license:`Apache-2.0`},{name:`React`,description:{de:`UI-Bibliothek der Web-App.`,en:`UI library of the web app.`},homepage:`https://github.com/facebook/react`,license:`MIT`},{name:`Vite`,description:{de:`Build-Werkzeug der Web-App.`,en:`Build tool of the web app.`},homepage:`https://github.com/vitejs/vite`,license:`MIT`},{name:`Tailwind CSS`,description:{de:`Styling der Oberfläche.`,en:`Styling of the interface.`},homepage:`https://github.com/tailwindlabs/tailwindcss`,license:`MIT`},{name:`React Flow`,description:{de:`Das Diagramm der Schritte im Editor.`,en:`The diagram of steps in the editor.`},homepage:`https://github.com/xyflow/xyflow`,license:`MIT`},{name:`TanStack Query`,description:{de:`Server-Daten in der Web-App.`,en:`Server data in the web app.`},homepage:`https://github.com/TanStack/query`,license:`MIT`},{name:`xterm.js`,description:{de:`Das Terminal auf der Seite, in dem sich KI-Clients anmelden.`,en:`The terminal on the page where AI clients sign in.`},homepage:`https://github.com/xtermjs/xterm.js`,license:`MIT`},{name:`Lucide`,description:{de:`Icons.`,en:`Icons.`},homepage:`https://github.com/lucide-icons/lucide`,license:`ISC`},{name:`Geist & Geist Mono`,description:{de:`Schrift der Oberfläche.`,en:`Typeface of the interface.`},homepage:`https://github.com/vercel/geist-font`,license:`OFL-1.1`},{name:`Space Grotesk`,description:{de:`Schrift der Überschriften.`,en:`Typeface of the headings.`},homepage:`https://github.com/floriankarsten/space-grotesk`,license:`OFL-1.1`},{name:`git-cliff`,description:{de:`Erzeugt den Changelog aus den Commits.`,en:`Generates the changelog from the commits.`},homepage:`https://github.com/orhun/git-cliff`,license:`MIT OR Apache-2.0`}],k=e(n(),1),A=(e,...t)=>t.some(t=>t?.toLowerCase().includes(e));function j({name:e,homepage:t,version:n,license:r,description:i}){return(0,k.jsxs)(`li`,{className:`py-2.5`,children:[(0,k.jsxs)(`div`,{className:`flex flex-wrap items-baseline gap-x-2.5 gap-y-0.5`,children:[t?(0,k.jsxs)(`a`,{href:t,target:`_blank`,rel:`noreferrer`,className:`inline-flex items-center gap-1 font-medium text-[0.875rem] text-ink underline-offset-4 hover:underline`,children:[e,(0,k.jsx)(a,{"aria-hidden":!0,className:`size-3 text-ink-4`})]}):(0,k.jsx)(`span`,{className:`font-medium text-[0.875rem] text-ink`,children:e}),n?(0,k.jsx)(`span`,{className:`text-[0.75rem] text-ink-3 tabular-nums`,children:n}):null,(0,k.jsx)(`span`,{className:`text-[0.75rem] text-ink-3`,children:r})]}),i?(0,k.jsx)(`p`,{className:`mt-0.5 text-[0.8125rem] text-ink-3`,children:i}):null]})}function M({title:e,hint:t,children:n}){return(0,k.jsxs)(`section`,{className:`mt-6`,children:[(0,k.jsx)(`h3`,{className:`border-border-soft border-b pb-2 font-display font-semibold text-[1rem]`,children:e}),t?(0,k.jsx)(`p`,{className:`mt-2 text-[0.8125rem] text-ink-3`,children:t}):null,n]})}function N({credits:e}){return(0,k.jsx)(`ul`,{className:`divide-y divide-border-soft`,children:e.map(e=>(0,k.jsx)(j,{name:e.name,homepage:e.homepage,license:e.license,description:e.description[l]},e.name))})}function P({label:e,packages:t}){return(0,k.jsxs)(`div`,{className:`mt-4`,children:[(0,k.jsx)(`h4`,{className:`font-medium text-[0.75rem] text-ink-3 tracking-wide`,children:e}),(0,k.jsx)(`ul`,{className:`divide-y divide-border-soft`,children:t.map(e=>(0,k.jsx)(j,{...e},e.name))})]})}function F({active:e}){let[t,n]=(0,E.useState)(null),[r,a]=(0,E.useState)(``);(0,E.useEffect)(()=>{if(!e||t)return;let r=!1;return i(()=>import(`./oss-credits-BQQX0kvk.js`).then(e=>{r||n(e.default)}),[]),()=>{r=!0}},[e,t]),(0,E.useEffect)(()=>{e||a(``)},[e]);let s=r.trim().toLowerCase(),c=e=>s?e.filter(e=>A(s,e.name,e.license,e.description.de,e.description.en)):e,l=e=>s?e.filter(e=>A(s,e.name,e.license,e.version)):e,d=c(D),f=c(O),m=t?[{id:`shared`,label:o(`about.creditsShared`),packages:l(t.shared)},...t.groups.map(e=>({id:e.id,label:e.label,packages:l(e.packages)}))].filter(e=>e.packages.length>0):[],h=d.length===0&&f.length===0&&m.length===0;return(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`p`,{className:`mb-4 text-[0.875rem] text-ink-3`,children:o(`about.creditsHint`)}),(0,k.jsx)(u,{type:`search`,value:r,placeholder:o(`about.search`),onChange:e=>a(e.target.value)}),d.length>0?(0,k.jsx)(M,{title:o(`about.creditsProduct`),children:(0,k.jsx)(N,{credits:d})}):null,f.length>0?(0,k.jsx)(M,{title:o(`about.creditsBuiltWith`),children:(0,k.jsx)(N,{credits:f})}):null,t===null?(0,k.jsx)(`div`,{className:`flex justify-center py-10 text-ink-3`,children:(0,k.jsx)(g,{})}):m.length>0?(0,k.jsx)(M,{title:o(`about.creditsDeps`),hint:s?void 0:o(`about.creditsDepsHint`),children:m.map(e=>(0,k.jsx)(P,{label:e.label,packages:e.packages},e.id))}):null,t!==null&&h?(0,k.jsx)(p,{children:o(`about.noMatches`)}):null]})}var I=[`new`,`earlier`,`credits`],L=e=>o(e===`new`?`about.tab.new`:e===`earlier`?`about.tab.earlier`:`about.credits`),R=e=>e===1?o(`about.change`):o(`about.changes`,{n:e});function z({open:e,onClose:t,me:n}){let i=n?.mode===`local`,{data:a,latest:l}=v(i&&e),[u,p]=(0,E.useState)(null),[D,O]=(0,E.useState)(`new`),A=x(I.indexOf(D)),j=a?.newer&&l?l:y;(0,E.useEffect)(()=>{if(!e)return;let t=!1;return p(null),C(j===`0.3.8`?void 0:j).then(e=>{t||p(e)}),()=>{t=!0}},[e,j]);let M=u?.find(e=>e.version===`v${j}`)??u?.[0]??null,N=u?.filter(e=>e!==M)??[],P=i?a?.kind??null:`cloud`;return(0,k.jsxs)(m,{open:e,onClose:t,wide:!0,height:`tall`,children:[(0,k.jsx)(`div`,{className:`-mt-2 -mr-2 sticky top-0 z-10 flex h-0 justify-end`,children:(0,k.jsx)(d,{label:o(`common.close`),onClick:t,children:(0,k.jsx)(c,{className:`size-4`})})}),(0,k.jsxs)(`div`,{className:`flex flex-col items-center pt-4 text-center`,children:[(0,k.jsx)(r,{size:96}),(0,k.jsxs)(`div`,{className:`mt-4 font-display font-semibold text-[1.5rem] tracking-tight`,children:[`engenty`,(0,k.jsx)(`span`,{className:`text-ember`,children:`.`}),(0,k.jsx)(`span`,{className:`ml-1.5 font-normal text-ink-3`,children:`wizards`})]}),(0,k.jsxs)(`div`,{className:`mt-1 text-[0.9375rem] text-ink-3 tabular-nums`,children:[o(`about.version`,{v:y}),P?` · ${o(`about.kind.${P}`)}`:``]})]}),i?(0,k.jsx)(`div`,{className:`mt-5`,children:(0,k.jsx)(_,{enabled:e})}):null,(0,k.jsx)(`div`,{className:`mt-6 flex justify-center`,children:(0,k.jsx)(h,{value:D,onChange:O,options:I.map(e=>({id:e,label:L(e)})),label:o(`about.title`)})}),(0,k.jsxs)(`div`,{className:s(`mt-5`,b(A)),children:[D===`new`?u===null?(0,k.jsx)(`div`,{className:`flex justify-center py-6 text-ink-3`,children:(0,k.jsx)(g,{})}):M?(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`h3`,{className:`flex items-baseline gap-3 border-border-soft border-b pb-2 font-display font-semibold text-[1rem]`,children:[T(M),(0,k.jsx)(`span`,{className:`font-normal font-sans text-[0.8125rem] text-ink-3 tabular-nums`,children:w(M)})]}),(0,k.jsx)(`div`,{className:`mt-3`,children:(0,k.jsx)(S,{release:M})})]}):(0,k.jsx)(`p`,{className:`text-[0.875rem] text-ink-3`,children:o(`about.changelogEmpty`)}):null,D===`earlier`?(0,k.jsx)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:N.map(e=>(0,k.jsxs)(`details`,{className:`group/r py-2`,children:[(0,k.jsxs)(`summary`,{className:`flex cursor-pointer list-none items-baseline gap-3 text-[0.875rem] [&::-webkit-details-marker]:hidden`,children:[(0,k.jsx)(f,{className:`size-3.5 shrink-0 self-center text-ink-4 transition group-open/r:rotate-90`}),(0,k.jsx)(`span`,{className:`font-medium text-ink tabular-nums`,children:T(e)}),(0,k.jsx)(`span`,{className:`text-[0.8125rem] text-ink-3 tabular-nums`,children:w(e)}),(0,k.jsx)(`span`,{className:`ml-auto text-[0.8125rem] text-ink-4`,children:R(e.commits.length)})]}),(0,k.jsx)(`div`,{className:`mt-2 pl-6`,children:(0,k.jsx)(S,{release:e})})]},e.version??`unreleased`))}):null,D===`credits`?(0,k.jsx)(F,{active:!0}):null]},D)]})}export{z as AboutDialog};