@adecore/agents-react 0.37.1 → 0.37.3

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 (105) 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/picker-entries.d.ts +6 -1
  9. package/dist/chat/logic/picker-entries.js +10 -0
  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/Markdown.d.ts +3 -1
  18. package/dist/chat/ui/Markdown.js +194 -120
  19. package/dist/chat/ui/MessageActions.js +70 -66
  20. package/dist/chat/ui/Pickers.js +151 -207
  21. package/dist/chat/ui/ReferenceChip.d.ts +15 -0
  22. package/dist/chat/ui/ReferenceChip.js +255 -0
  23. package/dist/chat/ui/RunSettings.js +25 -21
  24. package/dist/chat/ui/Timeline.js +52 -29
  25. package/dist/chat/ui/TimelineMenu.js +84 -78
  26. package/dist/chat/ui/chip-clipboard.d.ts +9 -0
  27. package/dist/chat/ui/chip-clipboard.js +105 -0
  28. package/dist/chat/ui/chips.d.ts +5 -0
  29. package/dist/chat/ui/chips.js +15 -1
  30. package/dist/chat/ui/composer/ComposerMenu.js +140 -79
  31. package/dist/chat/ui/composer/DraftParts.d.ts +0 -4
  32. package/dist/chat/ui/composer/DraftParts.js +0 -41
  33. package/dist/chat/ui/composer/chip-paste.d.ts +15 -0
  34. package/dist/chat/ui/composer/chip-paste.js +104 -0
  35. package/dist/chat/ui/composer/chips.d.ts +5 -0
  36. package/dist/chat/ui/composer/chips.js +59 -9
  37. package/dist/chat/ui/composer/key-handler.js +9 -0
  38. package/dist/chat/ui/composer/use-composer-draft.d.ts +0 -1
  39. package/dist/chat/ui/composer/use-composer-draft.js +2 -10
  40. package/dist/chat/ui/composer/use-composer-menus.d.ts +12 -2
  41. package/dist/chat/ui/composer/use-composer-menus.js +192 -149
  42. package/dist/chat/ui/composer/use-composer-submit.js +17 -12
  43. package/dist/chat/ui/composer/use-draft-files.d.ts +7 -1
  44. package/dist/chat/ui/composer/use-draft-files.js +90 -58
  45. package/dist/chat/ui/composer/use-take-back.js +2 -1
  46. package/dist/chat/ui/index.d.ts +0 -1
  47. package/dist/chat/ui/index.js +0 -1
  48. package/dist/chat/ui/reference-icons.d.ts +3 -0
  49. package/dist/chat/ui/reference-icons.js +15 -0
  50. package/dist/chat/ui/reference-lookup.d.ts +7 -0
  51. package/dist/chat/ui/reference-lookup.js +6 -0
  52. package/dist/chat/ui/rehype-chips.d.ts +8 -1
  53. package/dist/chat/ui/rehype-chips.js +35 -11
  54. package/dist/chat/ui/rows/MessageRows.js +218 -158
  55. package/dist/chat/ui/thread-references.d.ts +4 -0
  56. package/dist/chat/ui/thread-references.js +78 -0
  57. package/dist/host-hooks.d.ts +2 -0
  58. package/dist/host-hooks.js +7 -0
  59. package/dist/host.d.ts +3 -0
  60. package/dist/locales/en/agent-chat.d.ts +20 -3
  61. package/dist/locales/en/agent-chat.js +24 -7
  62. package/dist/locales/nl/agent-chat.d.ts +20 -3
  63. package/dist/locales/nl/agent-chat.js +24 -7
  64. package/package.json +8 -8
  65. package/src/chat/chat-client.ts +4 -1
  66. package/src/chat/chat-references.ts +44 -3
  67. package/src/chat/drafts.ts +24 -6
  68. package/src/chat/index.ts +1 -1
  69. package/src/chat/logic/picker-entries.ts +12 -1
  70. package/src/chat/mentions.ts +202 -36
  71. package/src/chat/stash.ts +6 -2
  72. package/src/chat/ui/Composer.tsx +14 -4
  73. package/src/chat/ui/ComposerInput.tsx +4 -0
  74. package/src/chat/ui/Markdown.tsx +57 -25
  75. package/src/chat/ui/MessageActions.tsx +5 -2
  76. package/src/chat/ui/Pickers.tsx +8 -2
  77. package/src/chat/ui/ReferenceChip.tsx +74 -0
  78. package/src/chat/ui/RunSettings.tsx +29 -23
  79. package/src/chat/ui/Timeline.tsx +24 -2
  80. package/src/chat/ui/TimelineMenu.tsx +7 -3
  81. package/src/chat/ui/chip-clipboard.ts +115 -0
  82. package/src/chat/ui/chips.ts +20 -1
  83. package/src/chat/ui/composer/ComposerMenu.tsx +53 -37
  84. package/src/chat/ui/composer/DraftParts.tsx +0 -12
  85. package/src/chat/ui/composer/chip-paste.ts +122 -0
  86. package/src/chat/ui/composer/chips.ts +60 -10
  87. package/src/chat/ui/composer/key-handler.ts +9 -0
  88. package/src/chat/ui/composer/use-composer-draft.ts +1 -7
  89. package/src/chat/ui/composer/use-composer-menus.ts +68 -29
  90. package/src/chat/ui/composer/use-composer-submit.ts +13 -6
  91. package/src/chat/ui/composer/use-draft-files.ts +39 -5
  92. package/src/chat/ui/composer/use-take-back.ts +2 -1
  93. package/src/chat/ui/index.ts +0 -1
  94. package/src/chat/ui/reference-icons.ts +17 -0
  95. package/src/chat/ui/reference-lookup.ts +13 -0
  96. package/src/chat/ui/rehype-chips.ts +38 -13
  97. package/src/chat/ui/rows/MessageRows.tsx +88 -64
  98. package/src/chat/ui/thread-references.ts +47 -0
  99. package/src/host-hooks.ts +9 -0
  100. package/src/host.ts +6 -1
  101. package/src/locales/en/agent-chat.ts +24 -7
  102. package/src/locales/nl/agent-chat.ts +24 -7
  103. package/dist/chat/ui/ChatReferenceChip.d.ts +0 -4
  104. package/dist/chat/ui/ChatReferenceChip.js +0 -110
  105. package/src/chat/ui/ChatReferenceChip.tsx +0 -26
@@ -0,0 +1,78 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { useChatHost } from '../../host.js';
3
+ import { useHostReferables } from '../../host-hooks.js';
4
+ import { useChatScope } from '../../scope.js';
5
+ import { useChatRow } from '../../state/chats.js';
6
+ const referencedOf = new WeakMap();
7
+ /*
8
+ * The ids a person pointed at with `#` in a thread, one per line. A string, so the selector that reads
9
+ * it answers the same value until a message with another one comes; the structure a streamed word
10
+ * leaves alone is what it is cached on.
11
+ */
12
+ export function threadReferencedKey(structure) {
13
+ if (structure === undefined) {
14
+ return '';
15
+ }
16
+ const cached = referencedOf.get(structure);
17
+ if (cached !== undefined) {
18
+ return cached;
19
+ }
20
+ const ids = new Set();
21
+ for (const item of Object.values(structure)) {
22
+ if (item.kind === 'user') {
23
+ for (const id of [...(item.chats ?? []), ...(item.references ?? [])]) {
24
+ ids.add(id);
25
+ }
26
+ }
27
+ }
28
+ const key = [...ids].join('\n');
29
+ referencedOf.set(structure, key);
30
+ return key;
31
+ }
32
+ /* What the views, nodes and chats of a thread stand for now, and how one opens, for the rows that draw chips. */
33
+ export function useReferenceLookup(chatId) {
34
+ const $ = _c(9);
35
+ const {
36
+ id: scopeId
37
+ } = useChatScope();
38
+ const {
39
+ openReference
40
+ } = useChatHost();
41
+ const referables = useHostReferables(scopeId, chatId);
42
+ const key = useChatRow(chatId, _temp);
43
+ let t0;
44
+ if ($[0] !== openReference || $[1] !== scopeId) {
45
+ t0 = openReference ? id => openReference(scopeId, id) : null;
46
+ $[0] = openReference;
47
+ $[1] = scopeId;
48
+ $[2] = t0;
49
+ } else {
50
+ t0 = $[2];
51
+ }
52
+ let t1;
53
+ if ($[3] !== key) {
54
+ t1 = key === "" ? [] : key.split("\n");
55
+ $[3] = key;
56
+ $[4] = t1;
57
+ } else {
58
+ t1 = $[4];
59
+ }
60
+ let t2;
61
+ if ($[5] !== referables.items || $[6] !== t0 || $[7] !== t1) {
62
+ t2 = {
63
+ items: referables.items,
64
+ open: t0,
65
+ referenced: t1
66
+ };
67
+ $[5] = referables.items;
68
+ $[6] = t0;
69
+ $[7] = t1;
70
+ $[8] = t2;
71
+ } else {
72
+ t2 = $[8];
73
+ }
74
+ return t2;
75
+ }
76
+ function _temp(chat) {
77
+ return threadReferencedKey(chat?.structure);
78
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ProviderPlatform } from '@adecore/agent-contracts';
2
+ import { type Referables } from './chat/chat-references.js';
2
3
  import { type ChatHost, type CommandLook } from './host.js';
3
4
  export declare function useHostCodeMode(): 'light' | 'dark';
4
5
  export declare function useHostCodeThemes(): {
@@ -13,6 +14,7 @@ export declare function useHostStreaming(): ReturnType<ChatHost['useStreaming']>
13
14
  export declare function useHostSendDelivery(): ReturnType<ChatHost['useSendDelivery']>;
14
15
  export declare function useHostTimelineFind(...args: Parameters<ChatHost['useTimelineFind']>): ReturnType<ChatHost['useTimelineFind']>;
15
16
  export declare function useHostReferableChats(): ReturnType<ChatHost['useReferableChats']>;
17
+ export declare function useHostReferables(scopeId: string, chatId: string): Referables;
16
18
  export declare function useHostLoginBlocked(...args: Parameters<ChatHost['useLoginBlocked']>): ReturnType<ChatHost['useLoginBlocked']>;
17
19
  export declare function useHostMachinePlatform(scopeId: string): ProviderPlatform | null;
18
20
  export declare function useHostProjectLook(...args: Parameters<ChatHost['useProjectLook']>): ReturnType<ChatHost['useProjectLook']>;
@@ -1,5 +1,6 @@
1
1
  'use no memo';
2
2
 
3
+ import { referablesOfChats } from './chat/chat-references.js';
3
4
  import { useChatHost } from './host.js';
4
5
  export function useHostCodeMode() {
5
6
  return useChatHost().code.useMode();
@@ -31,6 +32,12 @@ export function useHostTimelineFind(...args) {
31
32
  export function useHostReferableChats() {
32
33
  return useChatHost().useReferableChats();
33
34
  }
35
+ const useReferablesFromChats = (_scopeId, _chatId) => referablesOfChats(useHostReferableChats());
36
+ /* A host without views of its own offers its chats. */
37
+ export function useHostReferables(scopeId, chatId) {
38
+ const useReferables = useChatHost().useReferables ?? useReferablesFromChats;
39
+ return useReferables(scopeId, chatId);
40
+ }
34
41
  export function useHostLoginBlocked(...args) {
35
42
  return useChatHost().useLoginBlocked(...args);
36
43
  }
package/dist/host.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { EditorView } from '@codemirror/view';
4
4
  import type { Highlighter, ThemeRegistration } from 'shiki';
5
5
  import type { EditorEngine } from '@adecore/editor';
6
6
  import type { AgentKind, ChatCheckpointDiff, ChatConfigurePayload, ChatGoalAction, ChatItem, ChatSubagentItem, ChatUiChoicePayload, ChatUiQueryPayload, ChatUiQueryReading, ChatUiLinkPayload, ChatUiLinkReading, ProviderPlatform } from '@adecore/agent-contracts';
7
+ import type { Referables } from './chat/chat-references.js';
7
8
  import type { TimelineRow } from './chat/logic/timeline.js';
8
9
  import type { FindReveal } from './chat/ui/find-reveal.js';
9
10
  import type { PromptAction } from './prompts/logic/prompts.js';
@@ -247,6 +248,8 @@ export interface ChatHost {
247
248
  id: string;
248
249
  title: string;
249
250
  }>;
251
+ useReferables?(scopeId: string, chatId: string): Referables;
252
+ openReference?(scopeId: string, id: string): void;
250
253
  openLogin: ((scopeId: string, kind: AgentKind, accountId: string, name: string) => Promise<void>) | null;
251
254
  useLoginBlocked(scopeId: string): string | null;
252
255
  runProviderCommand?(scopeId: string, kind: AgentKind, command: string): void;
@@ -16,10 +16,9 @@ declare const _default: {
16
16
  unreadable: string;
17
17
  unreadableThumbnail: string;
18
18
  };
19
- chatReference: {
19
+ references: {
20
20
  gone: string;
21
21
  untitled: string;
22
- remove: string;
23
22
  };
24
23
  fork: {
25
24
  refusal: {
@@ -305,6 +304,7 @@ declare const _default: {
305
304
  commands: string;
306
305
  skills: string;
307
306
  mentions: string;
307
+ references: string;
308
308
  };
309
309
  held: {
310
310
  question: string;
@@ -349,10 +349,27 @@ declare const _default: {
349
349
  mentions: {
350
350
  here: string;
351
351
  files: string;
352
- chats: string;
353
352
  noMatch: string;
354
353
  empty: string;
355
354
  };
355
+ references: {
356
+ canvas: string;
357
+ views: string;
358
+ noMatch: string;
359
+ kinds: {
360
+ canvas: string;
361
+ chat: string;
362
+ terminal: string;
363
+ note: string;
364
+ text: string;
365
+ drawing: string;
366
+ diagram: string;
367
+ browser: string;
368
+ device: string;
369
+ file: string;
370
+ database: string;
371
+ };
372
+ };
356
373
  queue: {
357
374
  attachments_one: string;
358
375
  attachments_other: string;
@@ -16,10 +16,9 @@ export default {
16
16
  unreadable: 'Could not read {{name}}',
17
17
  unreadableThumbnail: 'Could not read the thumbnail'
18
18
  },
19
- chatReference: {
20
- gone: 'Chat not in this project',
21
- untitled: 'Untitled chat',
22
- remove: 'Remove {{title}}'
19
+ references: {
20
+ gone: 'Not in this project',
21
+ untitled: 'Untitled'
23
22
  },
24
23
  fork: {
25
24
  refusal: {
@@ -304,7 +303,8 @@ export default {
304
303
  menu: {
305
304
  commands: 'Commands',
306
305
  skills: 'Skills',
307
- mentions: 'Mentions'
306
+ mentions: 'Mentions',
307
+ references: 'Views and nodes'
308
308
  },
309
309
  held: {
310
310
  question: 'Question waiting. Opens when you pause typing.',
@@ -349,10 +349,27 @@ export default {
349
349
  mentions: {
350
350
  here: 'Files in this folder',
351
351
  files: 'Files',
352
- chats: 'Chats in this project',
353
- noMatch: 'No files or chats match',
352
+ noMatch: 'No files match',
354
353
  empty: 'No files here'
355
354
  },
355
+ references: {
356
+ canvas: 'On this canvas',
357
+ views: 'Views',
358
+ noMatch: 'No view or node matches',
359
+ kinds: {
360
+ canvas: 'canvas',
361
+ chat: 'chat',
362
+ terminal: 'terminal',
363
+ note: 'note',
364
+ text: 'text',
365
+ drawing: 'drawing',
366
+ diagram: 'diagram',
367
+ browser: 'browser',
368
+ device: 'device',
369
+ file: 'file',
370
+ database: 'table'
371
+ }
372
+ },
356
373
  queue: {
357
374
  attachments_one: '{{count}} attachment',
358
375
  attachments_other: '{{count}} attachments',
@@ -16,10 +16,9 @@ declare const _default: {
16
16
  unreadable: string;
17
17
  unreadableThumbnail: string;
18
18
  };
19
- chatReference: {
19
+ references: {
20
20
  gone: string;
21
21
  untitled: string;
22
- remove: string;
23
22
  };
24
23
  fork: {
25
24
  refusal: {
@@ -305,6 +304,7 @@ declare const _default: {
305
304
  commands: string;
306
305
  skills: string;
307
306
  mentions: string;
307
+ references: string;
308
308
  };
309
309
  held: {
310
310
  question: string;
@@ -349,10 +349,27 @@ declare const _default: {
349
349
  mentions: {
350
350
  here: string;
351
351
  files: string;
352
- chats: string;
353
352
  noMatch: string;
354
353
  empty: string;
355
354
  };
355
+ references: {
356
+ canvas: string;
357
+ views: string;
358
+ noMatch: string;
359
+ kinds: {
360
+ canvas: string;
361
+ chat: string;
362
+ terminal: string;
363
+ note: string;
364
+ text: string;
365
+ drawing: string;
366
+ diagram: string;
367
+ browser: string;
368
+ device: string;
369
+ file: string;
370
+ database: string;
371
+ };
372
+ };
356
373
  queue: {
357
374
  attachments_one: string;
358
375
  attachments_other: string;
@@ -16,10 +16,9 @@ export default {
16
16
  unreadable: 'Kon {{name}} niet lezen',
17
17
  unreadableThumbnail: 'Kon de miniatuur niet lezen'
18
18
  },
19
- chatReference: {
20
- gone: 'Chat niet in dit project',
21
- untitled: 'Naamloze chat',
22
- remove: '{{title}} verwijderen'
19
+ references: {
20
+ gone: 'Niet in dit project',
21
+ untitled: 'Naamloos'
23
22
  },
24
23
  fork: {
25
24
  refusal: {
@@ -304,7 +303,8 @@ export default {
304
303
  menu: {
305
304
  commands: "Commando's",
306
305
  skills: 'Skills',
307
- mentions: 'Vermeldingen'
306
+ mentions: 'Vermeldingen',
307
+ references: 'Views en nodes'
308
308
  },
309
309
  held: {
310
310
  question: 'Er wacht een vraag. Opent zodra je stopt met typen.',
@@ -349,10 +349,27 @@ export default {
349
349
  mentions: {
350
350
  here: 'Bestanden in deze map',
351
351
  files: 'Bestanden',
352
- chats: 'Chats in dit project',
353
- noMatch: 'Geen bestanden of chats gevonden',
352
+ noMatch: 'Geen bestanden gevonden',
354
353
  empty: 'Hier staan geen bestanden'
355
354
  },
355
+ references: {
356
+ canvas: 'Op dit canvas',
357
+ views: 'Views',
358
+ noMatch: 'Geen view of node gevonden',
359
+ kinds: {
360
+ canvas: 'canvas',
361
+ chat: 'chat',
362
+ terminal: 'terminal',
363
+ note: 'notitie',
364
+ text: 'tekst',
365
+ drawing: 'tekening',
366
+ diagram: 'diagram',
367
+ browser: 'browser',
368
+ device: 'apparaat',
369
+ file: 'bestand',
370
+ database: 'tabel'
371
+ }
372
+ },
356
373
  queue: {
357
374
  attachments_one: '{{count}} bijlage',
358
375
  attachments_other: '{{count}} bijlagen',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents-react",
3
- "version": "0.37.1",
3
+ "version": "0.37.3",
4
4
  "description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -67,11 +67,11 @@
67
67
  "test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
68
68
  },
69
69
  "dependencies": {
70
- "@adecore/agent-contracts": "0.37.1",
71
- "@adecore/editor": "0.37.1",
72
- "@adecore/editor-react": "0.37.1",
73
- "@adecore/intelligent-ui": "0.37.1",
74
- "@adecore/merge": "0.37.1",
70
+ "@adecore/agent-contracts": "0.37.3",
71
+ "@adecore/editor": "0.37.3",
72
+ "@adecore/editor-react": "0.37.3",
73
+ "@adecore/intelligent-ui": "0.37.3",
74
+ "@adecore/merge": "0.37.3",
75
75
  "@codemirror/commands": "^6.11.1",
76
76
  "@codemirror/language": "^6.12.4",
77
77
  "@codemirror/state": "^6.7.6",
@@ -94,14 +94,14 @@
94
94
  "zustand": "^5.0.15"
95
95
  },
96
96
  "peerDependencies": {
97
- "@adecore/ui": "^0.37.1",
97
+ "@adecore/ui": "^0.37.3",
98
98
  "i18next": "^26.4.2",
99
99
  "react": "^19.3.0",
100
100
  "react-dom": "^19.3.0",
101
101
  "react-i18next": "^17.0.15"
102
102
  },
103
103
  "devDependencies": {
104
- "@adecore/ui": "0.37.1",
104
+ "@adecore/ui": "0.37.3",
105
105
  "@types/bun": "^1.4.2",
106
106
  "@types/react": "^19.3.0",
107
107
  "@types/react-dom": "^19.3.0",
@@ -39,6 +39,8 @@ export interface ChatSendExtras {
39
39
  /* The reply a quote at the head of the text was taken from. */
40
40
  quoteSource?: ChatQuoteSource;
41
41
  attachments?: ChatAttachmentUpload[];
42
+ /* Views and nodes picked with `#`; a machine that does not know the field drops it. */
43
+ references?: string[];
42
44
  }
43
45
 
44
46
  interface Mounted extends ChatOpenOptions, MountedEntry {
@@ -211,7 +213,8 @@ export class ChatClient {
211
213
  skills: extras.skills,
212
214
  chats: extras.chats,
213
215
  quoteSource: extras.quoteSource,
214
- attachments: extras.attachments
216
+ attachments: extras.attachments,
217
+ references: extras.references
215
218
  });
216
219
  }
217
220
 
@@ -1,11 +1,52 @@
1
+ import type { ReferenceToken } from './mentions';
2
+
1
3
  /* Another chat a message may point at, under the name it has right now. */
2
4
  export interface ProjectChat {
3
5
  id: string;
4
6
  title: string;
5
7
  }
6
8
 
7
- /* The chats an `@query` offers: never the chat typing it or one already attached. */
8
- export function chatSuggestions(chats: readonly ProjectChat[], query: string, exclude: readonly string[], limit: number): ProjectChat[] {
9
+ export type ReferableKind = 'canvas' | 'chat' | 'terminal' | 'note' | 'text' | 'drawing' | 'diagram' | 'browser' | 'device' | 'file' | 'database';
10
+
11
+ /* What a message may point at with `#`, under the name it has right now. */
12
+ export interface Referable {
13
+ id: string;
14
+ name: string;
15
+ kind: ReferableKind;
16
+ /* What tells it apart from another of the same name, such as the canvas it stands on; absent when its kind does. */
17
+ detail?: string;
18
+ /* `canvas` for what stands on the canvas the chat is a node of, `views` for a view of the project. */
19
+ group: 'canvas' | 'views';
20
+ }
21
+
22
+ export interface Referables {
23
+ /* The canvas group first, then the views; never the chat that asks. */
24
+ items: readonly Referable[];
25
+ /* The machine only resolves chats, so nothing else is offered. */
26
+ chatsOnly: boolean;
27
+ }
28
+
29
+ /* What a host without views offers: its chats, which every machine takes. */
30
+ export function referablesOfChats(chats: readonly ProjectChat[]): Referables {
31
+ return { items: chats.map((chat) => ({ id: chat.id, name: chat.title, kind: 'chat', group: 'views' })), chatsOnly: true };
32
+ }
33
+
34
+ /*
35
+ * How each id is spelled in a text: under the name it was picked with where `spelled` knows it, else
36
+ * under the name it has now. An id with neither has no token.
37
+ */
38
+ export function referenceTokens(ids: readonly string[], items: readonly Referable[], spelled: Readonly<Record<string, string>> = {}): ReferenceToken[] {
39
+ return [...new Set(ids)].flatMap((id) => {
40
+ const name = spelled[id] ?? items.find((item) => item.id === id)?.name;
41
+ return name === undefined || name === '' ? [] : [{ id, name }];
42
+ });
43
+ }
44
+
45
+ /* What a `#query` offers, the canvas first: never an id in `exclude`, and only chats where the machine takes no more. */
46
+ export function referenceSuggestions(referables: Referables, query: string, exclude: readonly string[], limit: number): Referable[] {
9
47
  const needle = query.toLowerCase();
10
- return chats.filter((chat) => !exclude.includes(chat.id) && chat.title.toLowerCase().includes(needle)).slice(0, limit);
48
+ const matches = referables.items.filter(
49
+ (item) => item.name !== '' && (!referables.chatsOnly || item.kind === 'chat') && !exclude.includes(item.id) && item.name.toLowerCase().includes(needle)
50
+ );
51
+ return [...matches.filter((item) => item.group === 'canvas'), ...matches.filter((item) => item.group === 'views')].slice(0, limit);
11
52
  }
@@ -9,16 +9,20 @@ export interface ChatDraft {
9
9
  text: string;
10
10
  mentions: string[];
11
11
  skills: string[];
12
- /* Chats of the project picked with `@`, drawn beside the text rather than in it. */
12
+ /* Chats of the project picked with `#`, by id; apart from `references`, since every machine takes a chat. */
13
13
  chats: string[];
14
14
  attachments: ChatAttachmentUpload[];
15
15
  /* A piece of an answer that goes above the text as a blockquote; empty for none. */
16
16
  quote: string;
17
17
  /* The reply the quote was taken from; null when the quote names none. */
18
18
  quoteSource: string | null;
19
+ /* Views and nodes of the project picked with `#`, by id. */
20
+ references: string[];
21
+ /* The name each pick was spelled with in the text, by id, so a chip outlives a rename; a pick without one takes the name it has now. */
22
+ referenceNames?: Record<string, string>;
19
23
  }
20
24
 
21
- export const EMPTY_DRAFT: ChatDraft = { text: '', mentions: [], skills: [], chats: [], attachments: [], quote: '', quoteSource: null };
25
+ export const EMPTY_DRAFT: ChatDraft = { text: '', mentions: [], skills: [], chats: [], attachments: [], quote: '', quoteSource: null, references: [] };
22
26
 
23
27
  // Older records only had text; the rest is filled in on read.
24
28
  type DraftRecord = {
@@ -29,6 +33,8 @@ type DraftRecord = {
29
33
  attachments?: ChatAttachmentUpload[];
30
34
  quote?: string;
31
35
  quoteSource?: string;
36
+ references?: string[];
37
+ referenceNames?: Record<string, string>;
32
38
  };
33
39
 
34
40
  const storage = persistedJson<Record<string, DraftRecord>>(
@@ -74,7 +80,9 @@ export function readDraft(chatId: string): ChatDraft {
74
80
  chats: record.chats ?? [],
75
81
  attachments: record.attachments ?? [],
76
82
  quote: record.quote ?? '',
77
- quoteSource: record.quoteSource ?? null
83
+ quoteSource: record.quoteSource ?? null,
84
+ references: record.references ?? [],
85
+ ...(record.referenceNames === undefined ? {} : { referenceNames: record.referenceNames })
78
86
  }
79
87
  : EMPTY_DRAFT;
80
88
  }
@@ -94,7 +102,9 @@ export function writeDraft(chatId: string, draft: ChatDraft): void {
94
102
  chats: draft.chats,
95
103
  attachments: draft.attachments,
96
104
  quote: draft.quote,
97
- ...(draft.quoteSource === null ? {} : { quoteSource: draft.quoteSource })
105
+ ...(draft.quoteSource === null ? {} : { quoteSource: draft.quoteSource }),
106
+ references: draft.references,
107
+ ...namesOf(draft)
98
108
  };
99
109
  if (storage.write(drafts)) {
100
110
  return;
@@ -106,11 +116,17 @@ export function writeDraft(chatId: string, draft: ChatDraft): void {
106
116
  skills: draft.skills,
107
117
  chats: draft.chats,
108
118
  quote: draft.quote,
109
- ...(draft.quoteSource === null ? {} : { quoteSource: draft.quoteSource })
119
+ ...(draft.quoteSource === null ? {} : { quoteSource: draft.quoteSource }),
120
+ references: draft.references,
121
+ ...namesOf(draft)
110
122
  };
111
123
  storage.write(drafts);
112
124
  }
113
125
 
126
+ function namesOf(draft: ChatDraft): Pick<DraftRecord, 'referenceNames'> {
127
+ return draft.referenceNames === undefined ? {} : { referenceNames: draft.referenceNames };
128
+ }
129
+
114
130
  /* Text handed to a draft from outside the composer goes under what was already typed, never over it. */
115
131
  export function joinDraftText(current: string, added: string): string {
116
132
  return current.trim() === '' ? added : `${current.replace(/\s+$/, '')}\n\n${added}`;
@@ -142,7 +158,9 @@ export function takeBackIntoDraft(
142
158
  chats: union(taken.chats, current.chats),
143
159
  attachments: [...checked.accepted.map((entry) => entry.upload), ...current.attachments],
144
160
  quote: current.quote,
145
- quoteSource: current.quoteSource
161
+ quoteSource: current.quoteSource,
162
+ references: union(taken.references, current.references),
163
+ ...namesOf(current)
146
164
  },
147
165
  rejected: checked.rejected
148
166
  };
package/src/chat/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  export { type AccountChoice, useAccountChoice, type NewAccountChoice, useNewAccountChoice } from './account-choice';
3
3
  export { transportActions, actionsOf, useChatActions } from './actions';
4
4
  export { type ChatSendExtras, ChatClient } from './chat-client';
5
- export type { ProjectChat } from './chat-references';
5
+ export type { ProjectChat, Referable, ReferableKind, Referables } from './chat-references';
6
6
  export { sendFromComposer, type ComposerSend } from './composer-send';
7
7
  export { stepEffortFromComposer } from './composer-effort';
8
8
  export type { EffortStep } from './logic/effort';
@@ -1,6 +1,17 @@
1
- import type { AgentKind, ModelInfo, ProviderInfo } from '@adecore/agent-contracts';
1
+ import { pickerGroupsOf, providerGroupOf, type AgentKind, type ModelInfo, type ProviderInfo } from '@adecore/agent-contracts';
2
2
  import { nameCollator } from '@adecore/ui/format';
3
3
 
4
+ /* The providers in the order a picker lists them: the frontier labs, the rest, then the API providers, each by name. */
5
+ export function inPickerOrder<T extends { kind: AgentKind; name: string }>(providers: readonly T[]): T[] {
6
+ const groups = pickerGroupsOf(providers);
7
+ return [...groups.frontier, ...groups.others, ...groups.api];
8
+ }
9
+
10
+ /* Whether a separator goes between two providers next to each other in that order: where one group ends. */
11
+ export function separatesGroups(previous: AgentKind | undefined, kind: AgentKind): boolean {
12
+ return previous !== undefined && providerGroupOf(previous) !== providerGroupOf(kind);
13
+ }
14
+
4
15
  /*
5
16
  * A provider with more live models than this keeps the list short: the model that is picked and the default,
6
17
  * with the rest behind one row that opens all of them. A list of three hundred models is not one to scroll.