@adecore/agents-react 0.37.3 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { type ChatVisual } from '@adecore/agent-contracts/visual';
2
+ export declare const DEFAULT_HEIGHT_PX = 240;
2
3
  export declare function clampVisualHeight(visual: Pick<ChatVisual, 'maxHeight'>, height: number): number;
3
4
  export declare function visualContentHeight(height: number): number;
4
5
  export declare function initialVisualContentHeight(visual: Pick<ChatVisual, 'id' | 'heights'>, width: number): number;
@@ -1,6 +1,6 @@
1
1
  import { VISUAL_LIMITS, visualFrameHeight } from '@adecore/agent-contracts/visual';
2
2
  /* A frame before anything measured its page: room for a chart, never taller than the page may grow. */
3
- const DEFAULT_HEIGHT_PX = 240;
3
+ export const DEFAULT_HEIGHT_PX = 240;
4
4
  /* Plenty for the visuals of a long session; the oldest goes first. */
5
5
  const REMEMBERED_HEIGHTS = 500;
6
6
  // Stay within browser layout coordinates even if a page reports a bogus finite size.
@@ -0,0 +1,4 @@
1
+ import type { HostMenuAction } from '../../host.js';
2
+ export declare function HostMenuItems({ actions }: {
3
+ actions: readonly HostMenuAction[];
4
+ }): import("react").JSX.Element[];
@@ -0,0 +1,15 @@
1
+ import { Icon, Menu } from '@adecore/ui';
2
+ /* The items a host adds to a menu of the chat's, in the order it gave them. */
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ export function HostMenuItems({
5
+ actions
6
+ }) {
7
+ return actions.map(action => /*#__PURE__*/_jsxs(Menu.Item, {
8
+ disabled: action.disabled,
9
+ onClick: () => action.run(),
10
+ children: [action.icon !== undefined && /*#__PURE__*/_jsx(Icon, {
11
+ icon: action.icon,
12
+ size: 14
13
+ }), " ", action.label]
14
+ }, action.id));
15
+ }
@@ -5,4 +5,12 @@ export interface VisualFrameProps {
5
5
  fill?: boolean;
6
6
  className?: string;
7
7
  }
8
+ export interface VisualPageFrameProps {
9
+ page: () => Promise<string>;
10
+ title: string;
11
+ maxHeight?: number;
12
+ fill?: boolean;
13
+ className?: string;
14
+ }
8
15
  export declare function VisualFrame({ chatId, visual, fill, className }: VisualFrameProps): import("react").JSX.Element;
16
+ export declare function VisualPageFrame({ maxHeight, ...props }: VisualPageFrameProps): import("react").JSX.Element;
@@ -1,12 +1,12 @@
1
1
  import { c as _c } from "react/compiler-runtime";
2
- import { useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import clsx from 'clsx';
5
- import { visualThemeFragment } from '@adecore/agent-contracts/visual';
5
+ import { VISUAL_LIMITS, visualThemeFragment } from '@adecore/agent-contracts/visual';
6
6
  import { Spinner, useMeasuredWidth } from '@adecore/ui';
7
7
  import { useChatHost } from '../../host.js';
8
8
  import { useChatScope } from '../../scope.js';
9
- import { initialVisualContentHeight, rememberVisualHeight } from '../logic/visual-height.js';
9
+ import { DEFAULT_HEIGHT_PX, initialVisualContentHeight, rememberVisualHeight } from '../logic/visual-height.js';
10
10
  import { VisualBridge } from '../visual-bridge.js';
11
11
  import { VisualViewportController } from '../visual-viewport.js';
12
12
  import { useVisualTheme } from './visual-theme.js';
@@ -19,13 +19,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  function personActsIn(frame) {
20
20
  return frame.ownerDocument.activeElement === frame && navigator.userActivation?.isActive !== false;
21
21
  }
22
- /*
23
- * One visual's page in a frame that loads the app's sandbox host page; the bridge hands it the page and
24
- * the theme. The frame never gets `allow-same-origin`, which keeps the page on an opaque origin even
25
- * when the host page is served on the app's own. Its box keeps one height while it loads or fails.
26
- */
22
+ const defaultHeight = () => DEFAULT_HEIGHT_PX;
23
+ /* A visual of a chat, its page read from the chat's attachments under the visual's id. */
27
24
  export function VisualFrame(t0) {
28
- const $ = _c(65);
25
+ const $ = _c(17);
29
26
  const {
30
27
  chatId,
31
28
  visual,
@@ -34,13 +31,123 @@ export function VisualFrame(t0) {
34
31
  } = t0;
35
32
  const fill = t1 === undefined ? false : t1;
36
33
  const {
37
- visuals,
38
34
  attachments
39
35
  } = useChatHost();
36
+ const scope = useChatScope();
37
+ let t2;
38
+ if ($[0] !== attachments || $[1] !== chatId || $[2] !== scope.id || $[3] !== visual.id) {
39
+ t2 = () => attachments.read(scope.id, chatId, visual.id).then(_temp);
40
+ $[0] = attachments;
41
+ $[1] = chatId;
42
+ $[2] = scope.id;
43
+ $[3] = visual.id;
44
+ $[4] = t2;
45
+ } else {
46
+ t2 = $[4];
47
+ }
48
+ const page = t2;
49
+ let t3;
50
+ if ($[5] !== visual) {
51
+ t3 = width => initialVisualContentHeight(visual, width);
52
+ $[5] = visual;
53
+ $[6] = t3;
54
+ } else {
55
+ t3 = $[6];
56
+ }
57
+ let t4;
58
+ if ($[7] !== visual.id) {
59
+ t4 = (width_0, height) => rememberVisualHeight(visual.id, width_0, height);
60
+ $[7] = visual.id;
61
+ $[8] = t4;
62
+ } else {
63
+ t4 = $[8];
64
+ }
65
+ let t5;
66
+ if ($[9] !== className || $[10] !== fill || $[11] !== page || $[12] !== t3 || $[13] !== t4 || $[14] !== visual.maxHeight || $[15] !== visual.title) {
67
+ t5 = /*#__PURE__*/_jsx(PageFrame, {
68
+ page: page,
69
+ title: visual.title,
70
+ maxHeight: visual.maxHeight,
71
+ fill: fill,
72
+ className: className,
73
+ initialHeight: t3,
74
+ onHeight: t4
75
+ });
76
+ $[9] = className;
77
+ $[10] = fill;
78
+ $[11] = page;
79
+ $[12] = t3;
80
+ $[13] = t4;
81
+ $[14] = visual.maxHeight;
82
+ $[15] = visual.title;
83
+ $[16] = t5;
84
+ } else {
85
+ t5 = $[16];
86
+ }
87
+ return t5;
88
+ }
89
+ /*
90
+ * A visual's page outside a chat thread, such as one an app kept: the same sandboxed frame, theme and
91
+ * links as in a thread, with the page read through `page`. Draws nothing while the host has no `visuals`.
92
+ */
93
+ function _temp(blob) {
94
+ return blob.text();
95
+ }
96
+ export function VisualPageFrame(t0) {
97
+ const $ = _c(6);
98
+ let props;
99
+ let t1;
100
+ if ($[0] !== t0) {
101
+ ({
102
+ maxHeight: t1,
103
+ ...props
104
+ } = t0);
105
+ $[0] = t0;
106
+ $[1] = props;
107
+ $[2] = t1;
108
+ } else {
109
+ props = $[1];
110
+ t1 = $[2];
111
+ }
112
+ const maxHeight = t1 === undefined ? VISUAL_LIMITS.maxHeight : t1;
113
+ let t2;
114
+ if ($[3] !== maxHeight || $[4] !== props) {
115
+ t2 = /*#__PURE__*/_jsx(PageFrame, {
116
+ ...props,
117
+ maxHeight: maxHeight,
118
+ initialHeight: defaultHeight
119
+ });
120
+ $[3] = maxHeight;
121
+ $[4] = props;
122
+ $[5] = t2;
123
+ } else {
124
+ t2 = $[5];
125
+ }
126
+ return t2;
127
+ }
128
+ /*
129
+ * One page in a frame that loads the app's sandbox host page; the bridge hands it the page and the
130
+ * theme. The frame never gets `allow-same-origin`, which keeps the page on an opaque origin even when
131
+ * the host page is served on the app's own. Its box keeps one height while it loads or fails.
132
+ */
133
+ function PageFrame(t0) {
134
+ const $ = _c(61);
135
+ const {
136
+ page,
137
+ title,
138
+ maxHeight,
139
+ fill: t1,
140
+ className,
141
+ initialHeight,
142
+ onHeight
143
+ } = t0;
144
+ const fill = t1 === undefined ? false : t1;
145
+ const {
146
+ visuals
147
+ } = useChatHost();
40
148
  const {
41
149
  t
42
150
  } = useTranslation("agent-chat");
43
- const scope = useChatScope();
44
151
  const frameUrl = visuals?.frameUrl ?? null;
45
152
  const boxRef = useRef(null);
46
153
  const frameRef = useRef(null);
@@ -63,6 +170,7 @@ export function VisualFrame(t0) {
63
170
  const setBox = t2;
64
171
  const theme = useVisualTheme(boxRef);
65
172
  const widthRef = useRef(width);
173
+ const onHeightRef = useRef(onHeight);
66
174
  const [reported, setReported] = useState(null);
67
175
  const [drawn, setDrawn] = useState(null);
68
176
  const [failed, setFailed] = useState(false);
@@ -74,11 +182,11 @@ export function VisualFrame(t0) {
74
182
  });
75
183
  }
76
184
  let t3;
77
- if ($[2] !== fill || $[3] !== reported || $[4] !== visual || $[5] !== width) {
78
- t3 = fill || width === 0 ? undefined : reported ?? initialVisualContentHeight(visual, width);
185
+ if ($[2] !== fill || $[3] !== initialHeight || $[4] !== reported || $[5] !== width) {
186
+ t3 = fill || width === 0 ? undefined : reported ?? initialHeight(width);
79
187
  $[2] = fill;
80
- $[3] = reported;
81
- $[4] = visual;
188
+ $[3] = initialHeight;
189
+ $[4] = reported;
82
190
  $[5] = width;
83
191
  $[6] = t3;
84
192
  } else {
@@ -87,22 +195,24 @@ export function VisualFrame(t0) {
87
195
  const height = t3;
88
196
  let t4;
89
197
  let t5;
90
- if ($[7] !== width) {
198
+ if ($[7] !== onHeight || $[8] !== width) {
91
199
  t4 = () => {
92
200
  widthRef.current = width;
201
+ onHeightRef.current = onHeight;
93
202
  };
94
- t5 = [width];
95
- $[7] = width;
96
- $[8] = t4;
97
- $[9] = t5;
203
+ t5 = [width, onHeight];
204
+ $[7] = onHeight;
205
+ $[8] = width;
206
+ $[9] = t4;
207
+ $[10] = t5;
98
208
  } else {
99
- t4 = $[8];
100
- t5 = $[9];
209
+ t4 = $[9];
210
+ t5 = $[10];
101
211
  }
102
212
  useLayoutEffect(t4, t5);
103
213
  let t6;
104
214
  let t7;
105
- if ($[10] !== attachments || $[11] !== chatId || $[12] !== fill || $[13] !== opening || $[14] !== scope.id || $[15] !== visual.id || $[16] !== visual.maxHeight || $[17] !== visuals) {
215
+ if ($[11] !== fill || $[12] !== maxHeight || $[13] !== opening || $[14] !== page || $[15] !== visuals) {
106
216
  t6 = () => {
107
217
  const frame = frameRef.current;
108
218
  if (opening === null || frame === null) {
@@ -111,9 +221,9 @@ export function VisualFrame(t0) {
111
221
  const bridge = new VisualBridge({
112
222
  frame: () => frame.contentWindow,
113
223
  visual: {
114
- maxHeight: visual.maxHeight
224
+ maxHeight
115
225
  },
116
- page: attachments.read(scope.id, chatId, visual.id).then(_temp),
226
+ page: page(),
117
227
  theme: opening.theme,
118
228
  mayOpenLink: () => personActsIn(frame),
119
229
  openLink: url => visuals?.openLink(url),
@@ -122,7 +232,7 @@ export function VisualFrame(t0) {
122
232
  return;
123
233
  }
124
234
  setReported(next);
125
- rememberVisualHeight(visual.id, widthRef.current, next);
235
+ onHeightRef.current?.(widthRef.current, next);
126
236
  },
127
237
  ...(!fill ? {
128
238
  onViewportReady: () => setViewportReady(true),
@@ -141,24 +251,21 @@ export function VisualFrame(t0) {
141
251
  bridgeRef.current = null;
142
252
  };
143
253
  };
144
- t7 = [opening, scope.id, chatId, visual.id, visual.maxHeight, fill, visuals, attachments];
145
- $[10] = attachments;
146
- $[11] = chatId;
147
- $[12] = fill;
254
+ t7 = [opening, page, maxHeight, fill, visuals];
255
+ $[11] = fill;
256
+ $[12] = maxHeight;
148
257
  $[13] = opening;
149
- $[14] = scope.id;
150
- $[15] = visual.id;
151
- $[16] = visual.maxHeight;
152
- $[17] = visuals;
153
- $[18] = t6;
154
- $[19] = t7;
258
+ $[14] = page;
259
+ $[15] = visuals;
260
+ $[16] = t6;
261
+ $[17] = t7;
155
262
  } else {
156
- t6 = $[18];
157
- t7 = $[19];
263
+ t6 = $[16];
264
+ t7 = $[17];
158
265
  }
159
266
  useLayoutEffect(t6, t7);
160
267
  let t8;
161
- if ($[20] !== fill || $[21] !== viewportReady) {
268
+ if ($[18] !== fill || $[19] !== viewportReady) {
162
269
  t8 = () => {
163
270
  const box = boxRef.current;
164
271
  const viewport = viewportRef.current;
@@ -174,98 +281,96 @@ export function VisualFrame(t0) {
174
281
  controllerRef.current = null;
175
282
  };
176
283
  };
177
- $[20] = fill;
178
- $[21] = viewportReady;
179
- $[22] = t8;
284
+ $[18] = fill;
285
+ $[19] = viewportReady;
286
+ $[20] = t8;
180
287
  } else {
181
- t8 = $[22];
288
+ t8 = $[20];
182
289
  }
183
290
  let t9;
184
- if ($[23] !== chatId || $[24] !== fill || $[25] !== opening || $[26] !== scope.id || $[27] !== viewportReady || $[28] !== visual.id || $[29] !== visual.maxHeight) {
185
- t9 = [fill, viewportReady, opening, scope.id, chatId, visual.id, visual.maxHeight];
186
- $[23] = chatId;
187
- $[24] = fill;
188
- $[25] = opening;
189
- $[26] = scope.id;
190
- $[27] = viewportReady;
191
- $[28] = visual.id;
192
- $[29] = visual.maxHeight;
193
- $[30] = t9;
291
+ if ($[21] !== fill || $[22] !== maxHeight || $[23] !== opening || $[24] !== page || $[25] !== viewportReady) {
292
+ t9 = [fill, viewportReady, opening, page, maxHeight];
293
+ $[21] = fill;
294
+ $[22] = maxHeight;
295
+ $[23] = opening;
296
+ $[24] = page;
297
+ $[25] = viewportReady;
298
+ $[26] = t9;
194
299
  } else {
195
- t9 = $[30];
300
+ t9 = $[26];
196
301
  }
197
302
  useLayoutEffect(t8, t9);
198
303
  let t10;
199
- if ($[31] === Symbol.for("react.memo_cache_sentinel")) {
304
+ if ($[27] === Symbol.for("react.memo_cache_sentinel")) {
200
305
  t10 = () => controllerRef.current?.refresh();
201
- $[31] = t10;
306
+ $[27] = t10;
202
307
  } else {
203
- t10 = $[31];
308
+ t10 = $[27];
204
309
  }
205
310
  let t11;
206
- if ($[32] !== height || $[33] !== width) {
311
+ if ($[28] !== height || $[29] !== width) {
207
312
  t11 = [height, width];
208
- $[32] = height;
209
- $[33] = width;
210
- $[34] = t11;
313
+ $[28] = height;
314
+ $[29] = width;
315
+ $[30] = t11;
211
316
  } else {
212
- t11 = $[34];
317
+ t11 = $[30];
213
318
  }
214
319
  useLayoutEffect(t10, t11);
215
320
  let t12;
216
321
  let t13;
217
- if ($[35] !== theme) {
322
+ if ($[31] !== theme) {
218
323
  t12 = () => {
219
324
  if (theme !== null) {
220
325
  bridgeRef.current?.setTheme(theme);
221
326
  }
222
327
  };
223
328
  t13 = [theme];
224
- $[35] = theme;
225
- $[36] = t12;
226
- $[37] = t13;
329
+ $[31] = theme;
330
+ $[32] = t12;
331
+ $[33] = t13;
227
332
  } else {
228
- t12 = $[36];
229
- t13 = $[37];
333
+ t12 = $[32];
334
+ t13 = $[33];
230
335
  }
231
336
  useEffect(t12, t13);
232
337
  const t14 = fill ? "absolute inset-0" : "relative w-full";
233
338
  let t15;
234
- if ($[38] !== className || $[39] !== t14) {
339
+ if ($[34] !== className || $[35] !== t14) {
235
340
  t15 = clsx(t14, className);
236
- $[38] = className;
237
- $[39] = t14;
238
- $[40] = t15;
341
+ $[34] = className;
342
+ $[35] = t14;
343
+ $[36] = t15;
239
344
  } else {
240
- t15 = $[40];
345
+ t15 = $[36];
241
346
  }
242
347
  let t16;
243
- if ($[41] !== height) {
348
+ if ($[37] !== height) {
244
349
  t16 = {
245
350
  height
246
351
  };
247
- $[41] = height;
248
- $[42] = t16;
352
+ $[37] = height;
353
+ $[38] = t16;
249
354
  } else {
250
- t16 = $[42];
355
+ t16 = $[38];
251
356
  }
252
357
  const t17 = fill ? "absolute inset-0" : "sticky top-0 w-full";
253
358
  let t18;
254
- if ($[43] !== fill) {
359
+ if ($[39] !== fill) {
255
360
  t18 = fill ? undefined : {
256
361
  height: "100%"
257
362
  };
258
- $[43] = fill;
259
- $[44] = t18;
363
+ $[39] = fill;
364
+ $[40] = t18;
260
365
  } else {
261
- t18 = $[44];
366
+ t18 = $[40];
262
367
  }
263
368
  let t19;
264
- if ($[45] !== drawn || $[46] !== failed || $[47] !== opening || $[48] !== visual.title) {
369
+ if ($[41] !== drawn || $[42] !== failed || $[43] !== opening || $[44] !== title) {
265
370
  t19 = opening !== null && /*#__PURE__*/_jsx("iframe", {
266
371
  ref: frameRef,
267
372
  src: opening.src,
268
- title: visual.title,
373
+ title: title,
269
374
  loading: "lazy",
270
375
  sandbox: "allow-scripts allow-forms",
271
376
  className: clsx("block size-full border-0", failed && "invisible"),
@@ -274,72 +379,69 @@ export function VisualFrame(t0) {
274
379
  },
275
380
  onLoad: () => bridgeRef.current?.loaded()
276
381
  });
277
- $[45] = drawn;
278
- $[46] = failed;
279
- $[47] = opening;
280
- $[48] = visual.title;
281
- $[49] = t19;
382
+ $[41] = drawn;
383
+ $[42] = failed;
384
+ $[43] = opening;
385
+ $[44] = title;
386
+ $[45] = t19;
282
387
  } else {
283
- t19 = $[49];
388
+ t19 = $[45];
284
389
  }
285
390
  let t20;
286
- if ($[50] !== drawn || $[51] !== failed || $[52] !== t || $[53] !== visual.title) {
391
+ if ($[46] !== drawn || $[47] !== failed || $[48] !== t || $[49] !== title) {
287
392
  t20 = failed ? /*#__PURE__*/_jsx("p", {
288
393
  className: "absolute inset-0 grid place-items-center px-4 text-center text-xs text-text-muted",
289
394
  children: t("visuals.failed", {
290
- title: visual.title
395
+ title
291
396
  })
292
397
  }) : drawn === null && /*#__PURE__*/_jsx("span", {
293
398
  className: "chat-visual-wait pointer-events-none absolute inset-0 grid place-items-center text-text-faint",
294
399
  children: /*#__PURE__*/_jsx(Spinner, {
295
400
  size: 16,
296
401
  label: t("visuals.loading", {
297
- title: visual.title
402
+ title
298
403
  })
299
404
  })
300
405
  });
301
- $[50] = drawn;
302
- $[51] = failed;
303
- $[52] = t;
304
- $[53] = visual.title;
305
- $[54] = t20;
406
+ $[46] = drawn;
407
+ $[47] = failed;
408
+ $[48] = t;
409
+ $[49] = title;
410
+ $[50] = t20;
306
411
  } else {
307
- t20 = $[54];
412
+ t20 = $[50];
308
413
  }
309
414
  let t21;
310
- if ($[55] !== t17 || $[56] !== t18 || $[57] !== t19 || $[58] !== t20) {
415
+ if ($[51] !== t17 || $[52] !== t18 || $[53] !== t19 || $[54] !== t20) {
311
416
  t21 = /*#__PURE__*/_jsxs("div", {
312
417
  ref: viewportRef,
313
418
  className: t17,
314
419
  style: t18,
315
420
  children: [t19, t20]
316
421
  });
317
- $[55] = t17;
318
- $[56] = t18;
319
- $[57] = t19;
320
- $[58] = t20;
321
- $[59] = t21;
422
+ $[51] = t17;
423
+ $[52] = t18;
424
+ $[53] = t19;
425
+ $[54] = t20;
426
+ $[55] = t21;
322
427
  } else {
323
- t21 = $[59];
428
+ t21 = $[55];
324
429
  }
325
430
  let t22;
326
- if ($[60] !== setBox || $[61] !== t15 || $[62] !== t16 || $[63] !== t21) {
431
+ if ($[56] !== setBox || $[57] !== t15 || $[58] !== t16 || $[59] !== t21) {
327
432
  t22 = /*#__PURE__*/_jsx("div", {
328
433
  ref: setBox,
329
434
  className: t15,
330
435
  style: t16,
331
436
  children: t21
332
437
  });
333
- $[60] = setBox;
334
- $[61] = t15;
335
- $[62] = t16;
336
- $[63] = t21;
337
- $[64] = t22;
438
+ $[56] = setBox;
439
+ $[57] = t15;
440
+ $[58] = t16;
441
+ $[59] = t21;
442
+ $[60] = t22;
338
443
  } else {
339
- t22 = $[64];
444
+ t22 = $[60];
340
445
  }
341
446
  return t22;
342
- }
343
- function _temp(blob) {
344
- return blob.text();
345
447
  }
@@ -21,6 +21,7 @@ export { Timeline } from './Timeline.js';
21
21
  export { TimelineMenuPopup } from './TimelineMenu.js';
22
22
  export { default as UnifiedDiff } from './UnifiedDiff.js';
23
23
  export { UploadThumb } from './UploadThumb.js';
24
+ export { VisualPageFrame, type VisualPageFrameProps } from './VisualFrame.js';
24
25
  export { WelcomeGreeting } from './Welcome.js';
25
26
  export { externalChange, composerEditorExtensions } from './composer/editor.js';
26
27
  export { createDiffSyntaxRuntime, diffSyntaxRuntime, type DiffSyntaxRuntime, type DiffSyntaxRuntimeOptions, type DiffSyntaxSession, type DiffSyntaxMapping, type DiffSourceTokenSession, diffSourceTokens } from './diff-syntax.js';
@@ -22,6 +22,7 @@ export { Timeline } from './Timeline.js';
22
22
  export { TimelineMenuPopup } from './TimelineMenu.js';
23
23
  export { default as UnifiedDiff } from './UnifiedDiff.js';
24
24
  export { UploadThumb } from './UploadThumb.js';
25
+ export { VisualPageFrame } from './VisualFrame.js';
25
26
  export { WelcomeGreeting } from './Welcome.js';
26
27
  export { externalChange, composerEditorExtensions } from './composer/editor.js';
27
28
  export { createDiffSyntaxRuntime, diffSyntaxRuntime, diffSourceTokens } from './diff-syntax.js';
@@ -12,6 +12,7 @@ import { attachmentAspect, generatedImageView } from '../../logic/generated-imag
12
12
  import { toolStartedAt } from '../../logic/tools.js';
13
13
  import { CHIP_IN_MESSAGE, MENTION_TONE } from '../chips.js';
14
14
  import { useFileLinkCwd, useOpenFileLink } from '../file-links.js';
15
+ import { HostMenuItems } from '../HostMenuItems.js';
15
16
  import { ImageThumb } from '../ImageView.js';
16
17
  import { ROW_GUTTER } from '../icons.js';
17
18
  import { RunningFor, WorkRow } from './WorkRows.js';
@@ -299,6 +300,7 @@ function ReadyImage({
299
300
  open,
300
301
  saveToProject
301
302
  } = attachments;
303
+ const extra = attachments.actions?.(scopeId, chatId, attachment) ?? [];
302
304
  const detail = [attachment.width !== undefined && attachment.height !== undefined ? t('generatedImage.dimensions', {
303
305
  width: attachment.width,
304
306
  height: attachment.height
@@ -359,21 +361,23 @@ function ReadyImage({
359
361
  size: "sm",
360
362
  label: t('generatedImage.openLarge'),
361
363
  onClick: () => setLightbox(true)
362
- }), prompt !== null && /*#__PURE__*/_jsxs(Menu.Root, {
364
+ }), (prompt !== null || extra.length > 0) && /*#__PURE__*/_jsxs(Menu.Root, {
363
365
  children: [/*#__PURE__*/_jsx(IconButton, {
364
366
  icon: Ellipsis,
365
367
  size: "sm",
366
368
  label: t('generatedImage.more'),
367
369
  render: /*#__PURE__*/_jsx(Menu.Trigger, {})
368
- }), /*#__PURE__*/_jsx(Menu.Popup, {
370
+ }), /*#__PURE__*/_jsxs(Menu.Popup, {
369
371
  align: "end",
370
- children: /*#__PURE__*/_jsxs(Menu.Item, {
372
+ children: [prompt !== null && /*#__PURE__*/_jsxs(Menu.Item, {
371
373
  onClick: () => copyText(prompt),
372
374
  children: [/*#__PURE__*/_jsx(Icon, {
373
375
  icon: Copy,
374
376
  size: 14
375
377
  }), " ", t('generatedImage.copyPrompt')]
376
- })
378
+ }), /*#__PURE__*/_jsx(HostMenuItems, {
379
+ actions: extra
380
+ })]
377
381
  })]
378
382
  })]
379
383
  })