@adecore/agents-react 0.37.3 → 0.39.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 (66) hide show
  1. package/dist/agents/AgentModelLabel.d.ts +10 -0
  2. package/dist/agents/AgentModelLabel.js +97 -0
  3. package/dist/chat/logic/visual-height.d.ts +1 -0
  4. package/dist/chat/logic/visual-height.js +1 -1
  5. package/dist/chat/ui/Composer.js +385 -360
  6. package/dist/chat/ui/HostMenuItems.d.ts +4 -0
  7. package/dist/chat/ui/HostMenuItems.js +15 -0
  8. package/dist/chat/ui/Pickers.d.ts +2 -1
  9. package/dist/chat/ui/Pickers.js +126 -123
  10. package/dist/chat/ui/PromptEditor.d.ts +19 -0
  11. package/dist/chat/ui/PromptEditor.js +115 -0
  12. package/dist/chat/ui/Timeline.d.ts +2 -1
  13. package/dist/chat/ui/Timeline.js +13 -1
  14. package/dist/chat/ui/VisualFrame.d.ts +8 -0
  15. package/dist/chat/ui/VisualFrame.js +217 -115
  16. package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
  17. package/dist/chat/ui/composer/ComposerControls.js +235 -38
  18. package/dist/chat/ui/composer/key-handler.d.ts +1 -1
  19. package/dist/chat/ui/composer/key-handler.js +3 -11
  20. package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
  21. package/dist/chat/ui/composer/prompt-picks.js +14 -0
  22. package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
  23. package/dist/chat/ui/composer/use-composer-menus.js +65 -519
  24. package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
  25. package/dist/chat/ui/composer/use-prompt-menus.js +536 -0
  26. package/dist/chat/ui/index.d.ts +4 -0
  27. package/dist/chat/ui/index.js +3 -0
  28. package/dist/chat/ui/rows/GeneratedImageRow.js +8 -4
  29. package/dist/chat/ui/rows/VisualRow.js +184 -142
  30. package/dist/chat/ui/rows/row-rhythm.js +6 -4
  31. package/dist/host-hooks.d.ts +2 -1
  32. package/dist/host-hooks.js +6 -0
  33. package/dist/host.d.ts +27 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/locales/en/agent-chat.d.ts +1 -0
  36. package/dist/locales/en/agent-chat.js +1 -0
  37. package/dist/locales/nl/agent-chat.d.ts +1 -0
  38. package/dist/locales/nl/agent-chat.js +1 -0
  39. package/dist/providers/index.d.ts +1 -0
  40. package/dist/providers/index.js +1 -0
  41. package/dist/theme.css +3 -0
  42. package/package.json +8 -8
  43. package/src/agents/AgentModelLabel.tsx +40 -0
  44. package/src/chat/logic/visual-height.ts +1 -1
  45. package/src/chat/ui/Composer.tsx +12 -3
  46. package/src/chat/ui/HostMenuItems.tsx +11 -0
  47. package/src/chat/ui/Pickers.tsx +4 -1
  48. package/src/chat/ui/PromptEditor.tsx +130 -0
  49. package/src/chat/ui/Timeline.tsx +22 -1
  50. package/src/chat/ui/VisualFrame.tsx +68 -21
  51. package/src/chat/ui/composer/ComposerControls.tsx +65 -15
  52. package/src/chat/ui/composer/key-handler.ts +3 -9
  53. package/src/chat/ui/composer/prompt-picks.ts +17 -0
  54. package/src/chat/ui/composer/use-composer-menus.ts +18 -275
  55. package/src/chat/ui/composer/use-prompt-menus.ts +341 -0
  56. package/src/chat/ui/index.ts +4 -0
  57. package/src/chat/ui/rows/GeneratedImageRow.tsx +9 -4
  58. package/src/chat/ui/rows/VisualRow.tsx +5 -1
  59. package/src/chat/ui/rows/row-rhythm.ts +6 -4
  60. package/src/host-hooks.ts +9 -1
  61. package/src/host.ts +45 -1
  62. package/src/index.ts +3 -0
  63. package/src/locales/en/agent-chat.ts +1 -0
  64. package/src/locales/nl/agent-chat.ts +1 -0
  65. package/src/providers/index.ts +1 -0
  66. package/src/theme.css +3 -0
@@ -13,6 +13,7 @@ import { attachmentAspect, generatedImageView, type GeneratedImageFailure } from
13
13
  import { toolStartedAt } from '../../logic/tools';
14
14
  import { CHIP_IN_MESSAGE, MENTION_TONE } from '../chips';
15
15
  import { useFileLinkCwd, useOpenFileLink } from '../file-links';
16
+ import { HostMenuItems } from '../HostMenuItems';
16
17
  import { ImageThumb } from '../ImageView';
17
18
  import { ROW_GUTTER } from '../icons';
18
19
  import { RunningFor, WorkRow } from './WorkRows';
@@ -104,6 +105,7 @@ function ReadyImage({ chatId, attachment, prompt, transparent }: { chatId: strin
104
105
  const missing = source.failure !== null;
105
106
  const aspect = attachmentAspect(attachment);
106
107
  const { open, saveToProject } = attachments;
108
+ const extra = attachments.actions?.(scopeId, chatId, attachment) ?? [];
107
109
 
108
110
  const detail = [
109
111
  attachment.width !== undefined && attachment.height !== undefined
@@ -171,13 +173,16 @@ function ReadyImage({ chatId, attachment, prompt, transparent }: { chatId: strin
171
173
  >
172
174
  <ButtonGroup>
173
175
  <IconButton icon={Maximize2} size="sm" label={t('generatedImage.openLarge')} onClick={() => setLightbox(true)} />
174
- {prompt !== null && (
176
+ {(prompt !== null || extra.length > 0) && (
175
177
  <Menu.Root>
176
178
  <IconButton icon={Ellipsis} size="sm" label={t('generatedImage.more')} render={<Menu.Trigger />} />
177
179
  <Menu.Popup align="end">
178
- <Menu.Item onClick={() => copyText(prompt)}>
179
- <Icon icon={Copy} size={14} /> {t('generatedImage.copyPrompt')}
180
- </Menu.Item>
180
+ {prompt !== null && (
181
+ <Menu.Item onClick={() => copyText(prompt)}>
182
+ <Icon icon={Copy} size={14} /> {t('generatedImage.copyPrompt')}
183
+ </Menu.Item>
184
+ )}
185
+ <HostMenuItems actions={extra} />
181
186
  </Menu.Popup>
182
187
  </Menu.Root>
183
188
  )}
@@ -5,6 +5,7 @@ import { ButtonGroup, ErrorBoundary, Icon, IconButton, Menu, Surface } from '@ad
5
5
  import { useChatHost } from '../../../host';
6
6
  import { useChatScope } from '../../../scope';
7
7
  import { useVisualDialog } from '../../visuals';
8
+ import { HostMenuItems } from '../HostMenuItems';
8
9
  import { VisualFrame } from '../VisualFrame';
9
10
 
10
11
  /*
@@ -16,10 +17,11 @@ import { VisualFrame } from '../VisualFrame';
16
17
  export function VisualRow({ chatId, visual }: { chatId: string; visual: ChatVisual }) {
17
18
  const host = useChatHost();
18
19
  const { t } = useTranslation('agent-chat');
19
- const { keyOf } = useChatScope();
20
+ const { id: scopeId, keyOf } = useChatScope();
20
21
  if (host.visuals === null) {
21
22
  return null;
22
23
  }
24
+ const extra = host.visuals.actions?.(scopeId, chatId, visual) ?? [];
23
25
  const open = (kind: 'expand' | 'remove'): void => useVisualDialog.getState().open(keyOf(chatId), visual.id, kind);
24
26
  return (
25
27
  <ErrorBoundary label={t('visuals.renderFailed')} resetKeys={[visual]} compact className="relative rounded-lg">
@@ -35,6 +37,8 @@ export function VisualRow({ chatId, visual }: { chatId: string; visual: ChatVisu
35
37
  <Menu.Root>
36
38
  <IconButton icon={Ellipsis} size="sm" label={t('visuals.more')} render={<Menu.Trigger />} />
37
39
  <Menu.Popup align="end">
40
+ <HostMenuItems actions={extra} />
41
+ {extra.length > 0 && <Menu.Separator />}
38
42
  <Menu.Item onClick={() => open('remove')}>
39
43
  <Icon icon={Trash2} size={14} /> {t('visuals.remove')}
40
44
  </Menu.Item>
@@ -7,13 +7,15 @@ export const FOLLOW_THRESHOLD_PX = 40;
7
7
 
8
8
  /*
9
9
  * The gaps a row wears for where it sits: a question is followed by its answer gap, and a seam where
10
- * the list rhythm of tool lines meets the block rhythm of prose gets the block gap. A visual has no
11
- * margin of its own like prose does, so the row under one takes the block gap too. A question
12
- * already carries the gap before it, and the row after one is the answer, so neither takes a seam.
10
+ * the list rhythm of tool lines meets the block rhythm of prose gets the block gap. A visual and an
11
+ * app's card have no margin of their own like prose does, so the row under one takes the block gap
12
+ * too. A question already carries the gap before it, and the row after one is the answer, so neither
13
+ * takes a seam.
13
14
  */
14
15
  export function rowRhythm(row: TimelineRow, previous: TimelineRow | null): string {
15
16
  const question = row.kind === 'user';
16
- const seam = !question && previous !== null && previous.kind !== 'user' && (isBlock(row) !== isBlock(previous) || previous.kind === 'visual');
17
+ const bare = previous?.kind === 'visual' || previous?.kind === 'app-card';
18
+ const seam = !question && previous !== null && previous.kind !== 'user' && (isBlock(row) !== isBlock(previous) || bare);
17
19
  return clsx(question && 'pb-(--chat-answer-gap)', seam && 'pt-(--chat-block-gap)');
18
20
  }
19
21
 
package/src/host-hooks.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { ProviderPlatform } from '@adecore/agent-contracts';
9
9
  import { referablesOfChats, type Referables } from './chat/chat-references';
10
- import { useChatHost, type ChatHost, type CommandLook } from './host';
10
+ import { useChatHost, type ChatHost, type CommandLook, type ComposerSendAction } from './host';
11
11
 
12
12
  export function useHostCodeMode(): 'light' | 'dark' {
13
13
  return useChatHost().code.useMode();
@@ -101,6 +101,14 @@ export function useHostComposerPlaceholder(...args: Parameters<ChatHost['useComp
101
101
  return useChatHost().useComposerPlaceholder(...args);
102
102
  }
103
103
 
104
+ const NO_SEND_ACTIONS: readonly ComposerSendAction[] = [];
105
+ const noSendActions = (): readonly ComposerSendAction[] => NO_SEND_ACTIONS;
106
+
107
+ export function useHostComposerSendActions(scopeId: string, chatId: string): readonly ComposerSendAction[] {
108
+ const useSendActions = useChatHost().useComposerSendActions ?? noSendActions;
109
+ return useSendActions(scopeId, chatId);
110
+ }
111
+
104
112
  export function useHostThreadCards(...args: Parameters<ChatHost['useThreadCards']>): ReturnType<ChatHost['useThreadCards']> {
105
113
  return useChatHost().useThreadCards(...args);
106
114
  }
package/src/host.ts CHANGED
@@ -2,9 +2,11 @@ import { createContext, createElement, useContext, useState, type ComponentProps
2
2
  import type { Extension } from '@codemirror/state';
3
3
  import type { EditorView } from '@codemirror/view';
4
4
  import type { Highlighter, ThemeRegistration } from 'shiki';
5
+ import type { LucideIcon } from 'lucide-react';
5
6
  import type { EditorEngine } from '@adecore/editor';
6
7
  import type {
7
8
  AgentKind,
9
+ ChatAttachment,
8
10
  ChatCheckpointDiff,
9
11
  ChatConfigurePayload,
10
12
  ChatGoalAction,
@@ -15,7 +17,10 @@ import type {
15
17
  ChatUiQueryReading,
16
18
  ChatUiLinkPayload,
17
19
  ChatUiLinkReading,
18
- ProviderPlatform
20
+ ChatVisual,
21
+ ModelSelection,
22
+ ProviderPlatform,
23
+ RuntimeMode
19
24
  } from '@adecore/agent-contracts';
20
25
  import { isApplePlatform } from '@adecore/ui';
21
26
  import type { Referables } from './chat/chat-references';
@@ -165,6 +170,28 @@ export interface ComposerSlotProps {
165
170
  insert(text: string): void;
166
171
  }
167
172
 
173
+ /* What a send action of the app's gets: the message as it stands when a person picks the action. */
174
+ export interface ComposerSendDraft {
175
+ scopeId: string;
176
+ chatId: string;
177
+ /* The draft as Send would send it, trimmed and with its quote; empty when nothing is written. */
178
+ text: string;
179
+ provider: AgentKind;
180
+ selection: ModelSelection;
181
+ runtimeMode: RuntimeMode;
182
+ }
183
+
184
+ /* An action of the app's own in the menu beside the composer's send button, such as sending the draft later. */
185
+ export interface ComposerSendAction {
186
+ /* Unique among the actions of one composer. */
187
+ id: string;
188
+ label: string;
189
+ icon?: LucideIcon;
190
+ disabled?: boolean;
191
+ /* Runs once a person picks it; the draft stays in the box, and the host shows a failure itself. */
192
+ run(draft: ComposerSendDraft): void;
193
+ }
194
+
168
195
  /* What an app's own chips for a chat's sub-agents get, in place of the one flyout over the composer. */
169
196
  export interface SubagentSlotProps {
170
197
  chatId: string;
@@ -188,6 +215,19 @@ export interface VisualHost {
188
215
  frameUrl: string;
189
216
  /* Opens an http or https link a person followed in a visual, outside the app. */
190
217
  openLink(url: string): void;
218
+ /* Actions of the app's own in a visual's menu, above Remove; left out, the menu holds Remove alone. */
219
+ actions?(scopeId: string, chatId: string, visual: ChatVisual): readonly HostMenuAction[];
220
+ }
221
+
222
+ /* An action of the app's own in the menu of something a chat made, such as keeping a copy somewhere of the app's. */
223
+ export interface HostMenuAction {
224
+ /* Unique among the actions of one menu. */
225
+ id: string;
226
+ label: string;
227
+ icon?: LucideIcon;
228
+ disabled?: boolean;
229
+ /* Runs once a person picks it; the host shows a failure itself. */
230
+ run(): void;
191
231
  }
192
232
 
193
233
  /* Who writes a chat's replies, as the header over each one names them. */
@@ -329,6 +369,8 @@ export interface ChatHost {
329
369
  * Resolves with the path as a file link opens it, or null when the person cancels; left out, a generated image offers no Save.
330
370
  */
331
371
  saveToProject?(scopeId: string, chatId: string, attachmentId: string, suggestedName: string): Promise<string | null>;
372
+ /* Actions of the app's own in a generated image's menu, after the chat's own; left out, the menu holds those alone. */
373
+ actions?(scopeId: string, chatId: string, attachment: ChatAttachment): readonly HostMenuAction[];
332
374
  };
333
375
  /* The image a tool read, drawn under its row; null draws none. */
334
376
  ReadImage: ComponentType<{ path: string }> | null;
@@ -435,6 +477,8 @@ export interface ChatHost {
435
477
  useComposerPlaceholder(scopeId: string, chatId: string): string | null;
436
478
  /* The app's own control in the composer's row, after the pickers; null draws none. */
437
479
  ComposerSlot: ComponentType<ComposerSlotProps> | null;
480
+ /* Actions of the app's own behind a chevron on the send button, in the order given; left out, or none, Send stands alone. */
481
+ useComposerSendActions?(scopeId: string, chatId: string): readonly ComposerSendAction[];
438
482
  /* The app's own chips for a chat's sub-agents over the composer; null draws the one flyout. */
439
483
  SubagentSlot: ComponentType<SubagentSlotProps> | null;
440
484
  /* Cards of the app's own in a thread, in any order. Hand back the same array while none changed, or the thread lays out its rows again on every render. */
package/src/index.ts CHANGED
@@ -9,9 +9,12 @@ export {
9
9
  type TimelineFind,
10
10
  type ComposerDictationProps,
11
11
  type ComposerSlotProps,
12
+ type ComposerSendAction,
13
+ type ComposerSendDraft,
12
14
  type SubagentSlotProps,
13
15
  type ThreadCard,
14
16
  type VisualHost,
17
+ type HostMenuAction,
15
18
  type ReplyAuthor,
16
19
  type ChatHost,
17
20
  type UiMapSource,
@@ -391,6 +391,7 @@ export default {
391
391
  continue: 'Continue',
392
392
  continueHint: 'Continue the stopped turn',
393
393
  send: 'Send',
394
+ sendActions: 'More send options',
394
395
  clear: {
395
396
  title: 'Clear thread?',
396
397
  description: 'A turn is running.',
@@ -391,6 +391,7 @@ export default {
391
391
  continue: 'Doorgaan',
392
392
  continueHint: 'Ga verder met de gestopte beurt',
393
393
  send: 'Versturen',
394
+ sendActions: 'Meer verzendopties',
394
395
  clear: {
395
396
  title: 'Thread wissen?',
396
397
  description: 'Er loopt een turn.',
@@ -1,6 +1,7 @@
1
1
  // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
2
  export { AccountDot } from '../agents/AccountDot';
3
3
  export { AgentIcon } from '../agents/AgentIcon';
4
+ export { AgentModelLabel } from '../agents/AgentModelLabel';
4
5
  export { ModelOptionControl } from '../agents/ModelOptionControl';
5
6
  export { ProviderLogo } from '../agents/ProviderLogo';
6
7
  export { type AccountEntry, accountsOfKind, ACCOUNT_TONE_CLASSES, accountStatusLine, canContinueOn, type AccountTone } from '../agents/accounts';
package/src/theme.css CHANGED
@@ -288,6 +288,9 @@
288
288
  .composer-input .cm-editor .cm-scroller .cm-content { padding: 18px 14px 6px 20px; caret-color: var(--text); }
289
289
  .composer-input .cm-scroller .cm-content .cm-line { padding: 0; }
290
290
  .composer-input .cm-scroller .cm-content .cm-placeholder { color: var(--text-faint); }
291
+ /* The same editor as a form field (`PromptEditor`): the field's inset, and room for a few lines before it scrolls. */
292
+ .composer-input.prompt-editor .cm-editor { max-height: 240px; }
293
+ .composer-input.prompt-editor .cm-editor .cm-scroller .cm-content { min-height: 80px; padding: 6px 10px; }
291
294
  .composer-hints, .composer-hints-extra { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
292
295
  /* The joiner starts with its own comma, so it sits against the lead without the gap. */
293
296
  .composer-hints-extra { margin-left: -8px; }