@adecore/agents-react 0.27.5 → 0.28.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.
@@ -1,6 +1,6 @@
1
1
  import { ChartRenderer } from './renderers/chart.js';
2
2
  import { ChoiceRenderer, ChoicesRenderer } from './renderers/choices.js';
3
- import { CodeBlockRenderer, CompareRenderer, GalleryRenderer, ImageRenderer, SourceRenderer, SourcesRenderer } from './renderers/content.js';
3
+ import { CodeBlockRenderer, CompareRenderer, GalleryRenderer, ImageRenderer, ModelRenderer, SourceRenderer, SourcesRenderer } from './renderers/content.js';
4
4
  import { EntityListRenderer, EntryRenderer, StatRenderer, StatsRenderer, TableRenderer } from './renderers/data.js';
5
5
  import { ButtonRenderer, ChecklistRenderer, ItemRenderer, SegmentedRenderer, SelectRenderer, SliderRenderer, SwitchRenderer, TextFieldRenderer } from './renderers/inputs.js';
6
6
  import { CommitRenderer, DiffRenderer, FileRenderer, NodeRenderer } from './renderers/links.js';
@@ -37,6 +37,7 @@ export const UI_RENDERERS = {
37
37
  Image: ImageRenderer,
38
38
  Gallery: GalleryRenderer,
39
39
  Compare: CompareRenderer,
40
+ Model: ModelRenderer,
40
41
  Map: MapRenderer,
41
42
  Place: null,
42
43
  Sources: SourcesRenderer,
@@ -4,6 +4,7 @@ export declare function CodeBlockRenderer({ node }: UiRendererProps<UiProps<'Cod
4
4
  export declare function ImageRenderer({ node, children, context }: UiRendererProps<UiProps<'Image'>>): import("react").JSX.Element;
5
5
  export declare function GalleryRenderer({ node, children }: UiRendererProps<UiProps<'Gallery'>>): import("react").JSX.Element;
6
6
  export declare function CompareRenderer({ node, children, context }: UiRendererProps<UiProps<'Compare'>>): import("react").JSX.Element;
7
+ export declare function ModelRenderer({ node, children, context }: UiRendererProps<UiProps<'Model'>>): import("react").JSX.Element;
7
8
  export declare function SourcesRenderer({ node, children }: UiRendererProps<UiProps<'Sources'>>): import("react").JSX.Element;
8
9
  export interface UiSourceCitationProps {
9
10
  number: number;
@@ -2,7 +2,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import clsx from 'clsx';
3
3
  import { createContext, useContext, useId, useState } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { Pill, PreviewCard, Tooltip, Wipe } from '@adecore/ui';
5
+ import { Box } from 'lucide-react';
6
+ import { ErrorBoundary, Icon, Pill, PreviewCard, Tooltip, Wipe } from '@adecore/ui';
6
7
  import { formatNumber } from '@adecore/ui/format';
7
8
  import { useChatHost } from '../../../../host.js';
8
9
  import { CodeBlock } from '../../CodeBlock.js';
@@ -95,6 +96,31 @@ export function CompareRenderer({ node, children, context }) {
95
96
  function CompareChip({ side, label }) {
96
97
  return (_jsx("span", { className: clsx('pointer-events-none absolute top-2 z-20 max-w-2/5 truncate rounded-md bg-surface-raised', side === 'left' ? 'left-2' : 'right-2'), children: _jsx(Pill, { shape: "tag", size: "sm", tone: "muted", children: label }) }));
97
98
  }
99
+ /*
100
+ * The host draws the model, since the chat knows no 3D format. The frame has a fixed height in the reply column,
101
+ * so a view that loads late never moves the thread. Without a view of the host, or without the bytes, the model
102
+ * stays a quiet row that names it.
103
+ */
104
+ export function ModelRenderer({ node, children, context }) {
105
+ const { t } = useTranslation('agent-chat');
106
+ const host = useChatHost();
107
+ const { attachment, alt } = node.props;
108
+ const source = host.attachments.useUrl(context.scopeId, context.chatId, attachment);
109
+ const caption = uiNodeLabel(node);
110
+ const label = alt ?? (caption === '' ? t('blocks.model.label') : caption);
111
+ const View = host.ModelView;
112
+ let body;
113
+ if (View !== undefined && source.url !== null) {
114
+ body = (_jsx("div", { role: "group", "aria-label": label, className: "relative h-80 w-full overflow-hidden rounded-lg border border-border bg-surface-sunken", children: _jsx(ErrorBoundary, { label: t('blocks.model.crashed'), resetKeys: [attachment, source.url], compact: true, children: _jsx(View, { scopeId: context.scopeId, chatId: context.chatId, attachmentId: attachment, url: source.url, label: label, alt: alt, caption: caption }) }) }));
115
+ }
116
+ else if (View !== undefined && source.failure === null) {
117
+ body = _jsx("div", { "aria-busy": "true", "aria-label": label, className: "h-80 w-full rounded-lg border border-border bg-surface-sunken" });
118
+ }
119
+ else {
120
+ body = (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-2 rounded-lg border border-border px-3 py-2 text-xs", children: [_jsx(Icon, { icon: Box, size: 14, className: "shrink-0 text-text-muted" }), _jsx("span", { className: "min-w-0 truncate text-text", children: alt ?? attachment }), _jsx("span", { className: "ml-auto shrink-0 text-text-faint", children: source.failure === null ? t('blocks.model.label') : t('blocks.model.unavailable') })] }));
121
+ }
122
+ return (_jsxs("figure", { className: "flex flex-col items-start gap-1.5 px-2", children: [body, caption !== '' && _jsx("figcaption", { className: "text-xs text-text-muted", children: children })] }));
123
+ }
98
124
  export function SourcesRenderer({ node, children }) {
99
125
  const { t } = useTranslation('agent-chat');
100
126
  const headingId = useId();
package/dist/host.d.ts CHANGED
@@ -162,6 +162,15 @@ export type UiMapSource = {
162
162
  state: 'off';
163
163
  reason: 'disabled' | 'signed-out' | 'unavailable';
164
164
  };
165
+ export interface UiModelViewProps {
166
+ scopeId: string;
167
+ chatId: string;
168
+ attachmentId: string;
169
+ url: string;
170
+ label: string;
171
+ alt?: string;
172
+ caption: string;
173
+ }
165
174
  export interface ChatHost {
166
175
  intelligentUi?: {
167
176
  subscribe?(scopeId: string, chatId: string, changed: () => void): () => void;
@@ -195,6 +204,7 @@ export interface ChatHost {
195
204
  ReadImage: ComponentType<{
196
205
  path: string;
197
206
  }> | null;
207
+ ModelView?: ComponentType<UiModelViewProps>;
198
208
  MarkdownImage?: ComponentType<{
199
209
  path: string;
200
210
  alt: string;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type ChatActions, type FileRef, type ShellCodeBlockContext, type ChatPlace, type SubagentTask, type TimelineFindOptions, type TimelineFind, type ComposerDictationProps, type ComposerSlotProps, type SubagentSlotProps, type ThreadCard, type VisualHost, type ReplyAuthor, type ChatHost, type UiMapSource, type CommandLook, type CommandLookMatch, type CommandName, ChatHostProvider, useChatHost, type AccentChoice, type ChatToast, type TextareaProps, type ResourceUrl } from './host.js';
1
+ export { type ChatActions, type FileRef, type ShellCodeBlockContext, type ChatPlace, type SubagentTask, type TimelineFindOptions, type TimelineFind, type ComposerDictationProps, type ComposerSlotProps, type SubagentSlotProps, type ThreadCard, type VisualHost, type ReplyAuthor, type ChatHost, type UiMapSource, type UiModelViewProps, type CommandLook, type CommandLookMatch, type CommandName, ChatHostProvider, useChatHost, type AccentChoice, type ChatToast, type TextareaProps, type ResourceUrl } from './host.js';
2
2
  export { type ChatScope, ChatScopeContext, useChatScope } from './scope.js';
3
3
  export { type ChatRequestMap, type ChatEventMap, type ChatTransportStatus, type ChatTransport, ChatTransportError, errorCode, isConnectionError } from './transport.js';
4
4
  export { portTransport, type PortTransport } from './port-transport.js';
@@ -621,6 +621,11 @@ declare const _default: {
621
621
  label: string;
622
622
  unavailable: string;
623
623
  };
624
+ model: {
625
+ label: string;
626
+ unavailable: string;
627
+ crashed: string;
628
+ };
624
629
  sources: string;
625
630
  citation: string;
626
631
  outsideProject: string;
@@ -621,6 +621,11 @@ export default {
621
621
  label: 'Image',
622
622
  unavailable: 'The image is not available'
623
623
  },
624
+ model: {
625
+ label: '3D model',
626
+ unavailable: 'The model is not available',
627
+ crashed: 'This model failed to render'
628
+ },
624
629
  sources: 'Sources',
625
630
  citation: 'Source {{number}}: {{title}}',
626
631
  outsideProject: 'Outside the project, shown as text',
@@ -621,6 +621,11 @@ declare const _default: {
621
621
  label: string;
622
622
  unavailable: string;
623
623
  };
624
+ model: {
625
+ label: string;
626
+ unavailable: string;
627
+ crashed: string;
628
+ };
624
629
  sources: string;
625
630
  citation: string;
626
631
  outsideProject: string;
@@ -621,6 +621,11 @@ export default {
621
621
  label: 'Afbeelding',
622
622
  unavailable: 'De afbeelding is niet beschikbaar'
623
623
  },
624
+ model: {
625
+ label: '3D-model',
626
+ unavailable: 'Het model is niet beschikbaar',
627
+ crashed: 'Dit model kon niet worden getekend'
628
+ },
624
629
  sources: 'Bronnen',
625
630
  citation: 'Bron {{number}}: {{title}}',
626
631
  outsideProject: 'Buiten het project, als tekst getoond',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents-react",
3
- "version": "0.27.5",
3
+ "version": "0.28.0",
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.27.5",
71
- "@adecore/editor": "0.27.5",
72
- "@adecore/editor-react": "0.27.5",
73
- "@adecore/intelligent-ui": "0.27.5",
74
- "@adecore/merge": "0.27.5",
70
+ "@adecore/agent-contracts": "0.28.0",
71
+ "@adecore/editor": "0.28.0",
72
+ "@adecore/editor-react": "0.28.0",
73
+ "@adecore/intelligent-ui": "0.28.0",
74
+ "@adecore/merge": "0.28.0",
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.27.5",
97
+ "@adecore/ui": "^0.28.0",
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.27.5",
104
+ "@adecore/ui": "0.28.0",
105
105
  "@types/bun": "^1.4.2",
106
106
  "@types/react": "^19.3.0",
107
107
  "@types/react-dom": "^19.3.0",
@@ -1,7 +1,7 @@
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, CompareRenderer, GalleryRenderer, ImageRenderer, SourceRenderer, SourcesRenderer } from './renderers/content';
4
+ import { CodeBlockRenderer, CompareRenderer, GalleryRenderer, ImageRenderer, ModelRenderer, SourceRenderer, SourcesRenderer } from './renderers/content';
5
5
  import { EntityListRenderer, EntryRenderer, StatRenderer, StatsRenderer, TableRenderer } from './renderers/data';
6
6
  import {
7
7
  ButtonRenderer,
@@ -57,6 +57,7 @@ export const UI_RENDERERS: UiRenderers = {
57
57
  Image: ImageRenderer,
58
58
  Gallery: GalleryRenderer,
59
59
  Compare: CompareRenderer,
60
+ Model: ModelRenderer,
60
61
  Map: MapRenderer,
61
62
  Place: null,
62
63
  Sources: SourcesRenderer,
@@ -1,8 +1,9 @@
1
1
  import clsx from 'clsx';
2
2
  import { createContext, useContext, useId, useState, type ReactNode } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
+ import { Box } from 'lucide-react';
4
5
  import type { UiProps } from '@adecore/intelligent-ui';
5
- import { Pill, PreviewCard, Tooltip, Wipe } from '@adecore/ui';
6
+ import { ErrorBoundary, Icon, Pill, PreviewCard, Tooltip, Wipe } from '@adecore/ui';
6
7
  import { formatNumber } from '@adecore/ui/format';
7
8
  import { useChatHost } from '../../../../host';
8
9
  import { CodeBlock } from '../../CodeBlock';
@@ -187,6 +188,55 @@ function CompareChip({ side, label }: { side: 'left' | 'right'; label: string })
187
188
  );
188
189
  }
189
190
 
191
+ /*
192
+ * The host draws the model, since the chat knows no 3D format. The frame has a fixed height in the reply column,
193
+ * so a view that loads late never moves the thread. Without a view of the host, or without the bytes, the model
194
+ * stays a quiet row that names it.
195
+ */
196
+ export function ModelRenderer({ node, children, context }: UiRendererProps<UiProps<'Model'>>) {
197
+ const { t } = useTranslation('agent-chat');
198
+ const host = useChatHost();
199
+ const { attachment, alt } = node.props;
200
+ const source = host.attachments.useUrl(context.scopeId, context.chatId, attachment);
201
+ const caption = uiNodeLabel(node);
202
+ const label = alt ?? (caption === '' ? t('blocks.model.label') : caption);
203
+ const View = host.ModelView;
204
+ let body: ReactNode;
205
+ if (View !== undefined && source.url !== null) {
206
+ body = (
207
+ <div role="group" aria-label={label} className="relative h-80 w-full overflow-hidden rounded-lg border border-border bg-surface-sunken">
208
+ <ErrorBoundary label={t('blocks.model.crashed')} resetKeys={[attachment, source.url]} compact>
209
+ <View
210
+ scopeId={context.scopeId}
211
+ chatId={context.chatId}
212
+ attachmentId={attachment}
213
+ url={source.url}
214
+ label={label}
215
+ alt={alt}
216
+ caption={caption}
217
+ />
218
+ </ErrorBoundary>
219
+ </div>
220
+ );
221
+ } else if (View !== undefined && source.failure === null) {
222
+ body = <div aria-busy="true" aria-label={label} className="h-80 w-full rounded-lg border border-border bg-surface-sunken" />;
223
+ } else {
224
+ body = (
225
+ <div className="flex w-full min-w-0 items-center gap-2 rounded-lg border border-border px-3 py-2 text-xs">
226
+ <Icon icon={Box} size={14} className="shrink-0 text-text-muted" />
227
+ <span className="min-w-0 truncate text-text">{alt ?? attachment}</span>
228
+ <span className="ml-auto shrink-0 text-text-faint">{source.failure === null ? t('blocks.model.label') : t('blocks.model.unavailable')}</span>
229
+ </div>
230
+ );
231
+ }
232
+ return (
233
+ <figure className="flex flex-col items-start gap-1.5 px-2">
234
+ {body}
235
+ {caption !== '' && <figcaption className="text-xs text-text-muted">{children}</figcaption>}
236
+ </figure>
237
+ );
238
+ }
239
+
190
240
  export function SourcesRenderer({ node, children }: UiRendererProps<UiProps<'Sources'>>) {
191
241
  const { t } = useTranslation('agent-chat');
192
242
  const headingId = useId();
package/src/host.ts CHANGED
@@ -260,6 +260,23 @@ export type UiMapSource =
260
260
  }
261
261
  | { state: 'off'; reason: 'disabled' | 'signed-out' | 'unavailable' };
262
262
 
263
+ /*
264
+ * What the host's view of a Model block gets. It is drawn only once the attachment has a URL, inside a frame
265
+ * of fixed height that the block sizes and captions itself, so the view fills its parent and draws no caption.
266
+ */
267
+ export interface UiModelViewProps {
268
+ scopeId: string;
269
+ chatId: string;
270
+ attachmentId: string;
271
+ /* The attachment's bytes, as `attachments.useUrl` resolved them. */
272
+ url: string;
273
+ /* What the model shows, for a screen reader: the block's alt, its caption, or a word for a 3D model. */
274
+ label: string;
275
+ alt?: string;
276
+ /* The caption as plain text, empty without one. */
277
+ caption: string;
278
+ }
279
+
263
280
  /*
264
281
  * What the app around the chat decides and the chat cannot, handed to the views by a `ChatHostProvider`.
265
282
  * Whatever an app leaves out keeps the default here, which is the chat without that part. One host may
@@ -313,6 +330,8 @@ export interface ChatHost {
313
330
  };
314
331
  /* The image a tool read, drawn under its row; null draws none. */
315
332
  ReadImage: ComponentType<{ path: string }> | null;
333
+ /* The 3D view of a Model block; left out, a model shows as a row with its name and caption. */
334
+ ModelView?: ComponentType<UiModelViewProps>;
316
335
  /* An image a reply's markdown names by a path on the host's machine, absolute or under the chat's cwd; left out, the `<img>` stays as written. */
317
336
  MarkdownImage?: ComponentType<{ path: string; alt: string }>;
318
337
  code: {
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export {
15
15
  type ReplyAuthor,
16
16
  type ChatHost,
17
17
  type UiMapSource,
18
+ type UiModelViewProps,
18
19
  type CommandLook,
19
20
  type CommandLookMatch,
20
21
  type CommandName,
@@ -621,6 +621,11 @@ export default {
621
621
  label: 'Image',
622
622
  unavailable: 'The image is not available'
623
623
  },
624
+ model: {
625
+ label: '3D model',
626
+ unavailable: 'The model is not available',
627
+ crashed: 'This model failed to render'
628
+ },
624
629
  sources: 'Sources',
625
630
  citation: 'Source {{number}}: {{title}}',
626
631
  outsideProject: 'Outside the project, shown as text',
@@ -621,6 +621,11 @@ export default {
621
621
  label: 'Afbeelding',
622
622
  unavailable: 'De afbeelding is niet beschikbaar'
623
623
  },
624
+ model: {
625
+ label: '3D-model',
626
+ unavailable: 'Het model is niet beschikbaar',
627
+ crashed: 'Dit model kon niet worden getekend'
628
+ },
624
629
  sources: 'Bronnen',
625
630
  citation: 'Bron {{number}}: {{title}}',
626
631
  outsideProject: 'Buiten het project, als tekst getoond',