@adecore/agents-react 0.27.4 → 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.
- package/dist/chat/ui/intelligent-ui/registry.js +2 -1
- package/dist/chat/ui/intelligent-ui/renderers/content.d.ts +1 -0
- package/dist/chat/ui/intelligent-ui/renderers/content.js +27 -1
- package/dist/chat/ui/rows/MessageRows.js +2 -2
- package/dist/chat/ui/rows/TaskRow.js +3 -2
- package/dist/chat/ui/rows/WorkRows.js +1 -1
- package/dist/host.d.ts +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/locales/en/agent-chat.d.ts +5 -0
- package/dist/locales/en/agent-chat.js +5 -0
- package/dist/locales/nl/agent-chat.d.ts +5 -0
- package/dist/locales/nl/agent-chat.js +5 -0
- package/package.json +8 -8
- package/src/chat/ui/intelligent-ui/registry.ts +2 -1
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +51 -1
- package/src/chat/ui/rows/MessageRows.tsx +2 -2
- package/src/chat/ui/rows/TaskRow.tsx +12 -9
- package/src/chat/ui/rows/WorkRows.tsx +2 -2
- package/src/host.ts +19 -0
- package/src/index.ts +1 -0
- package/src/locales/en/agent-chat.ts +5 -0
- package/src/locales/nl/agent-chat.ts +5 -0
|
@@ -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 {
|
|
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();
|
|
@@ -185,7 +185,7 @@ export function ThinkingRow({ chatId, item: derived }) {
|
|
|
185
185
|
const preview = useMemo(() => (item.streaming ? '' : thoughtPreview(item.text)), [item.streaming, item.text]);
|
|
186
186
|
const duration = formatElapsedShort((item.endedAt ?? item.createdAt) - item.createdAt);
|
|
187
187
|
useOpenForFind(item.id, 'text', setOpen);
|
|
188
|
-
return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsxs("button", { className: "flex w-full min-w-0 items-center gap-2 text-left text-xs text-text-muted hover:text-text", disabled: item.streaming, "aria-expanded": item.streaming ? undefined : open, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: Brain, size: 12 }) }), item.streaming ? (_jsx("span", { className: "chat-live-text min-w-0 truncate", children: live || t('rows.thinking.live') })) : preview === '' ? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('rows.thinking.thoughtFor', { duration }) }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('shrink-0 transition-transform', open && 'rotate-180') })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 truncate", children: open ? t('rows.thinking.thought') : preview }), _jsx("span", { className: "grow" }), _jsx("span", { className: "shrink-0 text-text-faint tabular-nums", children: duration }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('shrink-0 transition-transform', open && 'rotate-180') })] }))] }), shown && text !== '' && (_jsx("div", { "data-find-field": "text", className: "mt-1 border-l border-border pl-
|
|
188
|
+
return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsxs("button", { className: "flex w-full min-w-0 items-center gap-2 text-left text-xs text-text-muted hover:text-text", disabled: item.streaming, "aria-expanded": item.streaming ? undefined : open, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: Brain, size: 12 }) }), item.streaming ? (_jsx("span", { className: "chat-live-text min-w-0 truncate", children: live || t('rows.thinking.live') })) : preview === '' ? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('rows.thinking.thoughtFor', { duration }) }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('shrink-0 transition-transform', open && 'rotate-180') })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 truncate", children: open ? t('rows.thinking.thought') : preview }), _jsx("span", { className: "grow" }), _jsx("span", { className: "shrink-0 text-text-faint tabular-nums", children: duration }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('shrink-0 transition-transform', open && 'rotate-180') })] }))] }), shown && text !== '' && (_jsx("div", { "data-find-field": "text", className: "mt-1 ml-1.75 border-l border-border pl-4 select-text", children: reveal.active ? (_jsx("div", { className: "text-sm whitespace-pre-wrap text-text-faint", children: _jsx(FadingWords, { text: reveal.text }) })) : (_jsx(Markdown, { text: text, muted: true })) }))] }));
|
|
189
189
|
}
|
|
190
190
|
/*
|
|
191
191
|
* The latest thought of a running turn over the call it led to, as its heading or first sentence,
|
|
@@ -257,7 +257,7 @@ export function ApprovalHistoryRow({ item }) {
|
|
|
257
257
|
if (item.decision === 'pending') {
|
|
258
258
|
return null;
|
|
259
259
|
}
|
|
260
|
-
return (_jsxs("div", { className: "-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-faint", children: [_jsx("span", { className: clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error'), children: DECISION_ICON[item.decision] }), _jsx("span", { className: clsx(item.decision === 'deny' && 'text-status-error'), children: t(`rows.approval.${item.decision}`) }), _jsx("span", { className: "text-text-muted", children: item.toolName }), _jsx("span", { className: "min-w-0 truncate font-mono", children: toolSummaryIsPath(item.toolName, item.input) ? (_jsx(MiddleTruncate, { text: toolSummary(item.toolName, item.input) })) : (toolSummary(item.toolName, item.input)) })] }));
|
|
260
|
+
return (_jsxs("div", { className: "-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-faint", children: [_jsx("span", { className: clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error'), children: DECISION_ICON[item.decision] }), _jsx("span", { className: clsx(item.decision === 'deny' && 'text-status-error'), children: t(`rows.approval.${item.decision}`) }), _jsx("span", { className: "text-text-muted", children: item.toolName }), _jsx("span", { className: "min-w-0 truncate font-mono text-code", children: toolSummaryIsPath(item.toolName, item.input) ? (_jsx(MiddleTruncate, { text: toolSummary(item.toolName, item.input) })) : (toolSummary(item.toolName, item.input)) })] }));
|
|
261
261
|
}
|
|
262
262
|
export function QuestionHistoryRow({ item }) {
|
|
263
263
|
const { t } = useTranslation('agent-chat');
|
|
@@ -10,6 +10,7 @@ import { useChatScope } from '../../../scope.js';
|
|
|
10
10
|
import { useChats } from '../../../state/chats.js';
|
|
11
11
|
import { useProviders } from '../../../state/providers.js';
|
|
12
12
|
import { taskRowStateOf } from '../../subagent-list.js';
|
|
13
|
+
import { ROW_GUTTER } from '../icons.js';
|
|
13
14
|
const DOT = {
|
|
14
15
|
running: 'bg-status-running',
|
|
15
16
|
waiting: 'bg-status-needs-you',
|
|
@@ -34,7 +35,7 @@ function StateLine({ state, task }) {
|
|
|
34
35
|
const { t } = useTranslation('agent-chat');
|
|
35
36
|
const until = state === 'paused' ? task?.paused?.until : undefined;
|
|
36
37
|
const now = useNow(60_000, until === undefined);
|
|
37
|
-
return (_jsx("span", { className: clsx('truncate text-
|
|
38
|
+
return (_jsx("span", { className: clsx('truncate text-2xs', STATE_TONE[state]), children: until === undefined ? t(`rows.task.${state}`) : t('rows.task.pausedUntil', { time: formatMoment(until, now) }) }));
|
|
38
39
|
}
|
|
39
40
|
/*
|
|
40
41
|
* The row of a task given to another node: that agent's mark and model, what the task is about and
|
|
@@ -59,5 +60,5 @@ export function TaskRow({ item, task, open, onPress }) {
|
|
|
59
60
|
const startedAt = task?.createdAt ?? item.startedAt;
|
|
60
61
|
const finishedAt = task === null ? item.finishedAt : (task.settledAt ?? item.finishedAt);
|
|
61
62
|
const settled = state === 'done' || state === 'failed' || state === 'cancelled';
|
|
62
|
-
return (_jsxs("button", { className: "-mx-1 mb-0.5 flex w-[calc(100%+8px)] items-
|
|
63
|
+
return (_jsxs("button", { className: "-mx-1 mb-0.5 flex w-[calc(100%+8px)] items-start gap-2 rounded-md px-1 py-1 text-left hover:bg-surface-hover", onClick: onPress, children: [_jsxs("span", { className: clsx(ROW_GUTTER, 'relative text-text'), children: [provider === undefined ? _jsx(Icon, { icon: Bot, size: 14 }) : _jsx(AgentIcon, { kind: provider, size: 14 }), _jsx("span", { className: clsx('absolute -right-0.75 bottom-0 size-2 rounded-full ring-2 ring-surface', DOT[state]) })] }), _jsxs("span", { className: "flex min-w-0 grow flex-col", children: [_jsx("span", { "data-find-field": "summary", className: "truncate text-xs font-medium text-text", children: model === null ? title : t('rows.task.named', { model, title }) }), _jsx(StateLine, { state: state, task: task })] }), _jsxs("span", { className: "flex h-5 shrink-0 items-center gap-2", children: [(state === 'running' || state === 'waiting') && startedAt > 0 && _jsx(Elapsed, { startedAt: startedAt }), settled && startedAt > 0 && finishedAt !== null && finishedAt >= startedAt && (_jsx("span", { className: "shrink-0 text-xs text-text-faint tabular-nums", children: formatElapsedShort(finishedAt - startedAt) })), _jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90') })] })] }));
|
|
63
64
|
}
|
|
@@ -76,7 +76,7 @@ export function ToggleLine({ icon, label, brand, detail, command, look, path, op
|
|
|
76
76
|
const classes = clsx(
|
|
77
77
|
// Fix the width so long commands reach the truncation inside the negative margins.
|
|
78
78
|
'group/line -mx-1 mb-0.5 flex h-7 w-[calc(100%+8px)] items-center gap-2 rounded-md px-1 text-left text-xs text-text-muted', onToggle !== undefined && 'hover:bg-surface-hover', failed && 'text-status-error', className);
|
|
79
|
-
const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: clsx(ROW_GUTTER, live && 'text-accent'), children: icon }), _jsxs("span", { className: clsx('shrink-0', live && 'chat-live-text'), children: [brand !== undefined && _jsx("span", { className: BRAND, children: brand }), brand !== undefined && label !== '' && ' ', label] }), look !== undefined ? (_jsxs(_Fragment, { children: [look.run.detail !== '' && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-text-faint", children: _jsx(LookDetail, { run: look.run, chatId: look.chatId }) })), _jsx(LookTags, { run: look.run })] })) : (detail && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-text-faint", children: command ? _jsx(ShellCommand, { command: detail }) : path ? _jsx(MiddleTruncate, { text: detail }) : detail }))), _jsx("span", { className: "grow" }), trailing, time !== undefined && _jsx(RowTime, { at: time }), failed && _jsx(Icon, { icon: X, size: 12, className: "shrink-0" }), onToggle !== undefined && (_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90') }))] }));
|
|
79
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: clsx(ROW_GUTTER, live && 'text-accent'), children: icon }), _jsxs("span", { className: clsx('shrink-0', live && 'chat-live-text'), children: [brand !== undefined && _jsx("span", { className: BRAND, children: brand }), brand !== undefined && label !== '' && ' ', label] }), look !== undefined ? (_jsxs(_Fragment, { children: [look.run.detail !== '' && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-code text-text-faint", children: _jsx(LookDetail, { run: look.run, chatId: look.chatId }) })), _jsx(LookTags, { run: look.run })] })) : (detail && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-code text-text-faint", children: command ? _jsx(ShellCommand, { command: detail }) : path ? _jsx(MiddleTruncate, { text: detail }) : detail }))), _jsx("span", { className: "grow" }), trailing, time !== undefined && _jsx(RowTime, { at: time }), failed && _jsx(Icon, { icon: X, size: 12, className: "shrink-0" }), onToggle !== undefined && (_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90') }))] }));
|
|
80
80
|
if (onToggle === undefined) {
|
|
81
81
|
return _jsx("div", { className: classes, children: content });
|
|
82
82
|
}
|
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 @@ 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',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
71
|
-
"@adecore/editor": "0.
|
|
72
|
-
"@adecore/editor-react": "0.
|
|
73
|
-
"@adecore/intelligent-ui": "0.
|
|
74
|
-
"@adecore/merge": "0.
|
|
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.
|
|
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.
|
|
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();
|
|
@@ -381,7 +381,7 @@ export function ThinkingRow({ chatId, item: derived }: { chatId: string; item: C
|
|
|
381
381
|
)}
|
|
382
382
|
</button>
|
|
383
383
|
{shown && text !== '' && (
|
|
384
|
-
<div data-find-field="text" className="mt-1 border-l border-border pl-
|
|
384
|
+
<div data-find-field="text" className="mt-1 ml-1.75 border-l border-border pl-4 select-text">
|
|
385
385
|
{reveal.active ? (
|
|
386
386
|
<div className="text-sm whitespace-pre-wrap text-text-faint">
|
|
387
387
|
<FadingWords text={reveal.text} />
|
|
@@ -535,7 +535,7 @@ export function ApprovalHistoryRow({ item }: { item: ChatApprovalItem }) {
|
|
|
535
535
|
<span className={clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error')}>{DECISION_ICON[item.decision]}</span>
|
|
536
536
|
<span className={clsx(item.decision === 'deny' && 'text-status-error')}>{t(`rows.approval.${item.decision}`)}</span>
|
|
537
537
|
<span className="text-text-muted">{item.toolName}</span>
|
|
538
|
-
<span className="min-w-0 truncate font-mono">
|
|
538
|
+
<span className="min-w-0 truncate font-mono text-code">
|
|
539
539
|
{toolSummaryIsPath(item.toolName, item.input) ? (
|
|
540
540
|
<MiddleTruncate text={toolSummary(item.toolName, item.input)} />
|
|
541
541
|
) : (
|
|
@@ -11,6 +11,7 @@ import { useChatScope } from '../../../scope';
|
|
|
11
11
|
import { useChats } from '../../../state/chats';
|
|
12
12
|
import { useProviders } from '../../../state/providers';
|
|
13
13
|
import { taskRowStateOf, type TaskRowState } from '../../subagent-list';
|
|
14
|
+
import { ROW_GUTTER } from '../icons';
|
|
14
15
|
|
|
15
16
|
const DOT: Record<TaskRowState, string> = {
|
|
16
17
|
running: 'bg-status-running',
|
|
@@ -40,7 +41,7 @@ function StateLine({ state, task }: { state: TaskRowState; task: SubagentTask |
|
|
|
40
41
|
const until = state === 'paused' ? task?.paused?.until : undefined;
|
|
41
42
|
const now = useNow(60_000, until === undefined);
|
|
42
43
|
return (
|
|
43
|
-
<span className={clsx('truncate text-
|
|
44
|
+
<span className={clsx('truncate text-2xs', STATE_TONE[state])}>
|
|
44
45
|
{until === undefined ? t(`rows.task.${state}`) : t('rows.task.pausedUntil', { time: formatMoment(until, now) })}
|
|
45
46
|
</span>
|
|
46
47
|
);
|
|
@@ -70,10 +71,10 @@ export function TaskRow({ item, task, open, onPress }: { item: ChatSubagentItem;
|
|
|
70
71
|
const finishedAt = task === null ? item.finishedAt : (task.settledAt ?? item.finishedAt);
|
|
71
72
|
const settled = state === 'done' || state === 'failed' || state === 'cancelled';
|
|
72
73
|
return (
|
|
73
|
-
<button className="-mx-1 mb-0.5 flex w-[calc(100%+8px)] items-
|
|
74
|
-
<span className=
|
|
74
|
+
<button className="-mx-1 mb-0.5 flex w-[calc(100%+8px)] items-start gap-2 rounded-md px-1 py-1 text-left hover:bg-surface-hover" onClick={onPress}>
|
|
75
|
+
<span className={clsx(ROW_GUTTER, 'relative text-text')}>
|
|
75
76
|
{provider === undefined ? <Icon icon={Bot} size={14} /> : <AgentIcon kind={provider} size={14} />}
|
|
76
|
-
<span className={clsx('absolute -right-0.
|
|
77
|
+
<span className={clsx('absolute -right-0.75 bottom-0 size-2 rounded-full ring-2 ring-surface', DOT[state])} />
|
|
77
78
|
</span>
|
|
78
79
|
<span className="flex min-w-0 grow flex-col">
|
|
79
80
|
<span data-find-field="summary" className="truncate text-xs font-medium text-text">
|
|
@@ -81,11 +82,13 @@ export function TaskRow({ item, task, open, onPress }: { item: ChatSubagentItem;
|
|
|
81
82
|
</span>
|
|
82
83
|
<StateLine state={state} task={task} />
|
|
83
84
|
</span>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
<span className="flex h-5 shrink-0 items-center gap-2">
|
|
86
|
+
{(state === 'running' || state === 'waiting') && startedAt > 0 && <Elapsed startedAt={startedAt} />}
|
|
87
|
+
{settled && startedAt > 0 && finishedAt !== null && finishedAt >= startedAt && (
|
|
88
|
+
<span className="shrink-0 text-xs text-text-faint tabular-nums">{formatElapsedShort(finishedAt - startedAt)}</span>
|
|
89
|
+
)}
|
|
90
|
+
<Icon icon={ChevronRight} size={12} className={clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90')} />
|
|
91
|
+
</span>
|
|
89
92
|
</button>
|
|
90
93
|
);
|
|
91
94
|
}
|
|
@@ -162,7 +162,7 @@ export function ToggleLine({
|
|
|
162
162
|
{look !== undefined ? (
|
|
163
163
|
<>
|
|
164
164
|
{look.run.detail !== '' && (
|
|
165
|
-
<span data-find-field="summary" className="min-w-0 truncate font-mono text-text-faint">
|
|
165
|
+
<span data-find-field="summary" className="min-w-0 truncate font-mono text-code text-text-faint">
|
|
166
166
|
<LookDetail run={look.run} chatId={look.chatId} />
|
|
167
167
|
</span>
|
|
168
168
|
)}
|
|
@@ -170,7 +170,7 @@ export function ToggleLine({
|
|
|
170
170
|
</>
|
|
171
171
|
) : (
|
|
172
172
|
detail && (
|
|
173
|
-
<span data-find-field="summary" className="min-w-0 truncate font-mono text-text-faint">
|
|
173
|
+
<span data-find-field="summary" className="min-w-0 truncate font-mono text-code text-text-faint">
|
|
174
174
|
{command ? <ShellCommand command={detail} /> : path ? <MiddleTruncate text={detail} /> : detail}
|
|
175
175
|
</span>
|
|
176
176
|
)
|
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
|
@@ -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',
|