@adecore/agents-react 0.21.0 → 0.21.1

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 (47) hide show
  1. package/dist/chat/ui/intelligent-ui/UiBlockFrame.d.ts +14 -1
  2. package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +47 -5
  3. package/dist/chat/ui/intelligent-ui/UiReply.d.ts +3 -1
  4. package/dist/chat/ui/intelligent-ui/UiReply.js +34 -30
  5. package/dist/chat/ui/intelligent-ui/block-local.d.ts +9 -0
  6. package/dist/chat/ui/intelligent-ui/block-local.js +30 -1
  7. package/dist/chat/ui/intelligent-ui/node-text.d.ts +6 -0
  8. package/dist/chat/ui/intelligent-ui/node-text.js +54 -0
  9. package/dist/chat/ui/intelligent-ui/registry.js +2 -1
  10. package/dist/chat/ui/intelligent-ui/render-context.d.ts +3 -0
  11. package/dist/chat/ui/intelligent-ui/renderers/chart.js +5 -7
  12. package/dist/chat/ui/intelligent-ui/renderers/choices.js +6 -2
  13. package/dist/chat/ui/intelligent-ui/renderers/content.js +6 -13
  14. package/dist/chat/ui/intelligent-ui/renderers/data.js +8 -15
  15. package/dist/chat/ui/intelligent-ui/renderers/inputs.d.ts +1 -0
  16. package/dist/chat/ui/intelligent-ui/renderers/inputs.js +16 -4
  17. package/dist/chat/ui/intelligent-ui/renderers/links.js +2 -1
  18. package/dist/chat/ui/intelligent-ui/use-ui-links.d.ts +4 -4
  19. package/dist/chat/ui/intelligent-ui/use-ui-links.js +29 -10
  20. package/dist/chat/ui/intelligent-ui/use-ui-queries.d.ts +2 -1
  21. package/dist/chat/ui/intelligent-ui/use-ui-queries.js +34 -21
  22. package/dist/chat/ui/rows/MessageRows.js +1 -1
  23. package/dist/locales/en/agent-chat.json +23 -4
  24. package/dist/locales/nl/agent-chat.json +23 -4
  25. package/dist/state/chats.js +5 -1
  26. package/package.json +24 -29
  27. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +135 -5
  28. package/src/chat/ui/intelligent-ui/UiReply.tsx +48 -38
  29. package/src/chat/ui/intelligent-ui/block-local.ts +36 -1
  30. package/src/chat/ui/intelligent-ui/node-text.ts +60 -0
  31. package/src/chat/ui/intelligent-ui/registry.ts +2 -1
  32. package/src/chat/ui/intelligent-ui/render-context.ts +6 -0
  33. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +5 -7
  34. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +6 -2
  35. package/src/chat/ui/intelligent-ui/renderers/content.tsx +9 -14
  36. package/src/chat/ui/intelligent-ui/renderers/data.tsx +13 -19
  37. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +28 -5
  38. package/src/chat/ui/intelligent-ui/renderers/links.tsx +2 -1
  39. package/src/chat/ui/intelligent-ui/use-ui-links.ts +31 -11
  40. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +48 -22
  41. package/src/chat/ui/rows/MessageRows.tsx +1 -1
  42. package/src/locales/en/agent-chat.json +23 -4
  43. package/src/locales/nl/agent-chat.json +23 -4
  44. package/src/state/chats.ts +5 -1
  45. package/dist/chat/ui/intelligent-ui/UiBlockFooter.d.ts +0 -15
  46. package/dist/chat/ui/intelligent-ui/UiBlockFooter.js +0 -50
  47. package/src/chat/ui/intelligent-ui/UiBlockFooter.tsx +0 -139
@@ -5,3 +5,4 @@ export declare function ItemRenderer({ node, children }: UiRendererProps<UiProps
5
5
  export declare function SwitchRenderer({ node, children, context }: UiRendererProps<UiProps<'Switch'>>): import("react").JSX.Element;
6
6
  export declare function SliderRenderer({ node, context }: UiRendererProps<UiProps<'Slider'>>): import("react").JSX.Element;
7
7
  export declare function SegmentedRenderer({ node, context }: UiRendererProps<UiProps<'Segmented'>>): import("react").JSX.Element;
8
+ export declare function ButtonRenderer({ node, children, context }: UiRendererProps<UiProps<'Button'>>): import("react").JSX.Element;
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, useContext } from 'react';
3
3
  import clsx from 'clsx';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { Checkbox, Segmented, Slider, Switch } from '@adecore/ui';
6
- import { uiChildrenOf, uiNodeLabel } from '../node-text.js';
5
+ import { Button, Checkbox, Segmented, Slider, Switch } from '@adecore/ui';
6
+ import { uiChildrenOf, uiNodeLabel, uiWithUnit } from '../node-text.js';
7
7
  const ChecklistContext = createContext({ values: [], enabled: false, toggle: () => undefined });
8
8
  /*
9
9
  * The binding of an input, or null for a read-only node. An input works once its own node closed and
@@ -15,13 +15,18 @@ function bindingOf(node, context) {
15
15
  }
16
16
  /* An input whose node has not closed yet stands at 60% and takes no pointer. */
17
17
  const PENDING = 'pointer-events-none opacity-60';
18
+ /* The rows of a checklist as one named group. */
19
+ function ChecklistGroup({ pending, children }) {
20
+ const { t } = useTranslation('agent-chat');
21
+ return (_jsx("div", { role: "group", "aria-label": t('blocks.checklist'), className: clsx('chat-ui-nodes flex flex-col', pending && PENDING), children: children }));
22
+ }
18
23
  export function ChecklistRenderer({ node, children, context }) {
19
24
  const binding = bindingOf(node, context);
20
25
  const values = binding?.value ?? node.props.value;
21
26
  const toggle = (value) => {
22
27
  binding?.onValueChange(values.includes(value) ? values.filter((entry) => entry !== value) : [...values, value]);
23
28
  };
24
- return (_jsx(ChecklistContext, { value: { values, enabled: binding !== null, toggle }, children: _jsx("div", { className: clsx('chat-ui-nodes flex flex-col', !node.complete && PENDING), children: children }) }));
29
+ return (_jsx(ChecklistContext, { value: { values, enabled: binding !== null, toggle }, children: _jsx(ChecklistGroup, { pending: !node.complete, children: children }) }));
25
30
  }
26
31
  /* The whole row is the label; a link inside it keeps its own click and checks nothing. */
27
32
  export function ItemRenderer({ node, children }) {
@@ -42,7 +47,9 @@ export function SwitchRenderer({ node, children, context }) {
42
47
  export function SliderRenderer({ node, context }) {
43
48
  const binding = bindingOf(node, context);
44
49
  const { value, min, max, step, unit } = node.props;
45
- return (_jsx(Slider, { value: binding?.value ?? value, onValueChange: (next) => binding?.onValueChange(next), min: min, max: max, step: step, unit: unit, label: uiNodeLabel(node), disabled: binding === null, className: clsx('px-2', !node.complete && PENDING) }));
50
+ return (_jsx(Slider, { value: binding?.value ?? value, onValueChange: (next) => binding?.onValueChange(next), min: min, max: max, step: step,
51
+ // The slider prints its unit right after the number, so the space a unit stands apart with comes from here.
52
+ unit: uiWithUnit('', unit), label: uiNodeLabel(node), disabled: binding === null, className: clsx('px-2', !node.complete && PENDING) }));
46
53
  }
47
54
  /* Left aligned, never the full width. Each Option is a segment by its place, since a value may be any scalar. */
48
55
  export function SegmentedRenderer({ node, context }) {
@@ -58,3 +65,8 @@ export function SegmentedRenderer({ node, context }) {
58
65
  }
59
66
  } }) }));
60
67
  }
68
+ /* Runs its local action once its own node closed; like an input, it closes once the block is answered. */
69
+ export function ButtonRenderer({ node, children, context }) {
70
+ const enabled = node.onAction !== undefined && node.complete && context.answer === null && node.props.disabled !== true;
71
+ return (_jsx("div", { className: clsx('px-2', !node.complete && PENDING), children: _jsx(Button, { variant: "secondary", size: "sm", disabled: !enabled, onClick: () => node.onAction?.(), children: children }) }));
72
+ }
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
4
4
  import { FileDiff, GitCommitHorizontal, SquareDashed } from 'lucide-react';
5
5
  import { FileIcon, Icon, Tooltip } from '@adecore/ui';
6
6
  import { CHIP_IN_MESSAGE, MENTION_TONE } from '../../chips.js';
7
+ import { uiReasonText } from '../node-text.js';
7
8
  const PLAIN = { state: 'plain' };
8
9
  function baseName(path) {
9
10
  const trimmed = path.replace(/\/+$/, '');
@@ -54,7 +55,7 @@ export function UiLinkChip({ target, context }) {
54
55
  const { t } = useTranslation('agent-chat');
55
56
  const link = context.link?.(target) ?? PLAIN;
56
57
  if (link.state === 'plain') {
57
- const reason = link.reason ?? (context.link === undefined ? null : t('blocks.outsideProject'));
58
+ const reason = uiReasonText(t, link.code, link.reason) ?? (context.link === undefined ? null : t('blocks.outsideProject'));
58
59
  // Focusable only with a reason, so a keyboard reaches the tooltip.
59
60
  const text = (_jsx("span", { tabIndex: reason === null ? undefined : 0, className: "font-mono text-code break-all text-text-muted", children: plainText(target, link) }));
60
61
  return reason === null ? text : _jsx(Tooltip, { label: reason, children: text });
@@ -1,9 +1,9 @@
1
- import type { ChatUiQueryState } from '@adecore/agent-contracts';
1
+ import type { ChatUiLinkReading, ChatUiQueryState } from '@adecore/agent-contracts';
2
2
  import { type UiLinkTarget } from '@adecore/intelligent-ui/links';
3
3
  import { type UiBlock, UiState } from '@adecore/intelligent-ui';
4
4
  import type { UiRenderContext } from './render-context.js';
5
- export declare function useUiLinks(block: UiBlock, state: UiState, context: UiRenderContext, reads: Readonly<Record<string, string>>, frozen?: ChatUiQueryState['blocks'][string]): {
6
- link: ((target: UiLinkTarget) => import("./render-context.js").UiLink) | undefined;
5
+ export declare function useUiLinks(block: UiBlock, state: UiState, context: Pick<UiRenderContext, 'scopeId' | 'chatId' | 'itemId' | 'phase'>, reads: Readonly<Record<string, string>>, frozen?: ChatUiQueryState['blocks'][string]): {
6
+ link: ((target: UiLinkTarget) => ChatUiLinkReading) | undefined;
7
7
  openLink: ((target: UiLinkTarget) => void) | undefined;
8
- openUrl: ((url: string) => void) | undefined;
8
+ openUrl: ((url: string) => void | undefined) | undefined;
9
9
  };
@@ -1,9 +1,13 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { uiLinkTargets } from '@adecore/intelligent-ui/links';
3
- import { uiInputValues, UiState } from '@adecore/intelligent-ui';
3
+ import { uiInputValues, UI_HOST_LIMITS, UiState } from '@adecore/intelligent-ui';
4
4
  import { chatHost } from '../../../host.js';
5
+ // Inputs that change in a run, such as a dragged slider, check their links once the run rested.
6
+ const LINK_SETTLE_MS = 300;
5
7
  export function useUiLinks(block, state, context, reads, frozen) {
6
8
  const [checked, setChecked] = useState({});
9
+ // What each node was last checked as, so a change of input checks only the targets it moved.
10
+ const asked = useRef(new Map());
7
11
  const host = chatHost().intelligentUi;
8
12
  const values = uiInputValues(block, state);
9
13
  const scope = state.scope();
@@ -20,14 +24,25 @@ export function useUiLinks(block, state, context, reads, frozen) {
20
24
  const signature = JSON.stringify(targets);
21
25
  const input = JSON.stringify(values);
22
26
  const tickets = JSON.stringify(reads);
27
+ const defaultInput = useMemo(() => JSON.stringify(uiInputValues(block, new UiState(block))), [block]);
23
28
  const link = host?.link;
29
+ useEffect(() => {
30
+ asked.current.clear();
31
+ }, [block.id, block.revision, context.scopeId, context.chatId, context.itemId, link]);
24
32
  useEffect(() => {
25
33
  if (!link || !block.revision || context.phase !== 'final') {
26
34
  return;
27
35
  }
28
36
  let stopped = false;
29
37
  const validate = async () => {
30
- for (const [nodeId, target] of Object.entries(JSON.parse(signature)).slice(0, 64)) {
38
+ const moved = Object.entries(JSON.parse(signature))
39
+ .slice(0, UI_HOST_LIMITS.links)
40
+ .filter(([nodeId, target]) => asked.current.get(nodeId) !== JSON.stringify(target));
41
+ for (const [nodeId, target] of moved) {
42
+ const record = (reading) => {
43
+ asked.current.set(nodeId, JSON.stringify(target));
44
+ setChecked((previous) => ({ ...previous, [nodeId]: { target: JSON.stringify(target), reading } }));
45
+ };
31
46
  try {
32
47
  const reading = await link(context.scopeId, {
33
48
  chatId: context.chatId,
@@ -41,19 +56,21 @@ export function useUiLinks(block, state, context, reads, frozen) {
41
56
  if (stopped) {
42
57
  return;
43
58
  }
44
- setChecked((previous) => ({ ...previous, [nodeId]: { target: JSON.stringify(target), reading } }));
59
+ record(reading);
45
60
  }
46
61
  catch {
47
62
  if (stopped) {
48
63
  return;
49
64
  }
50
- setChecked((previous) => ({ ...previous, [nodeId]: { target: JSON.stringify(target), reading: { state: 'plain' } } }));
65
+ record({ state: 'plain' });
51
66
  }
52
67
  }
53
68
  };
54
- void validate();
69
+ // The first check runs at once; a later one waits until the inputs rested.
70
+ const timer = setTimeout(() => void validate(), asked.current.size === 0 ? 0 : LINK_SETTLE_MS);
55
71
  return () => {
56
72
  stopped = true;
73
+ clearTimeout(timer);
57
74
  };
58
75
  }, [block.id, block.revision, context.scopeId, context.chatId, context.itemId, context.phase, signature, input, tickets, link]);
59
76
  const idOf = (target) => Object.entries(targets).find(([, entry]) => JSON.stringify(entry) === JSON.stringify(target))?.[0];
@@ -65,7 +82,7 @@ export function useUiLinks(block, state, context, reads, frozen) {
65
82
  if (checked[id]?.target === JSON.stringify(target)) {
66
83
  return checked[id].reading;
67
84
  }
68
- if (input === JSON.stringify(uiInputValues(block, new UiState(block))) && frozen && frozen.revision === block.revision) {
85
+ if (input === defaultInput && frozen && frozen.revision === block.revision) {
69
86
  const firstQueries = Object.fromEntries(Object.entries(frozen.readings)
70
87
  .filter(([, reading]) => reading.state === 'fresh')
71
88
  .map(([name, reading]) => [name, reading.value]));
@@ -99,9 +116,11 @@ export function useUiLinks(block, state, context, reads, frozen) {
99
116
  setChecked((previous) => ({ ...previous, [nodeId]: { target: JSON.stringify(target), reading: { state: 'plain' } } }));
100
117
  });
101
118
  };
119
+ const hostOpenUrl = host?.openUrl;
120
+ const openUrl = useCallback((url) => hostOpenUrl?.(context.scopeId, url), [hostOpenUrl, context.scopeId]);
102
121
  return {
103
- link: link ? resolution : context.link,
104
- openLink: link && host?.openLink ? open : context.openLink,
105
- openUrl: host?.openUrl ? (url) => host.openUrl(context.scopeId, url) : context.openUrl
122
+ link: link ? resolution : undefined,
123
+ openLink: link && host?.openLink ? open : undefined,
124
+ openUrl: hostOpenUrl ? openUrl : undefined
106
125
  };
107
126
  }
@@ -1,7 +1,7 @@
1
1
  import type { ChatUiQueryReading, ChatUiQueryState } from '@adecore/agent-contracts';
2
2
  import { UiState, type UiBlock, type UiViewNode } from '@adecore/intelligent-ui';
3
3
  import type { UiLiveValue, UiLiveStatus, UiRenderContext } from './render-context.js';
4
- export declare function useUiQueries(block: UiBlock, state: UiState, context: UiRenderContext, element: HTMLDivElement | null, frozen?: ChatUiQueryState['blocks'][string]): {
4
+ export declare function useUiQueries(block: UiBlock, state: UiState, context: Pick<UiRenderContext, 'scopeId' | 'chatId' | 'itemId' | 'phase'>, element: HTMLDivElement | null, frozen?: ChatUiQueryState['blocks'][string]): {
5
5
  live: UiLiveStatus | undefined;
6
6
  reads: Record<string, string>;
7
7
  reading: boolean;
@@ -13,6 +13,7 @@ export declare function useUiQueries(block: UiBlock, state: UiState, context: Ui
13
13
  value?: unknown;
14
14
  readId?: string | undefined;
15
15
  readAt: number;
16
+ code?: string | undefined;
16
17
  reason?: string | undefined;
17
18
  };
18
19
  };
@@ -1,27 +1,34 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { uiInputValues, UI_CATALOG_VERSION, UiState } from '@adecore/intelligent-ui';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { uiInputValues, UI_CATALOG_VERSION, UI_HOST_LIMITS, UiState } from '@adecore/intelligent-ui';
3
3
  import { chatHost } from '../../../host.js';
4
+ const NO_READINGS = {};
5
+ // A changed input reads once it has rested this long, so dragging a slider reads its last value only.
6
+ const INPUT_SETTLE_MS = 300;
7
+ /* The reading that holds for these inputs: this view's own read of them, or the frozen first one for the inputs the block starts with. */
8
+ function successfulReading(local, initial, identity, signature, defaultInputs) {
9
+ if (local?.identity === identity && local.successful?.inputs === signature) {
10
+ return local.successful.reading;
11
+ }
12
+ return signature === defaultInputs && initial?.state === 'fresh' ? initial : undefined;
13
+ }
14
+ function settleDeadline() {
15
+ return Date.now() + INPUT_SETTLE_MS;
16
+ }
4
17
  function documentVisible() {
5
18
  return document.visibilityState !== 'hidden';
6
19
  }
7
20
  export function useUiQueries(block, state, context, element, frozen) {
8
21
  const [readings, setReadings] = useState({});
9
22
  const [busyInput, setBusyInput] = useState(null);
10
- const lastReadAt = useRef({ identity: '', at: 0 });
23
+ const lastRead = useRef({ inputKey: '', at: 0 });
11
24
  const names = Object.keys(block.queries);
12
25
  const inputs = JSON.stringify(uiInputValues(block, state));
13
26
  const identity = JSON.stringify([context.scopeId, context.chatId, context.itemId, block.id, block.revision]);
14
27
  const inputKey = JSON.stringify([identity, inputs]);
15
28
  const reading = busyInput === inputKey;
16
- const defaultInputs = JSON.stringify(uiInputValues(block, new UiState(block)));
17
- const initial = frozen && frozen.revision === block.revision ? frozen.readings : {};
18
- const successfulFor = (name, signature) => {
19
- const local = readings[name];
20
- if (local?.identity === identity && local.successful?.inputs === signature) {
21
- return local.successful.reading;
22
- }
23
- return signature === defaultInputs && initial[name]?.state === 'fresh' ? initial[name] : undefined;
24
- };
29
+ const defaultInputs = useMemo(() => JSON.stringify(uiInputValues(block, new UiState(block))), [block]);
30
+ const initial = frozen && frozen.revision === block.revision ? frozen.readings : NO_READINGS;
31
+ const successfulFor = (name, signature) => successfulReading(readings[name], initial[name], identity, signature, defaultInputs);
25
32
  const readsFor = (values) => {
26
33
  const signature = JSON.stringify(values);
27
34
  const reads = {};
@@ -41,13 +48,13 @@ export function useUiQueries(block, state, context, element, frozen) {
41
48
  const subscribe = chatHost().intelligentUi?.subscribe;
42
49
  useEffect(() => {
43
50
  const scope = state.scope();
44
- for (const name of names) {
45
- const successful = successfulFor(name, inputs);
51
+ for (const name of Object.keys(block.queries)) {
52
+ const successful = successfulReading(readings[name], initial[name], identity, inputs, defaultInputs);
46
53
  if (successful && JSON.stringify(scope[name]) !== JSON.stringify(successful.value)) {
47
54
  state.setQuery(name, successful.value, block);
48
55
  }
49
56
  }
50
- });
57
+ }, [state, block, readings, identity, inputs, defaultInputs, initial]);
51
58
  useEffect(() => {
52
59
  if (!query ||
53
60
  !element ||
@@ -62,11 +69,14 @@ export function useUiQueries(block, state, context, element, frozen) {
62
69
  let visible = typeof IntersectionObserver === 'undefined';
63
70
  let busy = false;
64
71
  let timer = null;
72
+ // The first read of this block reads at once; a changed input waits until it rests.
73
+ const changed = lastRead.current.inputKey !== '' && lastRead.current.inputKey !== inputKey;
74
+ const settledAt = changed ? settleDeadline() : 0;
65
75
  const refresh = async () => {
66
76
  if (stopped || !visible || !documentVisible() || busy) {
67
77
  return;
68
78
  }
69
- const wait = lastReadAt.current.identity === identity ? 10_000 - (Date.now() - lastReadAt.current.at) : 0;
79
+ const wait = Math.max(settledAt - Date.now(), lastRead.current.inputKey === inputKey ? UI_HOST_LIMITS.refreshMilliseconds - (Date.now() - lastRead.current.at) : 0);
70
80
  if (wait > 0) {
71
81
  if (timer === null) {
72
82
  if (!visible || !documentVisible()) {
@@ -80,9 +90,9 @@ export function useUiQueries(block, state, context, element, frozen) {
80
90
  return;
81
91
  }
82
92
  busy = true;
83
- lastReadAt.current = { identity, at: Date.now() };
93
+ lastRead.current = { inputKey, at: Date.now() };
84
94
  setBusyInput(inputKey);
85
- for (const name of Object.keys(block.queries).slice(0, 8)) {
95
+ for (const name of Object.keys(block.queries).slice(0, UI_HOST_LIMITS.queries)) {
86
96
  if (!visible || !documentVisible()) {
87
97
  break;
88
98
  }
@@ -118,7 +128,7 @@ export function useUiQueries(block, state, context, element, frozen) {
118
128
  ...previous,
119
129
  [name]: {
120
130
  identity,
121
- reading: { state: 'failed', readAt: Date.now(), reason: error instanceof Error ? error.message : 'This query could not be read.' },
131
+ reading: { state: 'failed', readAt: Date.now(), code: 'unreadable', reason: error instanceof Error ? error.message : undefined },
122
132
  successful: previous[name]?.identity === identity ? previous[name].successful : undefined
123
133
  }
124
134
  }));
@@ -132,7 +142,7 @@ export function useUiQueries(block, state, context, element, frozen) {
132
142
  timer = setTimeout(() => {
133
143
  timer = null;
134
144
  void refresh();
135
- }, 10_000);
145
+ }, UI_HOST_LIMITS.refreshMilliseconds);
136
146
  };
137
147
  const observer = typeof IntersectionObserver === 'undefined'
138
148
  ? null
@@ -157,6 +167,8 @@ export function useUiQueries(block, state, context, element, frozen) {
157
167
  void refresh();
158
168
  return () => {
159
169
  stopped = true;
170
+ // A read cut short by this cleanup never reaches its own reset, which would leave the choices closed.
171
+ setBusyInput((current) => (current === inputKey ? null : current));
160
172
  observer?.disconnect();
161
173
  unsubscribe?.();
162
174
  document.removeEventListener('visibilitychange', focus);
@@ -179,7 +191,8 @@ export function useUiQueries(block, state, context, element, frozen) {
179
191
  .filter((reading) => reading.state === 'fresh')
180
192
  .reduce((at, value) => (at === null ? value.readAt : Math.min(at, value.readAt)), null),
181
193
  source: failed ? block.queries[failed[0]]?.source : undefined,
182
- reason: failed?.[1].reason
194
+ reason: failed?.[1].reason,
195
+ code: failed?.[1].code
183
196
  }
184
197
  : undefined;
185
198
  return { live, reads: reads ?? {}, reading, ready, readsFor, currentReadings };
@@ -129,7 +129,7 @@ export function AssistantRow({ chatId, item: derived }) {
129
129
  const live = mode === 'words' && item.streaming;
130
130
  const reveal = useRevealedText(item.text, live);
131
131
  if (item.ui?.length && !item.parentToolUseId && !(mode === 'whole' && item.streaming)) {
132
- return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsx(ReplyHeading, { chatId: chatId }), _jsx("div", { "data-find-field": "text", "data-quote-answer": true, className: "contents", children: _jsx(UiReply, { text: item.text, blocks: item.ui, answers: item.uiAnswers, queries: item.uiQueries, reply: reply, context: { scopeId: scope.id, chatId, itemId: item.id, phase: item.streaming ? 'streaming' : 'final', answer: null } }) })] }));
132
+ return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsx(ReplyHeading, { chatId: chatId }), _jsx("div", { "data-find-field": "text", "data-quote-answer": true, className: "contents", children: _jsx(UiReply, { text: item.text, blocks: item.ui, answers: item.uiAnswers, queries: item.uiQueries, reply: reply, context: { scopeId: scope.id, chatId, itemId: item.id, phase: item.streaming ? 'streaming' : 'final' } }) })] }));
133
133
  }
134
134
  if (mode === 'blocks') {
135
135
  const settled = item.streaming ? settledBlocksText(item.text) : item.text;
@@ -498,6 +498,7 @@
498
498
  "from": "from {{previous}}"
499
499
  },
500
500
  "table": {
501
+ "label": "Table of {{columns}}",
501
502
  "showAll": "Show all {{total}} rows"
502
503
  },
503
504
  "chart": {
@@ -514,8 +515,10 @@
514
515
  "outsideProject": "Outside the project, shown as text",
515
516
  "options": "Options",
516
517
  "choices": "Choices",
518
+ "checklist": "Checklist",
517
519
  "choice": {
518
520
  "waiting": "Available when the reply is done",
521
+ "reading": "Available once the live data is read",
519
522
  "unavailable": "Not available",
520
523
  "sent": "Sent",
521
524
  "queued": "Queued",
@@ -525,14 +528,11 @@
525
528
  "answered": "Answered with “{{label}}”",
526
529
  "chose": "Chose “{{label}}”",
527
530
  "choseOlder": "Chose “{{label}}” on the reply of {{time}}",
528
- "sendContext": "Send",
529
- "copyText": "Copy text",
530
531
  "fixes_one": "{{count}} fix",
531
532
  "fixes_other": "{{count}} fixes",
532
533
  "shownAsText": "Shown as text",
533
534
  "unknownComponent": "This version cannot draw {{component}}",
534
535
  "renderFailed": "Could not draw this part",
535
- "tooLarge": "The block is too large to draw",
536
536
  "unreadable": "The block could not be read",
537
537
  "live": {
538
538
  "label": "Live",
@@ -540,7 +540,26 @@
540
540
  "sources": "Reads {{sources}}",
541
541
  "failed": "Could not read {{source}}",
542
542
  "refused": "Not allowed to read {{source}}",
543
- "notRead": "Not read"
543
+ "notRead": "Not read",
544
+ "reasons": {
545
+ "access-unavailable": "The access of the chat that wrote this is no longer available.",
546
+ "block-stale": "This block changed since it was drawn.",
547
+ "busy": "Too many live blocks are reading right now.",
548
+ "link-unchecked": "This link has to be checked again.",
549
+ "link-unsupported": "This is not a link the machine can open.",
550
+ "links-unsupported": "The machine does not open links from blocks.",
551
+ "no-unique-match": "The source found no single match for that name.",
552
+ "outside-project": "This is outside the project the agent wrote this in.",
553
+ "query-undeclared": "This block does not read that source.",
554
+ "refresh-limit": "This source reads at most once every ten seconds.",
555
+ "result-too-large": "What the source returned is too large.",
556
+ "snapshot-too-large": "The first readings were too large to keep.",
557
+ "source-unregistered": "The machine does not know this source.",
558
+ "stale-read": "Read the source again before you choose.",
559
+ "target-unavailable": "What this points to is no longer available.",
560
+ "timed-out": "The source took too long to answer.",
561
+ "unreadable": "The source could not be read."
562
+ }
544
563
  }
545
564
  }
546
565
  }
@@ -498,6 +498,7 @@
498
498
  "from": "vanaf {{previous}}"
499
499
  },
500
500
  "table": {
501
+ "label": "Tabel van {{columns}}",
501
502
  "showAll": "Alle {{total}} rijen tonen"
502
503
  },
503
504
  "chart": {
@@ -514,8 +515,10 @@
514
515
  "outsideProject": "Buiten het project, als tekst getoond",
515
516
  "options": "Opties",
516
517
  "choices": "Keuzes",
518
+ "checklist": "Checklijst",
517
519
  "choice": {
518
520
  "waiting": "Beschikbaar zodra het antwoord klaar is",
521
+ "reading": "Beschikbaar zodra de live gegevens gelezen zijn",
519
522
  "unavailable": "Niet beschikbaar",
520
523
  "sent": "Verstuurd",
521
524
  "queued": "In de wachtrij",
@@ -525,14 +528,11 @@
525
528
  "answered": "Beantwoord met “{{label}}”",
526
529
  "chose": "Gekozen: “{{label}}”",
527
530
  "choseOlder": "Gekozen: “{{label}}”, bij het antwoord van {{time}}",
528
- "sendContext": "Versturen",
529
- "copyText": "Tekst kopiëren",
530
531
  "fixes_one": "{{count}} correctie",
531
532
  "fixes_other": "{{count}} correcties",
532
533
  "shownAsText": "Als tekst getoond",
533
534
  "unknownComponent": "Deze versie kan {{component}} niet tekenen",
534
535
  "renderFailed": "Dit deel kon niet getekend worden",
535
- "tooLarge": "Het blok is te groot om te tekenen",
536
536
  "unreadable": "Het blok kon niet gelezen worden",
537
537
  "live": {
538
538
  "label": "Live",
@@ -540,7 +540,26 @@
540
540
  "sources": "Leest {{sources}}",
541
541
  "failed": "{{source}} lezen lukte niet",
542
542
  "refused": "Geen toegang tot {{source}}",
543
- "notRead": "Niet gelezen"
543
+ "notRead": "Niet gelezen",
544
+ "reasons": {
545
+ "access-unavailable": "De toegang van de chat die dit schreef is niet meer beschikbaar.",
546
+ "block-stale": "Dit blok is veranderd sinds het getekend werd.",
547
+ "busy": "Er lezen nu te veel live blokken tegelijk.",
548
+ "link-unchecked": "Deze link moet opnieuw gecontroleerd worden.",
549
+ "link-unsupported": "Dit is geen link die de machine kan openen.",
550
+ "links-unsupported": "De machine opent geen links uit blokken.",
551
+ "no-unique-match": "De bron vond voor die naam niet precies één overeenkomst.",
552
+ "outside-project": "Dit valt buiten het project waarin de agent dit schreef.",
553
+ "query-undeclared": "Dit blok leest die bron niet.",
554
+ "refresh-limit": "Deze bron leest hooguit eens per tien seconden.",
555
+ "result-too-large": "Wat de bron teruggaf is te groot.",
556
+ "snapshot-too-large": "De eerste metingen waren te groot om te bewaren.",
557
+ "source-unregistered": "De machine kent deze bron niet.",
558
+ "stale-read": "Lees de bron opnieuw voordat je kiest.",
559
+ "target-unavailable": "Waar dit naar verwijst is niet meer beschikbaar.",
560
+ "timed-out": "De bron deed er te lang over om te antwoorden.",
561
+ "unreadable": "De bron kon niet gelezen worden."
562
+ }
544
563
  }
545
564
  }
546
565
  }
@@ -80,7 +80,11 @@ export function applyEvent(state, event) {
80
80
  case 'delta': {
81
81
  const item = state.items[event.itemId];
82
82
  if (event.uiQueries && item?.kind === 'assistant') {
83
- return { ...state, items: { ...state.items, [event.itemId]: { ...item, uiQueries: event.uiQueries } } };
83
+ // The text first, as the daemon's thread applies it, then the metadata on what it grew into.
84
+ const { uiQueries, ...text } = event;
85
+ const grown = event.text === '' ? state : applyEvent(state, text);
86
+ const reply = grown.items[event.itemId] ?? item;
87
+ return { ...grown, items: { ...grown.items, [event.itemId]: { ...reply, uiQueries } } };
84
88
  }
85
89
  if (event.ui !== undefined && event.text === '') {
86
90
  if (item?.kind === 'assistant' && item.streaming && event.textLength === item.text.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents-react",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -519,11 +519,6 @@
519
519
  "types": "./dist/chat/ui/icons.d.ts",
520
520
  "default": "./dist/chat/ui/icons.js"
521
521
  },
522
- "./chat/ui/intelligent-ui/UiBlockFooter": {
523
- "source": "./src/chat/ui/intelligent-ui/UiBlockFooter.tsx",
524
- "types": "./dist/chat/ui/intelligent-ui/UiBlockFooter.d.ts",
525
- "default": "./dist/chat/ui/intelligent-ui/UiBlockFooter.js"
526
- },
527
522
  "./chat/ui/intelligent-ui/UiBlockFrame": {
528
523
  "source": "./src/chat/ui/intelligent-ui/UiBlockFrame.tsx",
529
524
  "types": "./dist/chat/ui/intelligent-ui/UiBlockFrame.d.ts",
@@ -534,6 +529,11 @@
534
529
  "types": "./dist/chat/ui/intelligent-ui/UiFallbackPart.d.ts",
535
530
  "default": "./dist/chat/ui/intelligent-ui/UiFallbackPart.js"
536
531
  },
532
+ "./chat/ui/intelligent-ui/UiReply": {
533
+ "source": "./src/chat/ui/intelligent-ui/UiReply.tsx",
534
+ "types": "./dist/chat/ui/intelligent-ui/UiReply.d.ts",
535
+ "default": "./dist/chat/ui/intelligent-ui/UiReply.js"
536
+ },
537
537
  "./chat/ui/intelligent-ui/block-local": {
538
538
  "source": "./src/chat/ui/intelligent-ui/block-local.ts",
539
539
  "types": "./dist/chat/ui/intelligent-ui/block-local.d.ts",
@@ -599,6 +599,11 @@
599
599
  "types": "./dist/chat/ui/intelligent-ui/renderers/text.d.ts",
600
600
  "default": "./dist/chat/ui/intelligent-ui/renderers/text.js"
601
601
  },
602
+ "./chat/ui/intelligent-ui/reply-parts": {
603
+ "source": "./src/chat/ui/intelligent-ui/reply-parts.ts",
604
+ "types": "./dist/chat/ui/intelligent-ui/reply-parts.d.ts",
605
+ "default": "./dist/chat/ui/intelligent-ui/reply-parts.js"
606
+ },
602
607
  "./chat/ui/intelligent-ui/table-data": {
603
608
  "source": "./src/chat/ui/intelligent-ui/table-data.ts",
604
609
  "types": "./dist/chat/ui/intelligent-ui/table-data.d.ts",
@@ -614,6 +619,16 @@
614
619
  "types": "./dist/chat/ui/intelligent-ui/use-later.d.ts",
615
620
  "default": "./dist/chat/ui/intelligent-ui/use-later.js"
616
621
  },
622
+ "./chat/ui/intelligent-ui/use-ui-links": {
623
+ "source": "./src/chat/ui/intelligent-ui/use-ui-links.ts",
624
+ "types": "./dist/chat/ui/intelligent-ui/use-ui-links.d.ts",
625
+ "default": "./dist/chat/ui/intelligent-ui/use-ui-links.js"
626
+ },
627
+ "./chat/ui/intelligent-ui/use-ui-queries": {
628
+ "source": "./src/chat/ui/intelligent-ui/use-ui-queries.ts",
629
+ "types": "./dist/chat/ui/intelligent-ui/use-ui-queries.d.ts",
630
+ "default": "./dist/chat/ui/intelligent-ui/use-ui-queries.js"
631
+ },
617
632
  "./chat/ui/markdown-blocks": {
618
633
  "source": "./src/chat/ui/markdown-blocks.ts",
619
634
  "types": "./dist/chat/ui/markdown-blocks.d.ts",
@@ -987,26 +1002,6 @@
987
1002
  "source": "./src/chat/visual-viewport.ts",
988
1003
  "types": "./dist/chat/visual-viewport.d.ts",
989
1004
  "default": "./dist/chat/visual-viewport.js"
990
- },
991
- "./chat/ui/intelligent-ui/use-ui-links": {
992
- "source": "./src/chat/ui/intelligent-ui/use-ui-links.ts",
993
- "types": "./dist/chat/ui/intelligent-ui/use-ui-links.d.ts",
994
- "default": "./dist/chat/ui/intelligent-ui/use-ui-links.js"
995
- },
996
- "./chat/ui/intelligent-ui/use-ui-queries": {
997
- "source": "./src/chat/ui/intelligent-ui/use-ui-queries.ts",
998
- "types": "./dist/chat/ui/intelligent-ui/use-ui-queries.d.ts",
999
- "default": "./dist/chat/ui/intelligent-ui/use-ui-queries.js"
1000
- },
1001
- "./chat/ui/intelligent-ui/UiReply": {
1002
- "source": "./src/chat/ui/intelligent-ui/UiReply.tsx",
1003
- "types": "./dist/chat/ui/intelligent-ui/UiReply.d.ts",
1004
- "default": "./dist/chat/ui/intelligent-ui/UiReply.js"
1005
- },
1006
- "./chat/ui/intelligent-ui/reply-parts": {
1007
- "source": "./src/chat/ui/intelligent-ui/reply-parts.ts",
1008
- "types": "./dist/chat/ui/intelligent-ui/reply-parts.d.ts",
1009
- "default": "./dist/chat/ui/intelligent-ui/reply-parts.js"
1010
1005
  }
1011
1006
  },
1012
1007
  "scripts": {
@@ -1016,8 +1011,8 @@
1016
1011
  "test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
1017
1012
  },
1018
1013
  "dependencies": {
1019
- "@adecore/agent-contracts": "0.21.0",
1020
- "@adecore/intelligent-ui": "0.21.0",
1014
+ "@adecore/agent-contracts": "0.21.1",
1015
+ "@adecore/intelligent-ui": "0.21.1",
1021
1016
  "@codemirror/commands": "^6.11.1",
1022
1017
  "@codemirror/language": "^6.12.4",
1023
1018
  "@codemirror/state": "^6.7.6",
@@ -1038,7 +1033,7 @@
1038
1033
  "zustand": "^5.0.15"
1039
1034
  },
1040
1035
  "peerDependencies": {
1041
- "@adecore/ui": "^0.21.0",
1036
+ "@adecore/ui": "^0.21.1",
1042
1037
  "i18next": "^26.4.2",
1043
1038
  "react": "^19.3.0",
1044
1039
  "react-dom": "^19.3.0",