@adecore/agents-react 0.32.0 → 0.33.1

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/README.md CHANGED
@@ -61,4 +61,4 @@ The root holds the host, the scope and the transport; `/chat`, `/chat/ui`, `/pro
61
61
 
62
62
  FSL-1.1-MIT. See [LICENSE](./LICENSE).
63
63
 
64
- Diagrams in a reply are drawn by [Mermaid](https://github.com/mermaid-js/mermaid), a dependency under the MIT license, loaded only when a reply holds one.
64
+ Diagrams in a reply are drawn by [Pele](https://github.com/obsidianmd/pele), a dependency under the MIT license, loaded only when a reply holds one.
@@ -12,3 +12,13 @@ export interface WorkflowPhaseView {
12
12
  export declare function workflowAgentState(agent: ChatWorkflowAgent, workflowRunning: boolean): WorkflowAgentState;
13
13
  export declare function workflowPhases(workflow: ChatWorkflow, workflowRunning: boolean): WorkflowPhaseView[];
14
14
  export declare function workflowAgentTime(view: WorkflowAgentView): string | null;
15
+ export interface WorkflowAgentPreview {
16
+ tool: string | null;
17
+ text: string | null;
18
+ }
19
+ export declare function workflowAgentPreview(view: WorkflowAgentView): WorkflowAgentPreview;
20
+ export type WorkflowCounts = Record<WorkflowAgentState, number> & {
21
+ total: number;
22
+ };
23
+ export declare function workflowCounts(phases: WorkflowPhaseView[]): WorkflowCounts;
24
+ export declare function workflowDescription(input: unknown): string | null;
@@ -41,7 +41,6 @@ export function workflowPhases(workflow, workflowRunning) {
41
41
  }
42
42
  /* What the line of an agent that is not at work says about its time; one at work counts on by itself. */
43
43
  export function workflowAgentTime(view) {
44
- const duration = view.agent.durationMs === null ? null : formatElapsedShort(view.agent.durationMs);
45
44
  switch (view.state) {
46
45
  case 'running':
47
46
  return null;
@@ -51,8 +50,96 @@ export function workflowAgentTime(view) {
51
50
  return i18next.t('agent-chat:rows.workflow.stopped');
52
51
  case 'done':
53
52
  case 'failed':
54
- return duration === null ? i18next.t(`agent-chat:common.status.${view.state}`) : i18next.t(`agent-chat:rows.subagent.${view.state}In`, {
55
- duration
56
- });
53
+ return view.agent.durationMs === null ? null : formatElapsedShort(view.agent.durationMs);
57
54
  }
55
+ }
56
+ /* The CLI's previews start with a markdown heading as often as not, which says nothing on one line. */
57
+ function firstLine(text) {
58
+ const line = text?.split('\n').map(part => part.replace(/^#+\s*/, '').trim()).find(part => part !== '');
59
+ return line === undefined ? null : line;
60
+ }
61
+ /* The second line of an agent: what it does while at work, what it answered once it stopped, what it was asked before. */
62
+ export function workflowAgentPreview(view) {
63
+ const {
64
+ agent
65
+ } = view;
66
+ switch (view.state) {
67
+ case 'running':
68
+ return agent.lastTool === null ? {
69
+ tool: null,
70
+ text: firstLine(agent.promptPreview)
71
+ } : {
72
+ tool: agent.lastTool,
73
+ text: firstLine(agent.lastToolSummary)
74
+ };
75
+ case 'waiting':
76
+ return {
77
+ tool: null,
78
+ text: firstLine(agent.promptPreview)
79
+ };
80
+ case 'failed':
81
+ return {
82
+ tool: null,
83
+ text: firstLine(agent.error) ?? firstLine(agent.resultPreview)
84
+ };
85
+ case 'done':
86
+ case 'stopped':
87
+ return {
88
+ tool: null,
89
+ text: firstLine(agent.resultPreview) ?? firstLine(agent.promptPreview)
90
+ };
91
+ }
92
+ }
93
+ export function workflowCounts(phases) {
94
+ const counts = {
95
+ total: 0,
96
+ running: 0,
97
+ waiting: 0,
98
+ stopped: 0,
99
+ done: 0,
100
+ failed: 0
101
+ };
102
+ for (const view of phases.flatMap(phase => phase.agents)) {
103
+ counts.total += 1;
104
+ counts[view.state] += 1;
105
+ }
106
+ return counts;
107
+ }
108
+ const META_START = /export\s+const\s+meta\s*=\s*\{/;
109
+ const DESCRIPTION = /\bdescription\s*:\s*(['"`])((?:\\.|(?!\1)[^\\])*)\1/;
110
+ /* The literal from its opening brace to the one that closes it, skipping braces inside strings. */
111
+ function metaLiteral(script) {
112
+ const start = META_START.exec(script);
113
+ if (start === null) {
114
+ return null;
115
+ }
116
+ const from = start.index + start[0].length - 1;
117
+ let depth = 0;
118
+ let quote = null;
119
+ for (let i = from; i < script.length; i++) {
120
+ const char = script[i];
121
+ if (quote !== null) {
122
+ if (char === '\\') {
123
+ i++;
124
+ } else if (char === quote) {
125
+ quote = null;
126
+ }
127
+ continue;
128
+ }
129
+ if (char === "'" || char === '"' || char === '`') {
130
+ quote = char;
131
+ } else if (char === '{') {
132
+ depth++;
133
+ } else if (char === '}' && --depth === 0) {
134
+ return script.slice(from, i + 1);
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+ /* The script's `meta` is a pure literal by the CLI's own rule, so its description reads without running anything. A saved workflow called by name has no script here. */
140
+ export function workflowDescription(input) {
141
+ const script = typeof input === 'object' && input !== null && 'script' in input ? input.script : undefined;
142
+ const meta = typeof script === 'string' ? metaLiteral(script) : null;
143
+ const found = meta === null ? undefined : DESCRIPTION.exec(meta)?.[2]?.replace(/\\(.)/g, '$1').trim();
144
+ return found ? found : null;
58
145
  }
@@ -5,14 +5,12 @@ import { Code, Workflow } from 'lucide-react';
5
5
  import { IconButton } from '@adecore/ui';
6
6
  import { CodeBlock } from './CodeBlock.js';
7
7
  import { CodeStreamingContext } from './code-streaming.js';
8
- import { MERMAID_TOKENS, mermaidConfig, sizedSvg, svgDataUrl } from './mermaid.js';
8
+ import { DIAGRAM_TOKENS, GROUND_TOKEN, renderOptions, svgDataUrl, themedSvg } from './mermaid.js';
9
9
  import { watchTheme } from './theme-watch.js';
10
10
  import { useHostCodeMode } from '../../host-hooks.js';
11
- // Mermaid keeps one configuration for the page, so one diagram is drawn at a time with its own.
12
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
- let queue = Promise.resolve();
14
12
  let probeCanvas = null;
15
- /* A token as an opaque `#rrggbb` over `ground`, which is what Mermaid's color math reads. */
13
+ /* A token as an opaque `#rrggbb` over `ground`, so a border with an alpha mixes with the ground and not with what is under the image. */
16
14
  function resolveColor(element, token, ground) {
17
15
  probeCanvas ??= document.createElement('canvas');
18
16
  probeCanvas.width = 1;
@@ -31,38 +29,46 @@ function resolveColor(element, token, ground) {
31
29
  const [red, green, blue] = context.getImageData(0, 0, 1, 1).data;
32
30
  return `#${[red, green, blue].map(channel => channel.toString(16).padStart(2, '0')).join('')}`;
33
31
  }
34
- function themeColors(element) {
35
- const ground = resolveColor(element, MERMAID_TOKENS.ground, '#808080');
36
- const entries = Object.entries(MERMAID_TOKENS).map(([name, token]) => [name, name === 'ground' ? ground : resolveColor(element, token, ground)]);
37
- return Object.fromEntries(entries);
38
- }
39
32
  function diagramLook(element) {
40
33
  const style = getComputedStyle(element);
34
+ const ground = resolveColor(element, GROUND_TOKEN, '#808080');
35
+ const colors = Object.fromEntries(Object.entries(DIAGRAM_TOKENS).map(([name, token]) => [name, token === GROUND_TOKEN ? ground : resolveColor(element, token, ground)]));
36
+ const font = {
37
+ family: style.fontFamily,
38
+ mono: style.getPropertyValue('--font-mono').trim() || 'monospace',
39
+ size: Number.parseFloat(style.fontSize) || 14
40
+ };
41
41
  return {
42
- colors: themeColors(element),
43
- font: {
44
- family: style.fontFamily,
45
- size: style.fontSize
46
- }
42
+ colors,
43
+ font
47
44
  };
48
45
  }
49
- async function drawDiagram(id, source, look, dark) {
46
+ /* Keeps the look on screen when nothing of it changed, so a change of the root that leaves every color alone draws nothing again. */
47
+ function keepingSame(next) {
48
+ return current => current !== null && JSON.stringify(current) === JSON.stringify(next) ? current : next;
49
+ }
50
+ async function layOut(id, source, font) {
50
51
  const {
51
- default: mermaid
52
- } = await import('mermaid');
53
- mermaid.initialize(mermaidConfig(look.colors, dark, look.font));
52
+ renderAsync
53
+ } = await import('pele/lazy');
54
54
  const {
55
- svg
56
- } = await mermaid.render(id, source);
57
- return sizedSvg(svg);
55
+ svg,
56
+ width,
57
+ height
58
+ } = await renderAsync(source, renderOptions(font, id));
59
+ return {
60
+ svg,
61
+ width: Math.ceil(width),
62
+ height: Math.ceil(height)
63
+ };
58
64
  }
59
65
  /*
60
- * A ```mermaid fence, drawn as the diagram once the fence is closed. Mermaid is large, so it loads on
61
- * the first diagram of the page. While the fence grows, and when the text is no diagram Mermaid can
62
- * draw, it is the code block it would otherwise be.
66
+ * A ```mermaid fence, drawn as the diagram once the fence is closed. Pele loads on the first diagram of the
67
+ * page, and then only what a diagram of that kind needs. While the fence grows, and when the text is no diagram
68
+ * Pele can draw, it is the code block it would otherwise be.
63
69
  */
64
70
  export function MermaidBlock(t0) {
65
- const $ = _c(37);
71
+ const $ = _c(48);
66
72
  const {
67
73
  code
68
74
  } = t0;
@@ -100,46 +106,62 @@ export function MermaidBlock(t0) {
100
106
  t3 = $[3];
101
107
  }
102
108
  const [drawing, setDrawing] = useState(t3);
103
- const drawnLook = useRef(null);
104
- const [themeRevision, setThemeRevision] = useState(0);
109
+ const [look, setLook] = useState(null);
105
110
  let t4;
106
- let t5;
107
111
  if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
108
112
  t4 = () => {
109
113
  const element = frame.current;
110
- if (element === null) {
111
- return;
114
+ if (element !== null) {
115
+ setLook(keepingSame(diagramLook(element)));
112
116
  }
113
- return watchTheme(element.ownerDocument, () => {
114
- if (drawnLook.current !== null && JSON.stringify(diagramLook(element)) !== drawnLook.current) {
115
- setThemeRevision(_temp);
116
- }
117
- });
118
117
  };
119
- t5 = [];
120
118
  $[4] = t4;
121
- $[5] = t5;
122
119
  } else {
123
120
  t4 = $[4];
124
- t5 = $[5];
121
+ }
122
+ let t5;
123
+ if ($[5] !== dark) {
124
+ t5 = [dark];
125
+ $[5] = dark;
126
+ $[6] = t5;
127
+ } else {
128
+ t5 = $[6];
125
129
  }
126
130
  useEffect(t4, t5);
127
131
  let t6;
128
- if ($[6] !== code || $[7] !== dark || $[8] !== id || $[9] !== streaming) {
132
+ let t7;
133
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
129
134
  t6 = () => {
130
135
  const element_0 = frame.current;
131
- if (streaming || element_0 === null) {
136
+ if (element_0 === null) {
137
+ return;
138
+ }
139
+ return watchTheme(element_0.ownerDocument, () => setLook(keepingSame(diagramLook(element_0))));
140
+ };
141
+ t7 = [];
142
+ $[7] = t6;
143
+ $[8] = t7;
144
+ } else {
145
+ t6 = $[7];
146
+ t7 = $[8];
147
+ }
148
+ useEffect(t6, t7);
149
+ const fontFamily = look?.font.family;
150
+ const fontMono = look?.font.mono;
151
+ const fontSize = look?.font.size;
152
+ let t8;
153
+ let t9;
154
+ if ($[9] !== code || $[10] !== fontFamily || $[11] !== fontMono || $[12] !== fontSize || $[13] !== id || $[14] !== streaming) {
155
+ t8 = () => {
156
+ if (streaming || fontFamily === undefined || fontMono === undefined || fontSize === undefined) {
132
157
  return;
133
158
  }
134
159
  let cancelled = false;
135
- const look = diagramLook(element_0);
136
- drawnLook.current = JSON.stringify(look);
137
- setDrawing(_temp2);
138
- const drawn = queue.then(() => cancelled ? null : drawDiagram(id, code, look, dark));
139
- queue = drawn.catch(_temp3);
140
- drawn.then(image => !cancelled && setDrawing(image === null ? {
141
- state: "failed"
142
- } : {
160
+ layOut(id, code, {
161
+ family: fontFamily,
162
+ mono: fontMono,
163
+ size: fontSize
164
+ }).then(image => !cancelled && setDrawing({
143
165
  state: "drawn",
144
166
  image
145
167
  }), () => !cancelled && setDrawing({
@@ -149,117 +171,131 @@ export function MermaidBlock(t0) {
149
171
  cancelled = true;
150
172
  };
151
173
  };
152
- $[6] = code;
153
- $[7] = dark;
154
- $[8] = id;
155
- $[9] = streaming;
156
- $[10] = t6;
157
- } else {
158
- t6 = $[10];
159
- }
160
- let t7;
161
- if ($[11] !== code || $[12] !== dark || $[13] !== id || $[14] !== streaming || $[15] !== themeRevision) {
162
- t7 = [code, dark, id, streaming, themeRevision];
163
- $[11] = code;
164
- $[12] = dark;
174
+ t9 = [code, fontFamily, fontMono, fontSize, id, streaming];
175
+ $[9] = code;
176
+ $[10] = fontFamily;
177
+ $[11] = fontMono;
178
+ $[12] = fontSize;
165
179
  $[13] = id;
166
180
  $[14] = streaming;
167
- $[15] = themeRevision;
168
- $[16] = t7;
181
+ $[15] = t8;
182
+ $[16] = t9;
169
183
  } else {
170
- t7 = $[16];
184
+ t8 = $[15];
185
+ t9 = $[16];
171
186
  }
172
- useEffect(t6, t7);
173
- const showsDiagram = !streaming && !source && drawing.state !== "failed";
174
- let t8;
175
- if ($[17] !== drawing.state || $[18] !== source || $[19] !== streaming || $[20] !== t) {
176
- t8 = streaming || drawing.state === "failed" ? undefined : /*#__PURE__*/_jsx(IconButton, {
177
- icon: source ? Workflow : Code,
178
- size: "sm",
179
- label: source ? t("code.mermaid.showDiagram") : t("code.mermaid.showSource"),
180
- onClick: () => setSource(_temp4)
181
- });
182
- $[17] = drawing.state;
183
- $[18] = source;
184
- $[19] = streaming;
185
- $[20] = t;
186
- $[21] = t8;
187
- } else {
188
- t8 = $[21];
189
- }
190
- const toggle = t8;
191
- let t9;
192
- if ($[22] !== drawing.image || $[23] !== drawing.state || $[24] !== showsDiagram || $[25] !== t) {
193
- t9 = showsDiagram ? /*#__PURE__*/_jsx("div", {
187
+ useEffect(t8, t9);
188
+ let T0;
189
+ let t10;
190
+ let t11;
191
+ let t12;
192
+ let t13;
193
+ let t14;
194
+ if ($[17] !== code || $[18] !== drawing.image || $[19] !== drawing.state || $[20] !== look || $[21] !== source || $[22] !== streaming || $[23] !== t) {
195
+ const image_0 = drawing.state === "drawn" && look !== null ? {
196
+ ...drawing.image,
197
+ svg: themedSvg(drawing.image.svg, look.colors)
198
+ } : null;
199
+ const showsDiagram = !streaming && !source && drawing.state !== "failed";
200
+ let t15;
201
+ if ($[30] !== drawing.state || $[31] !== source || $[32] !== streaming || $[33] !== t) {
202
+ t15 = streaming || drawing.state === "failed" ? undefined : /*#__PURE__*/_jsx(IconButton, {
203
+ icon: source ? Workflow : Code,
204
+ size: "sm",
205
+ label: source ? t("code.mermaid.showDiagram") : t("code.mermaid.showSource"),
206
+ onClick: () => setSource(_temp)
207
+ });
208
+ $[30] = drawing.state;
209
+ $[31] = source;
210
+ $[32] = streaming;
211
+ $[33] = t;
212
+ $[34] = t15;
213
+ } else {
214
+ t15 = $[34];
215
+ }
216
+ const toggle = t15;
217
+ t14 = frame;
218
+ T0 = CodeBlock;
219
+ t10 = code;
220
+ t11 = "mermaid";
221
+ t12 = toggle;
222
+ t13 = showsDiagram ? /*#__PURE__*/_jsx("div", {
194
223
  className: "overflow-x-auto p-3",
195
- "aria-busy": drawing.state === "drawing" || undefined,
196
- children: drawing.state === "drawn" && /*#__PURE__*/_jsx("img", {
197
- src: svgDataUrl(drawing.image.svg),
198
- width: drawing.image.width,
199
- height: drawing.image.height,
224
+ "aria-busy": image_0 === null || undefined,
225
+ children: image_0 !== null && /*#__PURE__*/_jsx("img", {
226
+ src: svgDataUrl(image_0.svg),
227
+ width: image_0.width,
228
+ height: image_0.height,
200
229
  alt: t("code.mermaid.diagram"),
201
230
  className: "m-0 max-w-none",
202
231
  draggable: false
203
232
  })
204
233
  }) : undefined;
205
- $[22] = drawing.image;
206
- $[23] = drawing.state;
207
- $[24] = showsDiagram;
208
- $[25] = t;
209
- $[26] = t9;
234
+ $[17] = code;
235
+ $[18] = drawing.image;
236
+ $[19] = drawing.state;
237
+ $[20] = look;
238
+ $[21] = source;
239
+ $[22] = streaming;
240
+ $[23] = t;
241
+ $[24] = T0;
242
+ $[25] = t10;
243
+ $[26] = t11;
244
+ $[27] = t12;
245
+ $[28] = t13;
246
+ $[29] = t14;
210
247
  } else {
211
- t9 = $[26];
248
+ T0 = $[24];
249
+ t10 = $[25];
250
+ t11 = $[26];
251
+ t12 = $[27];
252
+ t13 = $[28];
253
+ t14 = $[29];
212
254
  }
213
- let t10;
214
- if ($[27] !== code || $[28] !== t9 || $[29] !== toggle) {
215
- t10 = /*#__PURE__*/_jsx(CodeBlock, {
216
- code: code,
217
- lang: "mermaid",
218
- actions: toggle,
219
- view: t9
255
+ let t15;
256
+ if ($[35] !== T0 || $[36] !== t10 || $[37] !== t11 || $[38] !== t12 || $[39] !== t13) {
257
+ t15 = /*#__PURE__*/_jsx(T0, {
258
+ code: t10,
259
+ lang: t11,
260
+ actions: t12,
261
+ view: t13
220
262
  });
221
- $[27] = code;
222
- $[28] = t9;
223
- $[29] = toggle;
224
- $[30] = t10;
263
+ $[35] = T0;
264
+ $[36] = t10;
265
+ $[37] = t11;
266
+ $[38] = t12;
267
+ $[39] = t13;
268
+ $[40] = t15;
225
269
  } else {
226
- t10 = $[30];
270
+ t15 = $[40];
227
271
  }
228
- let t11;
229
- if ($[31] !== drawing.state || $[32] !== t) {
230
- t11 = drawing.state === "failed" && /*#__PURE__*/_jsx("p", {
272
+ let t16;
273
+ if ($[41] !== drawing.state || $[42] !== t) {
274
+ t16 = drawing.state === "failed" && /*#__PURE__*/_jsx("p", {
231
275
  className: "m-0 mt-1 text-xs text-text-faint",
232
276
  children: t("code.mermaid.failed")
233
277
  });
234
- $[31] = drawing.state;
235
- $[32] = t;
236
- $[33] = t11;
278
+ $[41] = drawing.state;
279
+ $[42] = t;
280
+ $[43] = t16;
237
281
  } else {
238
- t11 = $[33];
282
+ t16 = $[43];
239
283
  }
240
- let t12;
241
- if ($[34] !== t10 || $[35] !== t11) {
242
- t12 = /*#__PURE__*/_jsxs("div", {
243
- ref: frame,
244
- children: [t10, t11]
284
+ let t17;
285
+ if ($[44] !== t14 || $[45] !== t15 || $[46] !== t16) {
286
+ t17 = /*#__PURE__*/_jsxs("div", {
287
+ ref: t14,
288
+ children: [t15, t16]
245
289
  });
246
- $[34] = t10;
247
- $[35] = t11;
248
- $[36] = t12;
290
+ $[44] = t14;
291
+ $[45] = t15;
292
+ $[46] = t16;
293
+ $[47] = t17;
249
294
  } else {
250
- t12 = $[36];
295
+ t17 = $[47];
251
296
  }
252
- return t12;
297
+ return t17;
253
298
  }
254
- function _temp4(shown) {
299
+ function _temp(shown) {
255
300
  return !shown;
256
- }
257
- function _temp3() {}
258
- function _temp2(current) {
259
- return current.state === "drawn" ? current : {
260
- state: "drawing"
261
- };
262
- }
263
- function _temp(revision) {
264
- return revision + 1;
265
301
  }
@@ -1,23 +1,11 @@
1
- import type { MermaidConfig } from 'mermaid';
2
- export interface MermaidColors {
3
- ground: string;
4
- node: string;
5
- nodeBorder: string;
6
- text: string;
7
- line: string;
8
- cluster: string;
9
- clusterBorder: string;
10
- note: string;
11
- }
12
- export declare const MERMAID_TOKENS: Readonly<Record<keyof MermaidColors, string>>;
13
- export declare function mermaidConfig(colors: MermaidColors, dark: boolean, font: {
1
+ import type { RenderOptions } from 'pele';
2
+ export declare const DIAGRAM_TOKENS: Readonly<Record<string, string>>;
3
+ export declare const GROUND_TOKEN: string;
4
+ export interface DiagramFont {
14
5
  family: string;
15
- size: string;
16
- }): MermaidConfig;
17
- export interface SizedSvg {
18
- svg: string;
19
- width: number;
20
- height: number;
6
+ mono: string;
7
+ size: number;
21
8
  }
22
- export declare function sizedSvg(svg: string): SizedSvg | null;
9
+ export declare function renderOptions(font: DiagramFont, idPrefix: string): RenderOptions;
10
+ export declare function themedSvg(svg: string, colors: Readonly<Record<string, string>>): string;
23
11
  export declare function svgDataUrl(svg: string): string;