@adecore/agents-react 0.24.1 → 0.25.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 (113) hide show
  1. package/dist/chat/index.d.ts +4 -7
  2. package/dist/chat/index.js +4 -7
  3. package/dist/chat/logic/continue-after-stop.d.ts +3 -0
  4. package/dist/chat/logic/continue-after-stop.js +28 -0
  5. package/dist/chat/logic/thread-cards.d.ts +0 -1
  6. package/dist/chat/logic/thread-cards.js +0 -4
  7. package/dist/chat/preferences.d.ts +0 -1
  8. package/dist/chat/preferences.js +0 -3
  9. package/dist/chat/stash.d.ts +6 -0
  10. package/dist/chat/stash.js +13 -0
  11. package/dist/chat/ui/Composer.js +26 -5
  12. package/dist/chat/ui/EditDiff.js +4 -3
  13. package/dist/chat/ui/Pickers.js +1 -1
  14. package/dist/chat/ui/UnifiedDiff.js +4 -3
  15. package/dist/chat/ui/composer/ComposerControls.d.ts +1 -0
  16. package/dist/chat/ui/composer/ComposerControls.js +3 -3
  17. package/dist/chat/ui/composer/use-composer-submit.d.ts +3 -1
  18. package/dist/chat/ui/composer/use-composer-submit.js +22 -4
  19. package/dist/chat/ui/composer/use-continuable-turn.d.ts +2 -0
  20. package/dist/chat/ui/composer/use-continuable-turn.js +12 -0
  21. package/dist/chat/ui/composer/use-notice.d.ts +3 -1
  22. package/dist/chat/ui/composer/use-notice.js +8 -6
  23. package/dist/chat/ui/diff/ReviewDiff.js +2 -2
  24. package/dist/chat/ui/index.d.ts +9 -28
  25. package/dist/chat/ui/index.js +8 -27
  26. package/dist/chat/ui/intelligent-ui/UiReply.js +20 -2
  27. package/dist/chat/ui/intelligent-ui/node-text.d.ts +3 -1
  28. package/dist/chat/ui/intelligent-ui/node-text.js +32 -0
  29. package/dist/chat/ui/intelligent-ui/registry.js +15 -5
  30. package/dist/chat/ui/intelligent-ui/render-context.d.ts +6 -0
  31. package/dist/chat/ui/intelligent-ui/renderers/base-land.d.ts +1 -0
  32. package/dist/chat/ui/intelligent-ui/renderers/base-land.js +6 -0
  33. package/dist/chat/ui/intelligent-ui/renderers/binding.d.ts +6 -0
  34. package/dist/chat/ui/intelligent-ui/renderers/binding.js +10 -0
  35. package/dist/chat/ui/intelligent-ui/renderers/content.d.ts +2 -0
  36. package/dist/chat/ui/intelligent-ui/renderers/content.js +68 -6
  37. package/dist/chat/ui/intelligent-ui/renderers/inputs.d.ts +2 -0
  38. package/dist/chat/ui/intelligent-ui/renderers/inputs.js +40 -13
  39. package/dist/chat/ui/intelligent-ui/renderers/map-slots.d.ts +20 -0
  40. package/dist/chat/ui/intelligent-ui/renderers/map-slots.js +87 -0
  41. package/dist/chat/ui/intelligent-ui/renderers/map-style.d.ts +18 -0
  42. package/dist/chat/ui/intelligent-ui/renderers/map-style.js +72 -0
  43. package/dist/chat/ui/intelligent-ui/renderers/map-view.d.ts +15 -0
  44. package/dist/chat/ui/intelligent-ui/renderers/map-view.js +213 -0
  45. package/dist/chat/ui/intelligent-ui/renderers/map.d.ts +3 -0
  46. package/dist/chat/ui/intelligent-ui/renderers/map.js +113 -0
  47. package/dist/chat/ui/intelligent-ui/renderers/place-point.d.ts +3 -0
  48. package/dist/chat/ui/intelligent-ui/renderers/place-point.js +13 -0
  49. package/dist/chat/ui/intelligent-ui/renderers/text.d.ts +2 -0
  50. package/dist/chat/ui/intelligent-ui/renderers/text.js +14 -1
  51. package/dist/chat/ui/intelligent-ui/use-in-view.d.ts +1 -0
  52. package/dist/chat/ui/intelligent-ui/use-in-view.js +17 -0
  53. package/dist/chat/ui/rows/WorkRows.js +3 -3
  54. package/dist/host.d.ts +16 -0
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.js +1 -1
  57. package/dist/lazy.js +5 -7
  58. package/dist/locales/en/agent-chat.d.ts +47 -0
  59. package/dist/locales/en/agent-chat.js +48 -1
  60. package/dist/locales/nl/agent-chat.d.ts +47 -0
  61. package/dist/locales/nl/agent-chat.js +48 -1
  62. package/dist/prompts/index.d.ts +3 -5
  63. package/dist/prompts/index.js +3 -5
  64. package/dist/providers/index.d.ts +3 -9
  65. package/dist/providers/index.js +3 -9
  66. package/dist/state/index.d.ts +2 -2
  67. package/dist/state/index.js +2 -2
  68. package/dist/state/usage.d.ts +0 -1
  69. package/dist/state/usage.js +0 -3
  70. package/dist/theme.css +10 -0
  71. package/package.json +9 -8
  72. package/src/chat/index.ts +2 -23
  73. package/src/chat/logic/continue-after-stop.ts +32 -0
  74. package/src/chat/logic/thread-cards.ts +0 -5
  75. package/src/chat/preferences.ts +0 -4
  76. package/src/chat/stash.ts +17 -0
  77. package/src/chat/ui/Composer.tsx +33 -5
  78. package/src/chat/ui/EditDiff.tsx +6 -5
  79. package/src/chat/ui/Pickers.tsx +1 -0
  80. package/src/chat/ui/UnifiedDiff.tsx +6 -5
  81. package/src/chat/ui/composer/ComposerControls.tsx +13 -3
  82. package/src/chat/ui/composer/use-composer-submit.ts +25 -5
  83. package/src/chat/ui/composer/use-continuable-turn.ts +14 -0
  84. package/src/chat/ui/composer/use-notice.ts +14 -7
  85. package/src/chat/ui/diff/ReviewDiff.tsx +3 -3
  86. package/src/chat/ui/index.ts +9 -49
  87. package/src/chat/ui/intelligent-ui/UiReply.tsx +24 -1
  88. package/src/chat/ui/intelligent-ui/node-text.ts +34 -1
  89. package/src/chat/ui/intelligent-ui/registry.ts +33 -5
  90. package/src/chat/ui/intelligent-ui/render-context.ts +14 -0
  91. package/src/chat/ui/intelligent-ui/renderers/base-land.ts +7 -0
  92. package/src/chat/ui/intelligent-ui/renderers/binding.ts +14 -0
  93. package/src/chat/ui/intelligent-ui/renderers/content.tsx +144 -9
  94. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +79 -17
  95. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +105 -0
  96. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +96 -0
  97. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +251 -0
  98. package/src/chat/ui/intelligent-ui/renderers/map.tsx +226 -0
  99. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +16 -0
  100. package/src/chat/ui/intelligent-ui/renderers/text.tsx +28 -1
  101. package/src/chat/ui/intelligent-ui/use-in-view.ts +18 -0
  102. package/src/chat/ui/rows/WorkRows.tsx +3 -3
  103. package/src/host.ts +28 -0
  104. package/src/index.ts +2 -12
  105. package/src/lazy.tsx +5 -7
  106. package/src/locales/en/agent-chat.ts +48 -1
  107. package/src/locales/nl/agent-chat.ts +48 -1
  108. package/src/prompts/index.ts +3 -18
  109. package/src/providers/index.ts +3 -9
  110. package/src/state/index.ts +1 -16
  111. package/src/state/usage.ts +0 -4
  112. package/src/stylesheets.d.ts +2 -0
  113. package/src/theme.css +10 -0
@@ -1,17 +1,20 @@
1
1
  import { useEffect, useRef, type RefObject } from 'react';
2
+ import { useTranslation } from 'react-i18next';
2
3
  import { useChatHost } from '../../../host';
3
4
  import { useChatScope } from '../../../scope';
4
5
  import { checkAttachmentLimits, uploadBytes } from '../../attachments';
5
6
  import type { ChatSendExtras } from '../../chat-client';
6
7
  import { registerComposerSender, type ComposerSend } from '../../composer-send';
7
8
  import { isEmptyDraft } from '../../drafts';
9
+ import { CONTINUE_PROMPT } from '../../logic/continue-after-stop';
8
10
  import type { PromptGuard } from '../../guards';
9
11
  import { presentMentions, presentSkills } from '../../mentions';
10
12
  import { withQuote } from '../../quote';
11
- import { stashDraft, useStash, type StashedPrompt } from '../../stash';
13
+ import { restorableChats, stashDraft, useStash, type StashedPrompt } from '../../stash';
12
14
  import { scrollTimelineToEnd } from '../../timeline-scroll';
13
15
  import type { ComposerInputHandle } from '../ComposerInput';
14
16
  import type { ChatControls } from './use-chat-controls';
17
+ import type { NoticeTone } from './use-notice';
15
18
  import type { ComposerDraft } from './use-composer-draft';
16
19
  import type { ComposerMenus } from './use-composer-menus';
17
20
  import type { PromptHistory } from './use-prompt-history';
@@ -31,7 +34,7 @@ interface SubmitInput {
31
34
  history: PromptHistory;
32
35
  hold: ComposerPromptHold;
33
36
  inputRef: RefObject<ComposerInputHandle | null>;
34
- setNotice(notice: string): void;
37
+ setNotice(notice: string, tone?: NoticeTone): void;
35
38
  onSend(text: string, extras: ChatSendExtras): void | Promise<boolean>;
36
39
  }
37
40
 
@@ -51,9 +54,11 @@ export function useComposerSubmit({
51
54
  setNotice,
52
55
  onSend
53
56
  }: SubmitInput) {
57
+ const { t } = useTranslation('agent-chat');
54
58
  const host = useChatHost();
55
59
  const scope = useChatScope();
56
60
  const { draft, setDraft } = box;
61
+ const referable = host.useReferableChats();
57
62
 
58
63
  /* Opens the host's new chat once the host took the message; a host that answers nothing took it. */
59
64
  const afterSend = (sent: void | Promise<boolean>, andNew: boolean): void => {
@@ -127,23 +132,38 @@ export function useComposerSubmit({
127
132
  useEffect(() => registerComposerSender(scope.keyOf(chatId), (how) => submitRef.current(how)), [scope, chatId]);
128
133
 
129
134
  /*
130
- * A stashed prompt comes back as text, mentions and skills; its files were never kept. Its quote
131
- * keeps leading to its reply in the chat it came from, and is plain text in any other.
135
+ * A stashed prompt comes back as text, mentions, skills and the chats this project still has; its
136
+ * files were never kept. Its quote keeps leading to its reply in the chat it came from, and is plain
137
+ * text in any other.
132
138
  */
133
139
  const restoreStashed = (prompt: StashedPrompt): void => {
134
140
  const stashedQuote = prompt.quote;
135
141
  const here = stashedQuote !== undefined && stashedQuote.chatId === chatId;
142
+ const { chats, dropped } = restorableChats(prompt.chats, new Set(referable.map((chat) => chat.id)), chatId);
136
143
  setDraft((current) => ({
137
144
  ...current,
138
145
  text: stashedQuote === undefined || here ? prompt.text : withQuote(stashedQuote.text, prompt.text),
139
146
  mentions: [...prompt.mentions],
140
147
  skills: [...prompt.skills],
148
+ chats,
141
149
  ...(here ? { quote: stashedQuote.text, quoteSource: stashedQuote.itemId } : {})
142
150
  }));
151
+ if (dropped > 0) {
152
+ setNotice(t('composer.notice.stashChatsDropped', { count: dropped }), 'info');
153
+ }
143
154
  history.forget();
144
155
  inputRef.current?.focus();
145
156
  };
146
157
 
158
+ /* Sends the prompt that goes on after a stop through the same door as a typed one, so the host sees a person's message. */
159
+ const continueTurn = (): void => {
160
+ if (!writable) {
161
+ return;
162
+ }
163
+ scrollTimelineToEnd(chatId, 'instant');
164
+ afterSend(onSend(CONTINUE_PROMPT, {}), false);
165
+ };
166
+
147
167
  /* Cmd+S puts the draft away and clears the box; on an empty box the same key takes the last one back. */
148
168
  const toggleStash = (): void => {
149
169
  if (isEmptyDraft(draft)) {
@@ -159,5 +179,5 @@ export function useComposerSubmit({
159
179
  }
160
180
  };
161
181
 
162
- return { submit, restoreStashed, toggleStash };
182
+ return { submit, continueTurn, restoreStashed, toggleStash };
163
183
  }
@@ -0,0 +1,14 @@
1
+ import { useMemo } from 'react';
2
+ import type { ChatInfo, ChatItem } from '@adecore/agent-contracts';
3
+ import { useChatRow } from '../../../state/chats';
4
+ import { continuableTurn } from '../../logic/continue-after-stop';
5
+
6
+ /* The turn a person stopped that Continue would pick up, or null when there is none. */
7
+ export function useContinuableTurn(chatId: string, info: ChatInfo): string | null {
8
+ const order = useChatRow(chatId, (row) => row?.order);
9
+ const structure = useChatRow(chatId, (row) => row?.structure);
10
+ return useMemo(() => {
11
+ const items = (order ?? []).map((id) => structure?.[id]).filter((item): item is ChatItem => item !== undefined);
12
+ return continuableTurn(info, items);
13
+ }, [info, order, structure]);
14
+ }
@@ -1,27 +1,34 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
 
3
3
  const NOTICE_MS = 4000;
4
4
 
5
+ /* An error is what went wrong; info is something the composer did that the person should know. */
6
+ export type NoticeTone = 'error' | 'info';
7
+
5
8
  export interface ComposerNotice {
6
9
  notice: string | null;
7
- setNotice(notice: string | null): void;
10
+ tone: NoticeTone;
11
+ setNotice(notice: string | null, tone?: NoticeTone): void;
8
12
  showError(error: unknown): void;
9
13
  }
10
14
 
11
15
  /* A line under the box that says what went wrong, gone again after a few seconds. */
12
16
  export function useNotice(): ComposerNotice {
13
- const [notice, setNotice] = useState<string | null>(null);
17
+ const [shown, setShown] = useState<{ text: string; tone: NoticeTone } | null>(null);
14
18
 
15
19
  useEffect(() => {
16
- if (!notice) {
20
+ if (!shown) {
17
21
  return;
18
22
  }
19
- const timer = setTimeout(() => setNotice(null), NOTICE_MS);
23
+ const timer = setTimeout(() => setShown(null), NOTICE_MS);
20
24
  return () => clearTimeout(timer);
21
- }, [notice]);
25
+ }, [shown]);
26
+
27
+ const setNotice = useCallback((notice: string | null, tone: NoticeTone = 'error'): void => setShown(notice ? { text: notice, tone } : null), []);
22
28
 
23
29
  return {
24
- notice,
30
+ notice: shown?.text ?? null,
31
+ tone: shown?.tone ?? 'error',
25
32
  setNotice,
26
33
  showError: (error) => setNotice(error instanceof Error ? error.message : String(error))
27
34
  };
@@ -1,4 +1,4 @@
1
- import { Suspense } from 'react';
1
+ import { LazyBoundary } from '@adecore/ui';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { lazyNamed } from '../../../lazy';
4
4
  import type { ReviewEntry } from './review-plan';
@@ -10,13 +10,13 @@ export function ReviewDiff({ entry, maxHeight = 480 }: { entry: ReviewEntry; max
10
10
  const { t } = useTranslation('agent-chat');
11
11
  return (
12
12
  <div data-review-entry={entry.key}>
13
- <Suspense fallback={<p className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</p>}>
13
+ <LazyBoundary fallback={<p className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</p>}>
14
14
  {entry.change.kind === 'edit' ? (
15
15
  <EditDiff change={entry.change.edit} maxHeight={maxHeight} />
16
16
  ) : (
17
17
  <UnifiedDiff change={entry.change.patch} contents={entry.change.contents} maxHeight={maxHeight} />
18
18
  )}
19
- </Suspense>
19
+ </LazyBoundary>
20
20
  </div>
21
21
  );
22
22
  }
@@ -1,7 +1,6 @@
1
1
  // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
2
  export { AccountPill } from './AccountPill';
3
3
  export { AnsiOutput } from './AnsiOutput';
4
- export { BookmarkMarker } from './BookmarkMarker';
5
4
  export { BookmarkSubmenu } from './BookmarkSubmenu';
6
5
  export { ChatActivity, StatusIcon } from './ChatActivity';
7
6
  export { ChatReferenceChip } from './ChatReferenceChip';
@@ -15,40 +14,17 @@ export { GoalDock, GoalPill } from './GoalDock';
15
14
  export { ImageThumb, type ImageThumbProps } from './ImageView';
16
15
  export { LimitDock, LimitPill } from './LimitState';
17
16
  export { ReplyMarkdown, MessageMarkdown, Markdown } from './Markdown';
18
- export { MermaidBlock } from './MermaidBlock';
19
- export { MessageActions } from './MessageActions';
20
- export { ModelPicker, StashPicker } from './Pickers';
21
- export { type PromptHold, PromptComposer } from './PromptComposer';
22
- export { QuoteButton } from './QuoteButton';
17
+ export { ModelPicker } from './Pickers';
23
18
  export { ResumeCompactionDock } from './ResumeCompactionDock';
24
- export { RowTime } from './RowTime';
25
- export { RunSettings } from './RunSettings';
26
- export { Scrubber, type CardChat } from './Scrubber';
27
- export { SubagentTitleCrumb, SubagentBreadcrumb, SubagentActions } from './SubagentControls';
19
+ export { SubagentTitleCrumb, SubagentBreadcrumb } from './SubagentControls';
28
20
  export { SubagentInfo } from './SubagentInfo';
29
- export { SubagentStopButton } from './SubagentStopButton';
30
21
  export { SubagentTimeline } from './SubagentTimeline';
31
22
  export { Timeline } from './Timeline';
32
- export { TimelineMenuPopup, BookmarkMenuItems } from './TimelineMenu';
23
+ export { TimelineMenuPopup } from './TimelineMenu';
33
24
  export { default as UnifiedDiff } from './UnifiedDiff';
34
25
  export { UploadThumb } from './UploadThumb';
35
- export { VisualDialogs } from './VisualDialogs';
36
- export { type VisualFrameProps, VisualFrame } from './VisualFrame';
37
26
  export { WelcomeGreeting } from './Welcome';
38
- export { chipDecorations, type ChipChoices } from './composer/chips';
39
- export { externalChange, markdownLanguage, composerEditorExtensions } from './composer/editor';
40
- export { insertAtSelection } from './composer/insert';
41
- export {
42
- listItemAt,
43
- enterAction,
44
- inOpenFence,
45
- recallDirection,
46
- type EnterAction,
47
- type EnterKeys,
48
- type ListItem,
49
- type EnterContext,
50
- type RecallContext
51
- } from './composer/keys';
27
+ export { externalChange, composerEditorExtensions } from './composer/editor';
52
28
  export {
53
29
  createDiffSyntaxRuntime,
54
30
  diffSyntaxRuntime,
@@ -61,11 +37,10 @@ export {
61
37
  } from './diff-syntax';
62
38
  export { useDiffTheme } from './diff-theme';
63
39
  export type { UnifiedDiffProps } from './diff/diff-props';
64
- export { FileLinkContext, useFileLinkCwd, useFileLinkScopeId, useOpenFileLink } from './file-links';
65
- export { type ChatFindField, type FindReveal, useOpenForFind } from './find-reveal';
40
+ export { FileLinkContext, useFileLinkCwd } from './file-links';
41
+ export { type ChatFindField, type FindReveal } from './find-reveal';
66
42
  export type { DiffContents } from './full-diff';
67
43
  export { type UiBlockFrameProps, UiBlockFrame, type UiBlockFix, type UiBlockFooterProps } from './intelligent-ui/UiBlockFrame';
68
- export { UiFallbackPart, type UiFallbackProblem } from './intelligent-ui/UiFallbackPart';
69
44
  export { UiReply } from './intelligent-ui/UiReply';
70
45
  export { uiChartData, type UiChartSeries, type UiChartValues, type UiChartData } from './intelligent-ui/chart-data';
71
46
  export { uiNodeLabel } from './intelligent-ui/node-text';
@@ -75,6 +50,7 @@ export type {
75
50
  UiRendererProps,
76
51
  UiLinkTarget,
77
52
  UiLink,
53
+ UiPlacePoint,
78
54
  UiAnswer,
79
55
  UiLiveStatus,
80
56
  UiLiveValue,
@@ -86,24 +62,8 @@ export type { QuoteTaker } from './quote-selection';
86
62
  export { ReplyContext } from './reply-context';
87
63
  export { ForksRow } from './rows/ForksRow';
88
64
  export { GeneratedImageRow } from './rows/GeneratedImageRow';
89
- export {
90
- UserRow,
91
- ReportRow,
92
- ReplyHeader,
93
- AssistantRow,
94
- ThinkingRow,
95
- LiveThoughtRow,
96
- NoteRow,
97
- AgentTurnRow,
98
- CompactionRow,
99
- ApprovalHistoryRow,
100
- QuestionHistoryRow
101
- } from './rows/MessageRows';
102
- export { type RowContainerProps, RowContainer } from './rows/RowContainer';
103
- export { Row, type RowProps } from './rows/Rows';
104
- export { SubagentRow, SubagentBranchRow } from './rows/SubagentRow';
65
+ export { ReportRow, AssistantRow, NoteRow, AgentTurnRow, CompactionRow } from './rows/MessageRows';
105
66
  export { TaskRow } from './rows/TaskRow';
106
- export { VisualRow } from './rows/VisualRow';
107
- export { ToggleLine, WorkRow, RunningFor, WorkLiveRow, WorkGroupRow, TurnFoldRow, ChangedFilesRow, WorkingRow } from './rows/WorkRows';
67
+ export { WorkRow, WorkGroupRow, WorkingRow } from './rows/WorkRows';
108
68
  export { WorkflowRow } from './rows/WorkflowRow';
109
69
  export { useDiffSourceTokens } from './use-diff-source-tokens';
@@ -365,6 +365,23 @@ function UiNodeBody({
365
365
  );
366
366
  }
367
367
 
368
+ /*
369
+ * The points the machine froze beside a block, as a renderer asks for them. Until the block is frozen
370
+ * nothing is looked up yet, unless the machine keeps no readings for this reply at all; once it is, a place
371
+ * with no reading for its current address is one the machine did not look up.
372
+ */
373
+ function placeReader(frozen: ChatUiQueryState['blocks'][string] | undefined, revision: string | undefined, unrecorded: boolean): UiRenderContext['place'] {
374
+ if (frozen === undefined || frozen.revision !== revision) {
375
+ return () => (unrecorded ? null : undefined);
376
+ }
377
+ return (nodeId, address) => {
378
+ const reading = Object.hasOwn(frozen.places ?? {}, nodeId) ? frozen.places![nodeId] : undefined;
379
+ return reading?.address === address.trim() && reading.state === 'found' && reading.lat !== undefined && reading.lon !== undefined
380
+ ? { lat: reading.lat, lon: reading.lon, label: reading.label }
381
+ : null;
382
+ };
383
+ }
384
+
368
385
  /* What names a block and whether it still streams; the rest of its render context the block works out itself. */
369
386
  type UiBlockIdentity = Pick<UiRenderContext, 'scopeId' | 'chatId' | 'itemId' | 'blockId' | 'phase'>;
370
387
 
@@ -372,12 +389,15 @@ function UiBlockBody({
372
389
  block,
373
390
  context,
374
391
  answered,
375
- frozen
392
+ frozen,
393
+ unrecorded
376
394
  }: {
377
395
  block: UiBlock;
378
396
  context: UiBlockIdentity;
379
397
  answered?: ChatUiAnswer;
380
398
  frozen?: ChatUiQueryState['blocks'][string];
399
+ /* Set for a finished reply the machine keeps no readings for, which nothing will ever look up. */
400
+ unrecorded: boolean;
381
401
  }): ReactNode {
382
402
  const host = useChatHost();
383
403
  const { t } = useTranslation('agent-chat');
@@ -430,9 +450,11 @@ function UiBlockBody({
430
450
  );
431
451
  const liveValue = useUiLiveValues(block, evaluation.nodes, queries.currentReadings);
432
452
  const sendChoice = host.intelligentUi?.sendChoice;
453
+ const place = useMemo(() => placeReader(frozen, block.revision, unrecorded), [frozen, block.revision, unrecorded]);
433
454
  const rendering: UiRenderContext = {
434
455
  ...context,
435
456
  ...links,
457
+ place,
436
458
  answer,
437
459
  failedChoiceId,
438
460
  live: queries.live,
@@ -531,6 +553,7 @@ export function UiReply({
531
553
  <UiBlockBody
532
554
  block={part.block}
533
555
  frozen={queries?.blocks[part.block.id]}
556
+ unrecorded={context.phase === 'final' && queries === undefined}
534
557
  answered={answers?.[part.block.id]}
535
558
  context={{ scopeId: context.scopeId, chatId: context.chatId, itemId: context.itemId, phase: context.phase, blockId: part.block.id }}
536
559
  />
@@ -1,5 +1,6 @@
1
1
  import type { TFunction } from 'i18next';
2
- import type { UiComponentName, UiProps, UiViewNode } from '@adecore/intelligent-ui';
2
+ import { parseUiEventTime, type UiComponentName, type UiProps, type UiViewNode } from '@adecore/intelligent-ui';
3
+ import { formatClock, formatDay, formatDayClock, formatDayClockWithYear, formatDayWithYear } from '@adecore/ui/format';
3
4
  import type { UiLiveStatus } from './render-context';
4
5
 
5
6
  /* The text a node holds, every `$text` below it joined as written, such as the code of a CodeBlock. */
@@ -12,6 +13,16 @@ export function uiNodeLabel(node: UiViewNode): string {
12
13
  return uiNodeText(node).replace(/\s+/g, ' ').trim();
13
14
  }
14
15
 
16
+ /* The text written directly in a node, beside its component children: the label of a Select, whose children are mostly its options. */
17
+ export function uiOwnLabel(node: UiViewNode): string {
18
+ return node.children
19
+ .filter((child) => child.type === '$text')
20
+ .map((child) => String(child.props.text ?? ''))
21
+ .join('')
22
+ .replace(/\s+/g, ' ')
23
+ .trim();
24
+ }
25
+
15
26
  /* The valid children of one kind, which a parent reads as metadata: the columns of a table, the tabs of a strip. */
16
27
  export function uiChildrenOf<Name extends UiComponentName>(node: UiViewNode, type: Name): UiViewNode<UiProps<Name>>[] {
17
28
  return node.children.filter((child) => child.type === type && child.error === undefined) as UiViewNode<UiProps<Name>>[];
@@ -117,3 +128,25 @@ export function uiUrlDomain(url: string): string {
117
128
  return url;
118
129
  }
119
130
  }
131
+
132
+ /*
133
+ * An event's `at` the way the person's language and region write it. The year is written once it is not the
134
+ * one we are in; a text without an offset reads as written, whatever zone or clock change the reader is in.
135
+ * A text the catalog would not accept stays as written.
136
+ */
137
+ export function uiEventTime(at: string, now: Date | number = Date.now()): string {
138
+ const moment = parseUiEventTime(at);
139
+ if (moment === null) {
140
+ return at;
141
+ }
142
+ const zone = moment.written ? 'UTC' : undefined;
143
+ const year = moment.written ? moment.date.getUTCFullYear() : moment.date.getFullYear();
144
+ const withYear = year !== new Date(now).getFullYear();
145
+ if (moment.kind === 'time') {
146
+ return formatClock(moment.date, zone);
147
+ }
148
+ if (moment.kind === 'date') {
149
+ return withYear ? formatDayWithYear(moment.date, zone) : formatDay(moment.date, zone);
150
+ }
151
+ return withYear ? formatDayClockWithYear(moment.date, zone) : formatDayClock(moment.date, zone);
152
+ }
@@ -1,17 +1,37 @@
1
1
  import type { UiRenderers } from './render-context';
2
2
  import { ChartRenderer } from './renderers/chart';
3
3
  import { ChoiceRenderer, ChoicesRenderer } from './renderers/choices';
4
- import { CodeBlockRenderer, ImageRenderer, SourceRenderer, SourcesRenderer } from './renderers/content';
4
+ import { CodeBlockRenderer, CompareRenderer, GalleryRenderer, ImageRenderer, SourceRenderer, SourcesRenderer } from './renderers/content';
5
5
  import { EntityListRenderer, EntryRenderer, StatRenderer, StatsRenderer, TableRenderer } from './renderers/data';
6
- import { ButtonRenderer, ChecklistRenderer, ItemRenderer, SegmentedRenderer, SliderRenderer, SwitchRenderer } from './renderers/inputs';
6
+ import {
7
+ ButtonRenderer,
8
+ ChecklistRenderer,
9
+ ItemRenderer,
10
+ SegmentedRenderer,
11
+ SelectRenderer,
12
+ SliderRenderer,
13
+ SwitchRenderer,
14
+ TextFieldRenderer
15
+ } from './renderers/inputs';
7
16
  import { CommitRenderer, DiffRenderer, FileRenderer, NodeRenderer } from './renderers/links';
17
+ import { MapRenderer } from './renderers/map';
8
18
  import { SectionRenderer, SectionsRenderer, TabRenderer, TabsRenderer } from './renderers/structure';
9
- import { CalloutRenderer, ProgressRenderer, StepRenderer, StepsRenderer, SummaryRenderer, TagRenderer } from './renderers/text';
19
+ import {
20
+ CalloutRenderer,
21
+ EventRenderer,
22
+ ProgressRenderer,
23
+ StepRenderer,
24
+ StepsRenderer,
25
+ SummaryRenderer,
26
+ TagRenderer,
27
+ TimelineRenderer
28
+ } from './renderers/text';
10
29
 
11
30
  /*
12
31
  * A renderer for every component of the catalog; a name missing here fails the typecheck. Null draws
13
- * nothing for the node itself: the runtime resolves Show and Each before rendering, and Column and
14
- * Option are metadata their parent reads from its node (a table its columns, Segmented its segments).
32
+ * nothing for the node itself: the runtime resolves Show and Each before rendering, and Column,
33
+ * Option and Place are metadata their parent reads from its node (a table its columns, Segmented and
34
+ * Select their options, a map its places).
15
35
  */
16
36
  export const UI_RENDERERS: UiRenderers = {
17
37
  Summary: SummaryRenderer,
@@ -20,6 +40,8 @@ export const UI_RENDERERS: UiRenderers = {
20
40
  Progress: ProgressRenderer,
21
41
  Steps: StepsRenderer,
22
42
  Step: StepRenderer,
43
+ Timeline: TimelineRenderer,
44
+ Event: EventRenderer,
23
45
  Stats: StatsRenderer,
24
46
  Stat: StatRenderer,
25
47
  EntityList: EntityListRenderer,
@@ -33,6 +55,10 @@ export const UI_RENDERERS: UiRenderers = {
33
55
  Section: SectionRenderer,
34
56
  CodeBlock: CodeBlockRenderer,
35
57
  Image: ImageRenderer,
58
+ Gallery: GalleryRenderer,
59
+ Compare: CompareRenderer,
60
+ Map: MapRenderer,
61
+ Place: null,
36
62
  Sources: SourcesRenderer,
37
63
  Source: SourceRenderer,
38
64
  File: FileRenderer,
@@ -44,7 +70,9 @@ export const UI_RENDERERS: UiRenderers = {
44
70
  Switch: SwitchRenderer,
45
71
  Slider: SliderRenderer,
46
72
  Segmented: SegmentedRenderer,
73
+ Select: SelectRenderer,
47
74
  Option: null,
75
+ TextField: TextFieldRenderer,
48
76
  Button: ButtonRenderer,
49
77
  Show: null,
50
78
  Each: null,
@@ -15,6 +15,14 @@ export interface UiLink {
15
15
  code?: string;
16
16
  }
17
17
 
18
+ /* The point a Place stands at, in decimal degrees. */
19
+ export interface UiPlacePoint {
20
+ lat: number;
21
+ lon: number;
22
+ /* What the host called the point it found, such as the street and town. */
23
+ label?: string;
24
+ }
25
+
18
26
  /* The Choice that answered a block. `sending` already reads as sent, so a second click does nothing. */
19
27
  export interface UiAnswer {
20
28
  choiceId: string;
@@ -63,6 +71,12 @@ export interface UiRenderContext {
63
71
  onChoose?(nodeId: string): void;
64
72
  /* Why the choices are closed while `onChoose` is absent, such as live data still being read; without it they say they are not available. */
65
73
  choiceReason?: string | null;
74
+ /*
75
+ * The point the host found for the `address` of the Place `nodeId`: undefined while it is not looked up
76
+ * yet, null when none was found. A reading counts only for the address it was made for, so a place that
77
+ * moved to another node, or whose address changed, does not wear another place's point.
78
+ */
79
+ place?(nodeId: string, address: string): UiPlacePoint | null | undefined;
66
80
  /* Table cells use the evaluated rows index and column key. */
67
81
  liveValue?(nodeId: string, prop: string, path?: readonly [row: number, key: string]): UiLiveValue | undefined;
68
82
  }