@adecore/agents-react 0.37.2 → 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.
Files changed (115) hide show
  1. package/dist/chat/chat-client.d.ts +1 -0
  2. package/dist/chat/chat-client.js +2 -1
  3. package/dist/chat/chat-references.d.ts +16 -1
  4. package/dist/chat/chat-references.js +29 -3
  5. package/dist/chat/drafts.d.ts +2 -0
  6. package/dist/chat/drafts.js +21 -5
  7. package/dist/chat/index.d.ts +1 -1
  8. package/dist/chat/logic/visual-height.d.ts +1 -0
  9. package/dist/chat/logic/visual-height.js +1 -1
  10. package/dist/chat/mentions.d.ts +31 -14
  11. package/dist/chat/mentions.js +186 -41
  12. package/dist/chat/stash.d.ts +1 -0
  13. package/dist/chat/stash.js +4 -2
  14. package/dist/chat/ui/Composer.js +438 -429
  15. package/dist/chat/ui/ComposerInput.d.ts +1 -0
  16. package/dist/chat/ui/ComposerInput.js +2 -0
  17. package/dist/chat/ui/HostMenuItems.d.ts +4 -0
  18. package/dist/chat/ui/HostMenuItems.js +15 -0
  19. package/dist/chat/ui/Markdown.d.ts +3 -1
  20. package/dist/chat/ui/Markdown.js +194 -120
  21. package/dist/chat/ui/MessageActions.js +70 -66
  22. package/dist/chat/ui/ReferenceChip.d.ts +15 -0
  23. package/dist/chat/ui/ReferenceChip.js +255 -0
  24. package/dist/chat/ui/Timeline.js +52 -29
  25. package/dist/chat/ui/TimelineMenu.js +84 -78
  26. package/dist/chat/ui/VisualFrame.d.ts +8 -0
  27. package/dist/chat/ui/VisualFrame.js +217 -115
  28. package/dist/chat/ui/chip-clipboard.d.ts +9 -0
  29. package/dist/chat/ui/chip-clipboard.js +105 -0
  30. package/dist/chat/ui/chips.d.ts +5 -0
  31. package/dist/chat/ui/chips.js +15 -1
  32. package/dist/chat/ui/composer/ComposerMenu.js +140 -79
  33. package/dist/chat/ui/composer/DraftParts.d.ts +0 -4
  34. package/dist/chat/ui/composer/DraftParts.js +0 -41
  35. package/dist/chat/ui/composer/chip-paste.d.ts +15 -0
  36. package/dist/chat/ui/composer/chip-paste.js +104 -0
  37. package/dist/chat/ui/composer/chips.d.ts +5 -0
  38. package/dist/chat/ui/composer/chips.js +59 -9
  39. package/dist/chat/ui/composer/key-handler.js +9 -0
  40. package/dist/chat/ui/composer/use-composer-draft.d.ts +0 -1
  41. package/dist/chat/ui/composer/use-composer-draft.js +2 -10
  42. package/dist/chat/ui/composer/use-composer-menus.d.ts +12 -2
  43. package/dist/chat/ui/composer/use-composer-menus.js +192 -149
  44. package/dist/chat/ui/composer/use-composer-submit.js +17 -12
  45. package/dist/chat/ui/composer/use-draft-files.d.ts +7 -1
  46. package/dist/chat/ui/composer/use-draft-files.js +90 -58
  47. package/dist/chat/ui/composer/use-take-back.js +2 -1
  48. package/dist/chat/ui/index.d.ts +1 -1
  49. package/dist/chat/ui/index.js +1 -1
  50. package/dist/chat/ui/reference-icons.d.ts +3 -0
  51. package/dist/chat/ui/reference-icons.js +15 -0
  52. package/dist/chat/ui/reference-lookup.d.ts +7 -0
  53. package/dist/chat/ui/reference-lookup.js +6 -0
  54. package/dist/chat/ui/rehype-chips.d.ts +8 -1
  55. package/dist/chat/ui/rehype-chips.js +35 -11
  56. package/dist/chat/ui/rows/GeneratedImageRow.js +8 -4
  57. package/dist/chat/ui/rows/MessageRows.js +218 -158
  58. package/dist/chat/ui/rows/VisualRow.js +184 -142
  59. package/dist/chat/ui/rows/row-rhythm.js +6 -4
  60. package/dist/chat/ui/thread-references.d.ts +4 -0
  61. package/dist/chat/ui/thread-references.js +78 -0
  62. package/dist/host-hooks.d.ts +2 -0
  63. package/dist/host-hooks.js +7 -0
  64. package/dist/host.d.ts +14 -1
  65. package/dist/index.d.ts +1 -1
  66. package/dist/locales/en/agent-chat.d.ts +20 -3
  67. package/dist/locales/en/agent-chat.js +24 -7
  68. package/dist/locales/nl/agent-chat.d.ts +20 -3
  69. package/dist/locales/nl/agent-chat.js +24 -7
  70. package/package.json +8 -8
  71. package/src/chat/chat-client.ts +4 -1
  72. package/src/chat/chat-references.ts +44 -3
  73. package/src/chat/drafts.ts +24 -6
  74. package/src/chat/index.ts +1 -1
  75. package/src/chat/logic/visual-height.ts +1 -1
  76. package/src/chat/mentions.ts +202 -36
  77. package/src/chat/stash.ts +6 -2
  78. package/src/chat/ui/Composer.tsx +14 -4
  79. package/src/chat/ui/ComposerInput.tsx +4 -0
  80. package/src/chat/ui/HostMenuItems.tsx +11 -0
  81. package/src/chat/ui/Markdown.tsx +57 -25
  82. package/src/chat/ui/MessageActions.tsx +5 -2
  83. package/src/chat/ui/ReferenceChip.tsx +74 -0
  84. package/src/chat/ui/Timeline.tsx +24 -2
  85. package/src/chat/ui/TimelineMenu.tsx +7 -3
  86. package/src/chat/ui/VisualFrame.tsx +68 -21
  87. package/src/chat/ui/chip-clipboard.ts +115 -0
  88. package/src/chat/ui/chips.ts +20 -1
  89. package/src/chat/ui/composer/ComposerMenu.tsx +53 -37
  90. package/src/chat/ui/composer/DraftParts.tsx +0 -12
  91. package/src/chat/ui/composer/chip-paste.ts +122 -0
  92. package/src/chat/ui/composer/chips.ts +60 -10
  93. package/src/chat/ui/composer/key-handler.ts +9 -0
  94. package/src/chat/ui/composer/use-composer-draft.ts +1 -7
  95. package/src/chat/ui/composer/use-composer-menus.ts +68 -29
  96. package/src/chat/ui/composer/use-composer-submit.ts +13 -6
  97. package/src/chat/ui/composer/use-draft-files.ts +39 -5
  98. package/src/chat/ui/composer/use-take-back.ts +2 -1
  99. package/src/chat/ui/index.ts +1 -1
  100. package/src/chat/ui/reference-icons.ts +17 -0
  101. package/src/chat/ui/reference-lookup.ts +13 -0
  102. package/src/chat/ui/rehype-chips.ts +38 -13
  103. package/src/chat/ui/rows/GeneratedImageRow.tsx +9 -4
  104. package/src/chat/ui/rows/MessageRows.tsx +88 -64
  105. package/src/chat/ui/rows/VisualRow.tsx +5 -1
  106. package/src/chat/ui/rows/row-rhythm.ts +6 -4
  107. package/src/chat/ui/thread-references.ts +47 -0
  108. package/src/host-hooks.ts +9 -0
  109. package/src/host.ts +24 -1
  110. package/src/index.ts +1 -0
  111. package/src/locales/en/agent-chat.ts +24 -7
  112. package/src/locales/nl/agent-chat.ts +24 -7
  113. package/dist/chat/ui/ChatReferenceChip.d.ts +0 -4
  114. package/dist/chat/ui/ChatReferenceChip.js +0 -110
  115. package/src/chat/ui/ChatReferenceChip.tsx +0 -26
@@ -1,8 +1,9 @@
1
1
  import { c as _c } from "react/compiler-runtime";
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import clsx from 'clsx';
4
- import { MessageSquare, SquareSlash, Zap } from 'lucide-react';
4
+ import { SquareSlash, Zap } from 'lucide-react';
5
5
  import { FileIcon, Icon, Menu, MiddleTruncate } from '@adecore/ui';
6
+ import { REFERENCE_ICONS } from '../reference-icons.js';
6
7
  import { optionIdOf } from './menu-aria.js';
7
8
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
9
  function splitPath(path) {
@@ -205,7 +206,7 @@ function SkillMenu(t0) {
205
206
  return t4;
206
207
  }
207
208
  function MentionMenu(t0) {
208
- const $ = _c(18);
209
+ const $ = _c(15);
209
210
  const {
210
211
  menus,
211
212
  query
@@ -215,118 +216,165 @@ function MentionMenu(t0) {
215
216
  } = useTranslation("agent-chat");
216
217
  const {
217
218
  listId,
218
- chatMatches,
219
219
  files,
220
220
  mentionCount
221
221
  } = menus;
222
- const mentionsLabelId = `${listId}-chats`;
223
222
  const filesLabelId = `${listId}-files`;
224
- const filesLabelShown = query === "" || chatMatches.length > 0;
223
+ const filesLabelShown = query === "";
225
224
  let t1;
226
- if ($[0] !== chatMatches || $[1] !== files || $[2] !== filesLabelId || $[3] !== filesLabelShown || $[4] !== listId || $[5] !== mentionCount || $[6] !== mentionsLabelId || $[7] !== menus || $[8] !== query || $[9] !== t) {
225
+ if ($[0] !== files || $[1] !== filesLabelId || $[2] !== filesLabelShown || $[3] !== listId || $[4] !== mentionCount || $[5] !== menus || $[6] !== t) {
227
226
  t1 = mentionCount > 0 && /*#__PURE__*/_jsxs("div", {
228
227
  id: listId,
229
228
  role: "listbox",
230
- "aria-label": t("composer.menu.mentions"),
231
- children: [chatMatches.length > 0 && /*#__PURE__*/_jsxs("div", {
232
- role: "group",
233
- "aria-labelledby": mentionsLabelId,
234
- children: [/*#__PURE__*/_jsx(Menu.Label, {
235
- id: mentionsLabelId,
236
- children: t("composer.mentions.chats")
237
- }), chatMatches.map((chat, index) => /*#__PURE__*/_jsxs(MenuOption, {
229
+ ...(filesLabelShown ? {
230
+ "aria-labelledby": filesLabelId
231
+ } : {
232
+ "aria-label": t("composer.mentions.files")
233
+ }),
234
+ children: [filesLabelShown && /*#__PURE__*/_jsx(Menu.Label, {
235
+ id: filesLabelId,
236
+ children: t("composer.mentions.here")
237
+ }), files.map((path, index) => {
238
+ const {
239
+ name,
240
+ dir
241
+ } = splitPath(path);
242
+ return /*#__PURE__*/_jsxs(MenuOption, {
238
243
  menus: menus,
239
244
  index: index,
240
245
  className: "items-center",
241
- onPick: () => menus.chooseChat(chat.id),
242
- children: [/*#__PURE__*/_jsx(Icon, {
243
- icon: MessageSquare,
244
- size: 14,
245
- className: "shrink-0 text-text-faint"
246
+ onPick: () => menus.chooseMention(path),
247
+ children: [/*#__PURE__*/_jsx(FileIcon, {
248
+ path: path,
249
+ size: 14
246
250
  }), /*#__PURE__*/_jsx("span", {
247
- className: "truncate text-text",
248
- children: chat.title || t("chatReference.untitled")
251
+ className: "truncate font-mono text-text",
252
+ children: name
253
+ }), dir && /*#__PURE__*/_jsx(MiddleTruncate, {
254
+ text: dir,
255
+ tooltip: false,
256
+ className: "text-text-faint"
249
257
  })]
250
- }, chat.id))]
251
- }), files.length > 0 && /*#__PURE__*/_jsxs("div", {
252
- role: "group",
253
- ...(filesLabelShown ? {
254
- "aria-labelledby": filesLabelId
255
- } : {
256
- "aria-label": t("composer.mentions.files")
257
- }),
258
- children: [filesLabelShown && /*#__PURE__*/_jsx(Menu.Label, {
259
- id: filesLabelId,
260
- children: query === "" ? t("composer.mentions.here") : t("composer.mentions.files")
261
- }), files.map((path, index_0) => {
262
- const {
263
- name,
264
- dir
265
- } = splitPath(path);
266
- return /*#__PURE__*/_jsxs(MenuOption, {
267
- menus: menus,
268
- index: chatMatches.length + index_0,
269
- className: "items-center",
270
- onPick: () => menus.chooseMention(path),
271
- children: [/*#__PURE__*/_jsx(FileIcon, {
272
- path: path,
273
- size: 14
274
- }), /*#__PURE__*/_jsx("span", {
275
- className: "truncate font-mono text-text",
276
- children: name
277
- }), dir && /*#__PURE__*/_jsx(MiddleTruncate, {
278
- text: dir,
279
- tooltip: false,
280
- className: "text-text-faint"
281
- })]
282
- }, path);
283
- })]
258
+ }, path);
284
259
  })]
285
260
  });
286
- $[0] = chatMatches;
287
- $[1] = files;
288
- $[2] = filesLabelId;
289
- $[3] = filesLabelShown;
290
- $[4] = listId;
291
- $[5] = mentionCount;
292
- $[6] = mentionsLabelId;
293
- $[7] = menus;
294
- $[8] = query;
295
- $[9] = t;
296
- $[10] = t1;
261
+ $[0] = files;
262
+ $[1] = filesLabelId;
263
+ $[2] = filesLabelShown;
264
+ $[3] = listId;
265
+ $[4] = mentionCount;
266
+ $[5] = menus;
267
+ $[6] = t;
268
+ $[7] = t1;
297
269
  } else {
298
- t1 = $[10];
270
+ t1 = $[7];
299
271
  }
300
272
  let t2;
301
- if ($[11] !== mentionCount || $[12] !== query || $[13] !== t) {
273
+ if ($[8] !== mentionCount || $[9] !== query || $[10] !== t) {
302
274
  t2 = mentionCount === 0 && /*#__PURE__*/_jsx("div", {
303
275
  className: "px-2 py-1 text-xs text-text-faint",
304
276
  children: query ? t("composer.mentions.noMatch") : t("composer.mentions.empty")
305
277
  });
306
- $[11] = mentionCount;
307
- $[12] = query;
308
- $[13] = t;
309
- $[14] = t2;
278
+ $[8] = mentionCount;
279
+ $[9] = query;
280
+ $[10] = t;
281
+ $[11] = t2;
310
282
  } else {
311
- t2 = $[14];
283
+ t2 = $[11];
312
284
  }
313
285
  let t3;
314
- if ($[15] !== t1 || $[16] !== t2) {
286
+ if ($[12] !== t1 || $[13] !== t2) {
315
287
  t3 = /*#__PURE__*/_jsxs("div", {
316
288
  className: "border-b border-border px-1.5 py-1.5",
317
289
  children: [t1, t2]
318
290
  });
319
- $[15] = t1;
320
- $[16] = t2;
321
- $[17] = t3;
291
+ $[12] = t1;
292
+ $[13] = t2;
293
+ $[14] = t3;
322
294
  } else {
323
- t3 = $[17];
295
+ t3 = $[14];
324
296
  }
325
297
  return t3;
326
298
  }
299
+ /* The views, nodes and chats a `#` offers, what stands on this canvas first, each with its kind or what tells it apart. */
300
+ function ReferenceMenu(t0) {
301
+ const $ = _c(5);
302
+ const {
303
+ menus
304
+ } = t0;
305
+ const {
306
+ t
307
+ } = useTranslation("agent-chat");
308
+ const {
309
+ listId,
310
+ referenceMatches
311
+ } = menus;
312
+ let t1;
313
+ if ($[0] !== listId || $[1] !== menus || $[2] !== referenceMatches || $[3] !== t) {
314
+ const groups = ["canvas", "views"].map(group => ({
315
+ group,
316
+ items: referenceMatches.filter(item => item.group === group)
317
+ })).filter(_temp2);
318
+ t1 = /*#__PURE__*/_jsx("div", {
319
+ className: "border-b border-border px-1.5 py-1.5",
320
+ children: referenceMatches.length > 0 ? /*#__PURE__*/_jsx("div", {
321
+ id: listId,
322
+ role: "listbox",
323
+ "aria-label": t("composer.menu.references"),
324
+ children: groups.map(t2 => {
325
+ const {
326
+ group: group_0,
327
+ items
328
+ } = t2;
329
+ return /*#__PURE__*/_jsxs("div", {
330
+ role: "group",
331
+ "aria-labelledby": `${listId}-${group_0}`,
332
+ children: [/*#__PURE__*/_jsx(Menu.Label, {
333
+ id: `${listId}-${group_0}`,
334
+ children: t(`composer.references.${group_0}`)
335
+ }), items.map(item_0 => {
336
+ const index = referenceMatches.indexOf(item_0);
337
+ return /*#__PURE__*/_jsxs(MenuOption, {
338
+ menus: menus,
339
+ index: index,
340
+ className: "items-center",
341
+ onPick: () => menus.chooseReference(item_0),
342
+ children: [/*#__PURE__*/_jsx(Icon, {
343
+ icon: REFERENCE_ICONS[item_0.kind],
344
+ size: 14,
345
+ className: "shrink-0 text-text-faint"
346
+ }), /*#__PURE__*/_jsx("span", {
347
+ className: "truncate text-text",
348
+ children: item_0.name
349
+ }), /*#__PURE__*/_jsx("span", {
350
+ className: "ml-auto shrink-0 truncate pl-2 text-text-faint",
351
+ children: item_0.detail ?? t(`composer.references.kinds.${item_0.kind}`)
352
+ })]
353
+ }, item_0.id);
354
+ })]
355
+ }, group_0);
356
+ })
357
+ }) : /*#__PURE__*/_jsx("div", {
358
+ className: "px-2 py-1 text-xs text-text-faint",
359
+ children: t("composer.references.noMatch")
360
+ })
361
+ });
362
+ $[0] = listId;
363
+ $[1] = menus;
364
+ $[2] = referenceMatches;
365
+ $[3] = t;
366
+ $[4] = t1;
367
+ } else {
368
+ t1 = $[4];
369
+ }
370
+ return t1;
371
+ }
327
372
  /* Whichever picker the caret opened, over the box. */
373
+ function _temp2(entry) {
374
+ return entry.items.length > 0;
375
+ }
328
376
  export function ComposerMenu(t0) {
329
- const $ = _c(6);
377
+ const $ = _c(8);
330
378
  const {
331
379
  menus
332
380
  } = t0;
@@ -370,5 +418,18 @@ export function ComposerMenu(t0) {
370
418
  }
371
419
  return t1;
372
420
  }
421
+ if (menus.referenceMenuOpen) {
422
+ let t1;
423
+ if ($[6] !== menus) {
424
+ t1 = /*#__PURE__*/_jsx(ReferenceMenu, {
425
+ menus: menus
426
+ });
427
+ $[6] = menus;
428
+ $[7] = t1;
429
+ } else {
430
+ t1 = $[7];
431
+ }
432
+ return t1;
433
+ }
373
434
  return null;
374
435
  }
@@ -7,10 +7,6 @@ interface DraftQuoteProps {
7
7
  onSourceGone(): void;
8
8
  }
9
9
  export declare function DraftQuote({ chatId, quote, source, onRemove, onSourceGone }: DraftQuoteProps): import("react").JSX.Element;
10
- export declare function DraftChats({ chats, onRemove }: {
11
- chats: readonly string[];
12
- onRemove(id: string): void;
13
- }): import("react").JSX.Element;
14
10
  export declare function DraftAttachments({ attachments, onRemove }: {
15
11
  attachments: readonly ChatAttachmentUpload[];
16
12
  onRemove(index: number): void;
@@ -5,7 +5,6 @@ import { Icon, IconButton, Tooltip } from '@adecore/ui';
5
5
  import { useChatScope } from '../../../scope.js';
6
6
  import { formatBytes, isImageAttachment, uploadBytes } from '../../attachments.js';
7
7
  import { jumpToTimelineItem } from '../../timeline-scroll.js';
8
- import { ChatReferenceChip } from '../ChatReferenceChip.js';
9
8
  import { UploadThumb } from '../UploadThumb.js';
10
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
10
  const ROW = 'px-5 pt-4 @max-md/composer:px-3.5 @max-md/composer:pt-3';
@@ -86,46 +85,6 @@ export function DraftQuote(t0) {
86
85
  }
87
86
  return t4;
88
87
  }
89
- /* Other chats the message points at, beside the text rather than in it. */
90
- export function DraftChats(t0) {
91
- const $ = _c(7);
92
- const {
93
- chats,
94
- onRemove
95
- } = t0;
96
- let t1;
97
- if ($[0] !== chats || $[1] !== onRemove) {
98
- let t2;
99
- if ($[3] !== onRemove) {
100
- t2 = id => /*#__PURE__*/_jsx(ChatReferenceChip, {
101
- chatId: id,
102
- onRemove: () => onRemove(id)
103
- }, id);
104
- $[3] = onRemove;
105
- $[4] = t2;
106
- } else {
107
- t2 = $[4];
108
- }
109
- t1 = chats.map(t2);
110
- $[0] = chats;
111
- $[1] = onRemove;
112
- $[2] = t1;
113
- } else {
114
- t1 = $[2];
115
- }
116
- let t2;
117
- if ($[5] !== t1) {
118
- t2 = /*#__PURE__*/_jsx("div", {
119
- className: `flex flex-wrap gap-1.5 ${ROW}`,
120
- children: t1
121
- });
122
- $[5] = t1;
123
- $[6] = t2;
124
- } else {
125
- t2 = $[6];
126
- }
127
- return t2;
128
- }
129
88
  /* The files the message carries: an image as a thumbnail, anything else by its name and size. */
130
89
  export function DraftAttachments(t0) {
131
90
  const $ = _c(9);
@@ -0,0 +1,15 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ import type { ChatDraft } from '../../drafts.js';
3
+ import type { Referable } from '../../chat-references.js';
4
+ import { type ClipboardChips, type ReferenceToken, type TextRange } from '../../mentions.js';
5
+ export interface PasteChoices {
6
+ skills: readonly string[];
7
+ referables: readonly Referable[];
8
+ referenced: readonly string[];
9
+ mentions: boolean;
10
+ }
11
+ export declare function pastedChipsIn(text: string, from: number, to: number, code: readonly TextRange[], choices: PasteChoices): ClipboardChips;
12
+ export declare function mayHoldChips(text: string): boolean;
13
+ export declare function withPastedChips(draft: ChatDraft, chips: ClipboardChips, choices: PasteChoices): ChatDraft;
14
+ export declare function pasteWithChips(view: EditorView, clip: string, copied: ClipboardChips | null, choices: PasteChoices): ClipboardChips | null;
15
+ export declare function copyWithChipsFromEditor(event: ClipboardEvent, view: EditorView, draft: Pick<ChatDraft, 'mentions' | 'skills'>, references: readonly ReferenceToken[]): boolean;
@@ -0,0 +1,104 @@
1
+ import { chipRanges, clipboardChipsOf, hasClipboardChips, namedReferenceRanges, tokenizeChips, writeClipboardChips } from '../../mentions.js';
2
+ import { codeRanges } from './editor.js';
3
+ /*
4
+ * The chips a plain paste spells between `from` and `to` of the text it landed in: `$name` for a skill
5
+ * there is, `#Name` by the reply rule. Code spans and code blocks keep what they hold.
6
+ */
7
+ export function pastedChipsIn(text, from, to, code, choices) {
8
+ const outside = [...code, {
9
+ from: 0,
10
+ to: from
11
+ }, {
12
+ from: to,
13
+ to: text.length
14
+ }];
15
+ const skills = chipRanges(text, [], [...choices.skills], outside);
16
+ const references = namedReferenceRanges(text, choices.referables, choices.referenced, outside);
17
+ return clipboardChipsOf(text.slice(from, to), [...skills, ...references].map(range => range.kind === 'skill' ? {
18
+ kind: 'skill',
19
+ name: range.value
20
+ } : {
21
+ kind: 'reference',
22
+ id: range.value,
23
+ name: text.slice(range.from + 1, range.to)
24
+ }));
25
+ }
26
+ /* Whether a plain paste could make a chip at all, before it goes in as text. */
27
+ export function mayHoldChips(text) {
28
+ return /(^|[\s([{"'“‘])[$#]\p{L}/u.test(text);
29
+ }
30
+ /* The draft with the chips of a paste added, a chat beside the views and nodes as the picker keeps it. */
31
+ export function withPastedChips(draft, chips, choices) {
32
+ const add = (ids, more) => [...ids, ...more.filter((id, index) => !ids.includes(id) && more.indexOf(id) === index)];
33
+ const isChat = token => choices.referables.some(item => item.id === token.id && item.kind === 'chat');
34
+ const chats = chips.references.filter(isChat);
35
+ const references = chips.references.filter(token => !isChat(token));
36
+ return {
37
+ ...draft,
38
+ mentions: choices.mentions ? add(draft.mentions, chips.mentions) : draft.mentions,
39
+ skills: add(draft.skills, chips.skills),
40
+ chats: add(draft.chats, chats.map(token => token.id)),
41
+ references: add(draft.references, references.map(token => token.id)),
42
+ referenceNames: chips.references.length === 0 ? draft.referenceNames : {
43
+ ...draft.referenceNames,
44
+ ...Object.fromEntries(chips.references.map(token => [token.id, token.name]))
45
+ }
46
+ };
47
+ }
48
+ /*
49
+ * Puts a paste in the editor and says which chips it brings: the ones it was copied with, or the ones
50
+ * its plain text spells. Null when there are none, so the editor pastes it as it always does.
51
+ */
52
+ export function pasteWithChips(view, clip, copied, choices) {
53
+ if (copied !== null && hasClipboardChips(copied)) {
54
+ view.dispatch(view.state.replaceSelection(copied.text), {
55
+ scrollIntoView: true,
56
+ userEvent: 'input.paste'
57
+ });
58
+ return copied;
59
+ }
60
+ if (!mayHoldChips(clip)) {
61
+ return null;
62
+ }
63
+ const from = view.state.selection.main.from;
64
+ const inserted = view.state.replaceSelection(clip);
65
+ const state = view.state.update(inserted).state;
66
+ const to = state.selection.main.head;
67
+ const found = pastedChipsIn(state.doc.toString(), from, to, codeRanges(state), choices);
68
+ if (!hasClipboardChips(found)) {
69
+ return null;
70
+ }
71
+ view.dispatch(inserted, {
72
+ scrollIntoView: true,
73
+ userEvent: 'input.paste'
74
+ });
75
+ return found;
76
+ }
77
+ /* A copy or a cut of a selection that holds chips, which carries them beside its text. False leaves it to the editor. */
78
+ export function copyWithChipsFromEditor(event, view, draft, references) {
79
+ const {
80
+ from,
81
+ to
82
+ } = view.state.selection.main;
83
+ if (from === to || event.clipboardData === null) {
84
+ return false;
85
+ }
86
+ const text = view.state.sliceDoc(from, to);
87
+ const chips = clipboardChipsOf(text, tokenizeChips(text, draft.mentions, draft.skills, references));
88
+ if (!hasClipboardChips(chips)) {
89
+ return false;
90
+ }
91
+ writeClipboardChips(event.clipboardData, chips);
92
+ event.preventDefault();
93
+ if (event.type === 'cut') {
94
+ view.dispatch({
95
+ changes: {
96
+ from,
97
+ to
98
+ },
99
+ scrollIntoView: true,
100
+ userEvent: 'delete.cut'
101
+ });
102
+ }
103
+ return true;
104
+ }
@@ -1,6 +1,11 @@
1
1
  import { type Extension } from '@codemirror/state';
2
+ import type { ReferableKind } from '../../chat-references.js';
3
+ import { type ReferenceToken } from '../../mentions.js';
2
4
  export interface ChipChoices {
3
5
  mentions: string[];
4
6
  skills: string[];
7
+ references?: ReadonlyArray<ReferenceToken & {
8
+ kind: ReferableKind | null;
9
+ }>;
5
10
  }
6
11
  export declare function chipDecorations(value: ChipChoices): Extension;
@@ -1,8 +1,12 @@
1
+ import { createElement } from 'react';
2
+ import { createRoot } from 'react-dom/client';
1
3
  import { syntaxTree } from '@codemirror/language';
2
- import { Facet, RangeSetBuilder, StateField } from '@codemirror/state';
3
- import { Decoration, EditorView } from '@codemirror/view';
4
+ import { Facet, StateField } from '@codemirror/state';
5
+ import { Decoration, EditorView, WidgetType } from '@codemirror/view';
6
+ import { Icon } from '@adecore/ui';
4
7
  import { chipRanges } from '../../mentions.js';
5
- import { CHIP_IN_EDITOR, MENTION_TONE, SKILL_TONE } from '../chips.js';
8
+ import { CHIP_IN_EDITOR, MENTION_TONE, REFERENCE_TONE, SKILL_TONE, TOKEN_ICON_IN_EDITOR } from '../chips.js';
9
+ import { REFERENCE_ICONS } from '../reference-icons.js';
6
10
  import { codeRanges } from './editor.js';
7
11
  const NO_CHOICES = {
8
12
  mentions: [],
@@ -17,17 +21,63 @@ const mentionMark = Decoration.mark({
17
21
  const skillMark = Decoration.mark({
18
22
  class: `${SKILL_TONE} ${CHIP_IN_EDITOR}`
19
23
  });
24
+ // Inclusive at the start, so the pill opens before the mark of the kind that replaces its `#` and holds it.
25
+ const referenceMark = Decoration.mark({
26
+ class: `${REFERENCE_TONE} ${CHIP_IN_EDITOR}`,
27
+ inclusiveStart: true
28
+ });
29
+ const roots = new WeakMap();
30
+ /* The kind's mark in place of the `#`, the way a sent message draws it. */
31
+ class ReferenceIcon extends WidgetType {
32
+ kind;
33
+ constructor(kind) {
34
+ super();
35
+ this.kind = kind;
36
+ }
37
+ eq(other) {
38
+ return other.kind === this.kind;
39
+ }
40
+ toDOM() {
41
+ const holder = document.createElement('span');
42
+ holder.className = TOKEN_ICON_IN_EDITOR;
43
+ const root = createRoot(holder);
44
+ root.render(createElement(Icon, {
45
+ icon: REFERENCE_ICONS[this.kind],
46
+ size: 12
47
+ }));
48
+ roots.set(holder, root);
49
+ return holder;
50
+ }
51
+ destroy(dom) {
52
+ const root = roots.get(dom);
53
+ roots.delete(dom);
54
+ // The editor drops a widget inside React's own commit, where a root may not unmount at once.
55
+ queueMicrotask(() => root?.unmount());
56
+ }
57
+ }
20
58
  function chipMarks(state) {
21
59
  const {
22
60
  mentions,
23
- skills
61
+ skills,
62
+ references = []
24
63
  } = state.facet(choices);
25
- const builder = new RangeSetBuilder();
64
+ const kinds = new Map(references.map(token => [token.id, token.kind]));
65
+ const marks = [];
26
66
  // `@a.ts` in backticks is code, not a mention.
27
- for (const range of chipRanges(state.doc.toString(), mentions, skills, codeRanges(state))) {
28
- builder.add(range.from, range.to, range.kind === 'skill' ? skillMark : mentionMark);
67
+ for (const range of chipRanges(state.doc.toString(), mentions, skills, codeRanges(state), references)) {
68
+ if (range.kind !== 'reference') {
69
+ marks.push((range.kind === 'skill' ? skillMark : mentionMark).range(range.from, range.to));
70
+ continue;
71
+ }
72
+ marks.push(referenceMark.range(range.from, range.to));
73
+ const kind = kinds.get(range.value);
74
+ if (kind !== undefined && kind !== null) {
75
+ marks.push(Decoration.replace({
76
+ widget: new ReferenceIcon(kind)
77
+ }).range(range.from, range.from + 1));
78
+ }
29
79
  }
30
- return builder.finish();
80
+ return Decoration.set(marks, true);
31
81
  }
32
82
  const chipField = StateField.define({
33
83
  create: chipMarks,
@@ -35,7 +85,7 @@ const chipField = StateField.define({
35
85
  update: (marks, tr) => tr.docChanged || tr.startState.facet(choices) !== tr.state.facet(choices) || syntaxTree(tr.startState) !== syntaxTree(tr.state) ? chipMarks(tr.state) : marks,
36
86
  provide: field => EditorView.decorations.from(field)
37
87
  });
38
- /* Draws the chosen `@path` and `$name` tokens as chips; a new `ChipChoices` object is what redraws them. */
88
+ /* Draws the chosen `@path`, `$name` and `#name` tokens as chips; a new `ChipChoices` object is what redraws them. */
39
89
  export function chipDecorations(value) {
40
90
  return [choices.of(value), chipField];
41
91
  }
@@ -76,6 +76,15 @@ function pickInMenu(e, {
76
76
  return true;
77
77
  }
78
78
  }
79
+ if (menus.referenceMenuOpen && menus.referenceMatches.length > 0) {
80
+ if (menus.moveInMenu(e.key, menus.referenceMatches.length)) {
81
+ return true;
82
+ }
83
+ if (e.key === 'Tab' || e.key === 'Enter') {
84
+ menus.chooseReferenceAt(activeIndex);
85
+ return true;
86
+ }
87
+ }
79
88
  return false;
80
89
  }
81
90
  /* Enter sends, adds a line, or carries a list on, by the send key and where the caret is. */
@@ -8,7 +8,6 @@ export interface ComposerDraft {
8
8
  setText(next: string, mentions?: string[], skills?: string[]): void;
9
9
  clear(): void;
10
10
  removeQuote(): void;
11
- removeChat(id: string): void;
12
11
  removeAttachment(index: number): void;
13
12
  }
14
13
  export declare function useComposerDraft(chatId: string, inputRef: RefObject<ComposerInputHandle | null>): ComposerDraft;
@@ -44,17 +44,10 @@ export function useComposerDraft(chatId, inputRef) {
44
44
  }));
45
45
  inputRef.current?.focus();
46
46
  };
47
- const removeChat = id => {
47
+ const removeAttachment = index => {
48
48
  setDraft(current_3 => ({
49
49
  ...current_3,
50
- chats: current_3.chats.filter(entry => entry !== id)
51
- }));
52
- inputRef.current?.focus();
53
- };
54
- const removeAttachment = index => {
55
- setDraft(current_4 => ({
56
- ...current_4,
57
- attachments: current_4.attachments.filter((_, i) => i !== index)
50
+ attachments: current_3.attachments.filter((_, i) => i !== index)
58
51
  }));
59
52
  };
60
53
  return {
@@ -64,7 +57,6 @@ export function useComposerDraft(chatId, inputRef) {
64
57
  setText,
65
58
  clear,
66
59
  removeQuote,
67
- removeChat,
68
60
  removeAttachment
69
61
  };
70
62
  }
@@ -1,6 +1,7 @@
1
1
  import { type RefObject } from 'react';
2
2
  import type { EditorState } from '@codemirror/state';
3
3
  import type { ChatInfo, ProviderCapabilities } from '@adecore/agent-contracts';
4
+ import { type Referable } from '../../chat-references.js';
4
5
  import { type MentionQuery, type TextRange } from '../../mentions.js';
5
6
  import type { ComposerInputHandle } from '../ComposerInput.js';
6
7
  import type { ComposerDraft } from './use-composer-draft.js';
@@ -24,6 +25,7 @@ export declare function useComposerMenus({ chatId, info, capabilities, providerN
24
25
  activeIndex: number;
25
26
  inputAria: Record<string, string>;
26
27
  hasSkills: boolean;
28
+ skillNames: string[];
27
29
  commandQuery: string | null;
28
30
  commands: ComposerCommand[];
29
31
  commandMenuOpen: boolean;
@@ -36,14 +38,22 @@ export declare function useComposerMenus({ chatId, info, capabilities, providerN
36
38
  mention: MentionQuery | null;
37
39
  mentionMenuOpen: boolean;
38
40
  mentionCount: number;
39
- chatMatches: import("../../index.js").ProjectChat[];
40
41
  files: string[];
42
+ referenceMenuOpen: boolean;
43
+ referenceMatches: Referable[];
44
+ referables: import("../../index.js").Referables;
45
+ referenceChips: {
46
+ id: string;
47
+ name: string;
48
+ kind: import("../../index.js").ReferableKind | null;
49
+ }[];
41
50
  setMenuIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
42
51
  trackTriggers: (value: string, selection: TextRange, state: EditorState) => void;
43
52
  close: () => void;
44
53
  chooseMention: (path: string) => void;
45
- chooseChat: (id: string) => void;
46
54
  chooseMentionAt: (index: number) => void;
55
+ chooseReference: (item: Referable) => void;
56
+ chooseReferenceAt: (index: number) => void;
47
57
  chooseSkill: (name: string) => void;
48
58
  pickCommand: (command: ComposerCommand, index: number) => void;
49
59
  moveInMenu: (key: string, count: number) => boolean;