@adecore/agents-react 0.17.0-beta.1 → 0.17.0-beta.2
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/LICENSE +1 -1
- package/README.md +44 -36
- package/dist/chat/logic/ansi.js +2 -2
- package/dist/chat/ui/Scrubber.js +1 -1
- package/dist/chat/ui/UnifiedDiff.js +2 -2
- package/dist/chat/ui/rows/WorkRows.js +4 -4
- package/dist/theme.css +17 -1
- package/package.json +3 -3
- package/src/chat/logic/ansi.ts +2 -2
- package/src/chat/ui/Scrubber.tsx +1 -4
- package/src/chat/ui/UnifiedDiff.tsx +2 -2
- package/src/chat/ui/rows/WorkRows.tsx +6 -6
- package/src/theme.css +17 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,53 +1,61 @@
|
|
|
1
1
|
# @adecore/agents-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@adecore/agents-react)
|
|
4
|
+
[](https://adecore.dev/agents-react/)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
React views for agent chats: the thread, the composer, approvals and questions, the agent CLIs and their accounts, and what they cost. They talk to a host of chats, such as [`@adecore/agents`](https://adecore.dev/agents/), over the requests of [`@adecore/agent-contracts`](https://adecore.dev/agent-contracts/). Built on React 19, [`@adecore/ui`](https://adecore.dev/ui/) and Tailwind 4.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { ChatClient } from '@adecore/agents-react/chat/chat-client';
|
|
11
|
-
import { portTransport } from '@adecore/agents-react/port-transport';
|
|
8
|
+
**[Documentation with live demos](https://adecore.dev/agents-react/)**
|
|
9
|
+
|
|
10
|
+
## Install
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
```sh
|
|
13
|
+
bun add @adecore/agents-react @adecore/agent-contracts
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
`@adecore/ui`, React 19, `react-dom`, `i18next` and `react-i18next` are peer dependencies.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Set up
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Import the theme after the one of `@adecore/ui`, let Tailwind scan the package, and load the typography plugin. The `@source` path is relative to the CSS file.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- [Approvals and tasks](../../docs/agents-react/approvals-tasks.md)
|
|
30
|
-
- [Accounts and models](../../docs/agents-react/accounts-models.md)
|
|
31
|
-
- [Usage](../../docs/agents-react/usage.md)
|
|
32
|
-
- [Persistence](../../docs/agents-react/persistence.md)
|
|
33
|
-
- [CSS, theme, and translations](../../docs/agents-react/styling.md)
|
|
34
|
-
- [Testing](../../docs/agents-react/testing.md)
|
|
35
|
-
- [Migration](../../docs/agents-react/migration.md)
|
|
36
|
-
- [Troubleshooting](../../docs/agents-react/troubleshooting.md)
|
|
37
|
-
- [Runtime reference](../../docs/agents-react/reference-runtime.md)
|
|
38
|
-
- [Chat reference](../../docs/agents-react/reference-chat.md)
|
|
39
|
-
- [Settings reference](../../docs/agents-react/reference-settings.md)
|
|
22
|
+
```css
|
|
23
|
+
@import "tailwindcss";
|
|
24
|
+
@import "@adecore/ui/theme.css";
|
|
25
|
+
@import "@adecore/agents-react/theme.css";
|
|
26
|
+
@plugin "@tailwindcss/typography";
|
|
27
|
+
@source "../node_modules/@adecore/agents-react/dist";
|
|
28
|
+
```
|
|
40
29
|
|
|
41
|
-
|
|
30
|
+
Add the words of `AGENTS_LOCALES` to your i18next, call `setChatHost` once, and render the views in a `ChatScopeContext`:
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
```tsx
|
|
33
|
+
import { Composer } from '@adecore/agents-react/chat/ui/Composer';
|
|
34
|
+
import { Timeline } from '@adecore/agents-react/chat/ui/Timeline';
|
|
35
|
+
import { ChatScopeContext } from '@adecore/agents-react/scope';
|
|
36
|
+
|
|
37
|
+
<ChatScopeContext.Provider value={scope}>
|
|
38
|
+
<Timeline chatId={chatId} composer={<Composer chatId={chatId} info={info} {...composerProps} />} />
|
|
39
|
+
</ChatScopeContext.Provider>;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
[Getting started](https://adecore.dev/agents-react/guide/getting-started) covers the words, the host, the scope and its transport.
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
Every module has its own subpath, `@adecore/agents-react/<path>`; there is no root import.
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
## Documentation
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
| Page | What it covers |
|
|
49
|
+
|---|---|
|
|
50
|
+
| [Getting started](https://adecore.dev/agents-react/guide/getting-started) | Install, CSS, words, the host and a first chat |
|
|
51
|
+
| [Host adapters](https://adecore.dev/agents-react/guide/host) | Everything `setChatHost` takes |
|
|
52
|
+
| [Chat client and state](https://adecore.dev/agents-react/guide/chat-client) | The transport, `ChatClient` and the stores |
|
|
53
|
+
| [Persistence](https://adecore.dev/agents-react/guide/persistence) | Drafts, preferences and their storage keys |
|
|
54
|
+
| [Chat](https://adecore.dev/agents-react/chat/timeline) | The timeline, the composer, prompts and their parts, each with a live demo |
|
|
55
|
+
| [Providers](https://adecore.dev/agents-react/providers/providers-pane) | The settings of the CLIs and their accounts |
|
|
56
|
+
| [Usage](https://adecore.dev/agents-react/usage/usage-page) | Cost, charts and plan limits |
|
|
57
|
+
| [Modules](https://adecore.dev/agents-react/reference) | Every subpath and its exports |
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
## License
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
FSL-1.1-MIT. See [LICENSE](./LICENSE).
|
package/dist/chat/logic/ansi.js
CHANGED
|
@@ -125,8 +125,8 @@ function applyRendition(state, parameters) {
|
|
|
125
125
|
}
|
|
126
126
|
function styleOf(state) {
|
|
127
127
|
const style = {};
|
|
128
|
-
const color = state.inverse ? (state.background ?? 'var(--
|
|
129
|
-
const background = state.inverse ? (state.foreground ?? 'var(--
|
|
128
|
+
const color = state.inverse ? (state.background ?? 'var(--chat-code-bg)') : state.foreground;
|
|
129
|
+
const background = state.inverse ? (state.foreground ?? 'var(--chat-output)') : state.background;
|
|
130
130
|
if (color !== undefined || state.dim) {
|
|
131
131
|
style.color = state.dim ? `color-mix(in srgb, ${color ?? 'currentColor'} 65%, transparent)` : color;
|
|
132
132
|
}
|
package/dist/chat/ui/Scrubber.js
CHANGED
|
@@ -112,7 +112,7 @@ export const Scrubber = memo(function Scrubber({ ticks, firstInView, lastInView,
|
|
|
112
112
|
const distance = pointing && hoveredSlot !== null ? Math.abs(index - hoveredSlot) : null;
|
|
113
113
|
// A bookmark is a place to find again from anywhere, so it never dims with the rest.
|
|
114
114
|
const bright = slot.marked || slotInView(slot, inView) || distance === 0;
|
|
115
|
-
return (_jsxs(Fragment, { children: [_jsx("span", { "aria-hidden": true, className: clsx('absolute left-1 rounded-full transition-[width,opacity] duration-100 ease-out motion-reduce:transition-none', slot.marked ? 'bg-accent' : slot.kind === 'wake' ? 'bg-text-muted' : 'bg-text', bright ? 'opacity-100' : 'opacity-30'), style: { top: slot.y, height: TICK_HEIGHT_PX, width: tickWidth(slot.kind, distance) } }), slot.found && (_jsx("span", { "aria-hidden": true, className: clsx('absolute right-0 rounded-full bg-find
|
|
115
|
+
return (_jsxs(Fragment, { children: [_jsx("span", { "aria-hidden": true, className: clsx('absolute left-1 rounded-full transition-[width,opacity] duration-100 ease-out motion-reduce:transition-none', slot.marked ? 'bg-accent' : slot.kind === 'wake' ? 'bg-text-muted' : 'bg-text', bright ? 'opacity-100' : 'opacity-30'), style: { top: slot.y, height: TICK_HEIGHT_PX, width: tickWidth(slot.kind, distance) } }), slot.found && (_jsx("span", { "aria-hidden": true, className: clsx('absolute right-0 rounded-full bg-chat-find', index === currentSlot ? 'opacity-100' : 'opacity-50'), style: { top: slot.y, height: TICK_HEIGHT_PX, width: FOUND_WIDTH_PX } }))] }, slot.first));
|
|
116
116
|
}) }), _jsx(PreviewCard.Popup, { anchor: anchor, side: "right", align: "center", sideOffset: 4, className: "w-72", children: hoveredTick !== null && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "px-2.5 pt-1.5 pb-2", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3 text-xs text-text-faint", children: [hoveredTick.bookmark === null ? (_jsx("span", { children: hoveredTick.kind === 'person' ? t('rows.user.heading') : t('scrubber.tasks') })) : (_jsxs("span", { className: "flex min-w-0 items-center gap-1 self-center text-text-muted", children: [_jsx(Icon, { icon: Bookmark, size: 12, className: "shrink-0 text-accent" }), _jsx("span", { className: "truncate", children: hoveredTick.bookmark.name ?? t('bookmarks.unnamed') })] })), _jsx("time", { className: "tabular-nums", children: timeOf(hoveredTick.createdAt) })] }), _jsx("p", { className: "mt-1 line-clamp-4 text-sm break-words whitespace-pre-line text-text", children: hoveredTick.text.slice(0, PREVIEW_CHARS) || t('pickers.stash.noText') })] }), actions.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Menu.Separator, {}), actions.map((action) => (_jsxs("button", { type: "button", className: "menu-item w-full hover:bg-surface-hover", onClick: () => action.run(hoveredTick, chat), children: [_jsx(Icon, { icon: action.icon, size: 14 }), " ", t(action.label)] }, action.label)))] }))] })) })] }) }), _jsxs(ContextMenu.Popup, { children: [menuTick !== null &&
|
|
117
117
|
actionsFor(menuTick).map((action) => (_jsxs(ContextMenu.Item, { onClick: () => action.run(menuTick, chat), children: [_jsx(Icon, { icon: action.icon, size: 14 }), " ", t(action.label)] }, action.label))), menuTick !== null && actionsFor(menuTick).length > 0 && menuMarkable !== null && _jsx(ContextMenu.Separator, {}), chat !== null && menuIndex !== null && menuMarkable !== null && (_jsx(BookmarkMenuItems, { chatId: chat.chatId, itemId: menuMarkable, onName: () => onPick(menuIndex) }))] })] }));
|
|
118
118
|
});
|
|
@@ -32,10 +32,10 @@ pre > [data-code] { flex-grow: 1; align-self: stretch; align-content: start; }
|
|
|
32
32
|
`;
|
|
33
33
|
function lineClass(line) {
|
|
34
34
|
if (line.startsWith('+')) {
|
|
35
|
-
return 'text-
|
|
35
|
+
return 'text-chat-added';
|
|
36
36
|
}
|
|
37
37
|
if (line.startsWith('-')) {
|
|
38
|
-
return 'text-
|
|
38
|
+
return 'text-chat-removed';
|
|
39
39
|
}
|
|
40
40
|
return 'text-text-muted';
|
|
41
41
|
}
|
|
@@ -34,7 +34,7 @@ function ToolBody({ tool }) {
|
|
|
34
34
|
const { t } = useTranslation('agent-chat');
|
|
35
35
|
const patches = unifiedChanges(tool);
|
|
36
36
|
const changes = patches.length > 0 ? [] : fileChanges(tool.name, tool.input);
|
|
37
|
-
return (_jsxs("div", { className: "mt-1.5 mb-2 ml-6 overflow-hidden rounded-md border border-border bg-surface-raised", children: [patches.length > 0 ? (_jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: patches.map((change, index) => (_jsx(UnifiedDiff, { change: change }, index))) })) : changes.length > 0 ? (_jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: changes.map((change, index) => (_jsx(EditDiff, { change: change }, index))) })) : (_jsx("pre", { className: "max-h-64 overflow-auto px-3 py-2 font-mono text-code text-text-muted select-text", children: clip(JSON.stringify(tool.input, null, 2) ?? '') })), tool.output !== null && tool.output !== '' && patches.length === 0 && (_jsx("pre", { "data-find-field": "output", className: clsx('max-h-64 overflow-auto whitespace-pre-wrap border-t border-border px-3 py-2 font-mono text-code select-text', tool.state === 'error' ? 'text-
|
|
37
|
+
return (_jsxs("div", { className: "mt-1.5 mb-2 ml-6 overflow-hidden rounded-md border border-border bg-surface-raised", children: [patches.length > 0 ? (_jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: patches.map((change, index) => (_jsx(UnifiedDiff, { change: change }, index))) })) : changes.length > 0 ? (_jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: changes.map((change, index) => (_jsx(EditDiff, { change: change }, index))) })) : (_jsx("pre", { className: "max-h-64 overflow-auto px-3 py-2 font-mono text-code text-text-muted select-text", children: clip(JSON.stringify(tool.input, null, 2) ?? '') })), tool.output !== null && tool.output !== '' && patches.length === 0 && (_jsx("pre", { "data-find-field": "output", className: clsx('max-h-64 overflow-auto whitespace-pre-wrap border-t border-border px-3 py-2 font-mono text-code select-text', tool.state === 'error' ? 'text-chat-removed' : 'text-chat-output'), children: _jsx(AnsiOutput, { text: tool.output, limit: OUTPUT_LIMIT }) }))] }));
|
|
38
38
|
}
|
|
39
39
|
/* One settled tool call: a line, and its input and output behind it. An image the call looked at
|
|
40
40
|
is drawn under the line, because a picture says more about that read than its path does. */
|
|
@@ -56,7 +56,7 @@ export function WorkLiveRow({ chatId, tool: derived }) {
|
|
|
56
56
|
const [open, setOpen] = useState(false);
|
|
57
57
|
const tool = useCurrentItem(chatId, derived);
|
|
58
58
|
const tail = liveOutput(tool);
|
|
59
|
-
return (_jsxs("div", { children: [_jsx(ToggleLine, { icon: toolIcon(tool.name), label: tool.name, detail: toolSummary(tool.name, tool.input) || tool.progress?.description || '', open: open, onToggle: () => setOpen((o) => !o), trailing: _jsx(RunningFor, { startedAt: toolStartedAt(tool) }), live: true }), open && _jsx(ToolBody, { tool: tool }), tail !== null && !open && (_jsx("pre", { className: "mt-1.5 mb-2 ml-6 max-h-48 overflow-auto rounded-md border border-border bg-surface-raised px-3 py-2 font-mono text-code whitespace-pre-wrap text-
|
|
59
|
+
return (_jsxs("div", { children: [_jsx(ToggleLine, { icon: toolIcon(tool.name), label: tool.name, detail: toolSummary(tool.name, tool.input) || tool.progress?.description || '', open: open, onToggle: () => setOpen((o) => !o), trailing: _jsx(RunningFor, { startedAt: toolStartedAt(tool) }), live: true }), open && _jsx(ToolBody, { tool: tool }), tail !== null && !open && (_jsx("pre", { className: "mt-1.5 mb-2 ml-6 max-h-48 overflow-auto rounded-md border border-border bg-surface-raised px-3 py-2 font-mono text-code whitespace-pre-wrap text-chat-output select-text", children: _jsx(AnsiOutput, { text: tail, tailLines: 12 }) }))] }));
|
|
60
60
|
}
|
|
61
61
|
/* A run of tool calls as one line ("Read 4 files"); opening it lists every call. */
|
|
62
62
|
export function WorkGroupRow({ tools, summary, expanded, onToggle }) {
|
|
@@ -91,7 +91,7 @@ function ChangedFilesCard({ count, truncated, added, deleted, revealNonce = 0, c
|
|
|
91
91
|
setSeenReveal(revealNonce);
|
|
92
92
|
setExpanded(true);
|
|
93
93
|
}
|
|
94
|
-
return (_jsxs("div", { className: "mb-3 overflow-hidden rounded-lg border border-border bg-surface-raised", children: [_jsxs("button", { className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-text-muted hover:bg-surface-hover", "aria-expanded": expanded, onClick: () => setExpanded((value) => !value), children: [_jsx(Icon, { icon: FileDiff, size: 12, className: "shrink-0" }), _jsx("span", { className: "shrink-0 font-medium text-text", children: t('work.changedFiles', { count }) }), truncated && _jsx("span", { className: "min-w-0 truncate text-text-faint", children: t('work.andMore') }), _jsx("span", { className: "grow" }), added !== undefined && _jsxs("span", { className: "text-
|
|
94
|
+
return (_jsxs("div", { className: "mb-3 overflow-hidden rounded-lg border border-border bg-surface-raised", children: [_jsxs("button", { className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-text-muted hover:bg-surface-hover", "aria-expanded": expanded, onClick: () => setExpanded((value) => !value), children: [_jsx(Icon, { icon: FileDiff, size: 12, className: "shrink-0" }), _jsx("span", { className: "shrink-0 font-medium text-text", children: t('work.changedFiles', { count }) }), truncated && _jsx("span", { className: "min-w-0 truncate text-text-faint", children: t('work.andMore') }), _jsx("span", { className: "grow" }), added !== undefined && _jsxs("span", { className: "text-chat-added tabular-nums", children: ["+", added] }), deleted !== undefined && _jsxs("span", { className: "text-chat-removed tabular-nums", children: ["-", deleted] }), _jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', expanded && 'rotate-90') })] }), expanded && children] }));
|
|
95
95
|
}
|
|
96
96
|
/*
|
|
97
97
|
* The files a settled turn changed, as a card; each file opens its diff in place. The host's
|
|
@@ -126,7 +126,7 @@ export function ChangedFilesRow({ tools, diff, checkpoint, chatId, turnId }) {
|
|
|
126
126
|
const revealNonce = flash !== null && flash.opens && flash.key === keyOf(chatId) && flash.rowId === `files-${turnId}` ? flash.nonce : 0;
|
|
127
127
|
if (checkpointDiff !== null && checkpointDiff.files.length > 0) {
|
|
128
128
|
const files = checkpointDiff.files;
|
|
129
|
-
return (_jsx(ChangedFilesCard, { revealNonce: revealNonce, count: files.length, truncated: checkpointDiff.truncated, added: files.reduce((sum, file) => sum + file.added, 0), deleted: files.reduce((sum, file) => sum + file.deleted, 0), children: files.map((file) => (_jsxs("div", { className: "border-t border-border", children: [_jsxs("button", { className: "flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-text-muted hover:bg-surface-hover", "data-file-path": file.path, onClick: () => setOpen((o) => ({ ...o, [file.path]: !o[file.path] })), children: [_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open[file.path] && 'rotate-90') }), _jsx("span", { className: "min-w-0 truncate font-mono text-text", children: file.path }), _jsx("span", { className: "grow" }), _jsxs("span", { className: "text-
|
|
129
|
+
return (_jsx(ChangedFilesCard, { revealNonce: revealNonce, count: files.length, truncated: checkpointDiff.truncated, added: files.reduce((sum, file) => sum + file.added, 0), deleted: files.reduce((sum, file) => sum + file.deleted, 0), children: files.map((file) => (_jsxs("div", { className: "border-t border-border", children: [_jsxs("button", { className: "flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-text-muted hover:bg-surface-hover", "data-file-path": file.path, onClick: () => setOpen((o) => ({ ...o, [file.path]: !o[file.path] })), children: [_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open[file.path] && 'rotate-90') }), _jsx("span", { className: "min-w-0 truncate font-mono text-text", children: file.path }), _jsx("span", { className: "grow" }), _jsxs("span", { className: "text-chat-added tabular-nums", children: ["+", file.added] }), _jsxs("span", { className: "text-chat-removed tabular-nums", children: ["-", file.deleted] })] }), open[file.path] && (_jsx("div", { className: "border-t border-border", children: _jsx(CheckpointFileBody, { file: file }) }))] }, file.path))) }));
|
|
130
130
|
}
|
|
131
131
|
return _jsx(ProviderChangedFiles, { tools: tools, open: open, setOpen: setOpen, revealNonce: revealNonce });
|
|
132
132
|
}
|
package/dist/theme.css
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
--chart-context-files: #9a63e0;
|
|
19
19
|
--chart-context-conversation: #2f9e5a;
|
|
20
20
|
--chart-context-system: var(--text-faint);
|
|
21
|
+
/* The current hit of a find, which has to stand out from the accent of a bookmark next to it. */
|
|
22
|
+
--chat-find: #facc15;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
[data-theme="dark"] {
|
|
@@ -31,6 +33,16 @@
|
|
|
31
33
|
--chart-context-files: #b98af3;
|
|
32
34
|
--chart-context-conversation: #6dd08b;
|
|
33
35
|
--chart-context-system: var(--text-faint);
|
|
36
|
+
--chat-find: #f5c542;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Output and diffs take the terminal's colors when the app loads `@adecore/terminal/terminal.css`,
|
|
40
|
+
and the base theme's otherwise. Declared on every themed element, so a nested theme resolves its own. */
|
|
41
|
+
:root, [data-theme] {
|
|
42
|
+
--chat-code-bg: var(--term-bg, var(--surface-sunken));
|
|
43
|
+
--chat-output: var(--term-fg, var(--text));
|
|
44
|
+
--chat-added: var(--term-ansi-green, var(--positive));
|
|
45
|
+
--chat-removed: var(--term-ansi-red, var(--status-error));
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
@theme inline {
|
|
@@ -44,6 +56,10 @@
|
|
|
44
56
|
--color-chart-context-files: var(--chart-context-files);
|
|
45
57
|
--color-chart-context-conversation: var(--chart-context-conversation);
|
|
46
58
|
--color-chart-context-system: var(--chart-context-system);
|
|
59
|
+
--color-chat-find: var(--chat-find);
|
|
60
|
+
--color-chat-output: var(--chat-output);
|
|
61
|
+
--color-chat-added: var(--chat-added);
|
|
62
|
+
--color-chat-removed: var(--chat-removed);
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
@layer components {
|
|
@@ -160,7 +176,7 @@
|
|
|
160
176
|
.chat-markdown :is(th, td) { border: 1px solid var(--tw-prose-td-borders); padding: 4px 8px; text-align: left; }
|
|
161
177
|
|
|
162
178
|
/* A code block is shiki's own `<pre>`, which brings a background of its own to override. */
|
|
163
|
-
.chat-code { border-radius: 8px; border: 1px solid var(--border); background: var(--
|
|
179
|
+
.chat-code { border-radius: 8px; border: 1px solid var(--border); background: var(--chat-code-bg); overflow: auto; }
|
|
164
180
|
.chat-code pre { margin: 0; padding: round(0.625rem, 1px) round(0.75rem, 1px); background: transparent !important; font-family: var(--font-mono); font-size: var(--text-code); line-height: var(--text-code--line-height); }
|
|
165
181
|
.chat-code code { font-family: inherit; }
|
|
166
182
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.17.0-beta.
|
|
3
|
+
"version": "0.17.0-beta.2",
|
|
4
4
|
"description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -848,7 +848,7 @@
|
|
|
848
848
|
"@lezer/highlight": "^1.2.3",
|
|
849
849
|
"@lezer/markdown": "^1.7.2",
|
|
850
850
|
"@pierre/diffs": "^1.4.3",
|
|
851
|
-
"@adecore/agent-contracts": "0.17.0-beta.
|
|
851
|
+
"@adecore/agent-contracts": "0.17.0-beta.2",
|
|
852
852
|
"@tanstack/react-virtual": "^3.14.13",
|
|
853
853
|
"clsx": "^2.1.1",
|
|
854
854
|
"lucide-react": "^1.47.0",
|
|
@@ -861,7 +861,7 @@
|
|
|
861
861
|
"zustand": "^5.0.15"
|
|
862
862
|
},
|
|
863
863
|
"peerDependencies": {
|
|
864
|
-
"@adecore/ui": "^0.17.0-beta.
|
|
864
|
+
"@adecore/ui": "^0.17.0-beta.2",
|
|
865
865
|
"i18next": "^26.4.2",
|
|
866
866
|
"react": "^19.3.0",
|
|
867
867
|
"react-dom": "^19.3.0",
|
package/src/chat/logic/ansi.ts
CHANGED
|
@@ -144,8 +144,8 @@ function applyRendition(state: AnsiState, parameters: string): AnsiState {
|
|
|
144
144
|
|
|
145
145
|
function styleOf(state: AnsiState): CSSProperties {
|
|
146
146
|
const style: CSSProperties = {};
|
|
147
|
-
const color = state.inverse ? (state.background ?? 'var(--
|
|
148
|
-
const background = state.inverse ? (state.foreground ?? 'var(--
|
|
147
|
+
const color = state.inverse ? (state.background ?? 'var(--chat-code-bg)') : state.foreground;
|
|
148
|
+
const background = state.inverse ? (state.foreground ?? 'var(--chat-output)') : state.background;
|
|
149
149
|
if (color !== undefined || state.dim) {
|
|
150
150
|
style.color = state.dim ? `color-mix(in srgb, ${color ?? 'currentColor'} 65%, transparent)` : color;
|
|
151
151
|
}
|
package/src/chat/ui/Scrubber.tsx
CHANGED
|
@@ -198,10 +198,7 @@ export const Scrubber = memo(function Scrubber({
|
|
|
198
198
|
{slot.found && (
|
|
199
199
|
<span
|
|
200
200
|
aria-hidden
|
|
201
|
-
className={clsx(
|
|
202
|
-
'absolute right-0 rounded-full bg-find-current',
|
|
203
|
-
index === currentSlot ? 'opacity-100' : 'opacity-50'
|
|
204
|
-
)}
|
|
201
|
+
className={clsx('absolute right-0 rounded-full bg-chat-find', index === currentSlot ? 'opacity-100' : 'opacity-50')}
|
|
205
202
|
style={{ top: slot.y, height: TICK_HEIGHT_PX, width: FOUND_WIDTH_PX }}
|
|
206
203
|
/>
|
|
207
204
|
)}
|
|
@@ -35,10 +35,10 @@ pre > [data-code] { flex-grow: 1; align-self: stretch; align-content: start; }
|
|
|
35
35
|
|
|
36
36
|
function lineClass(line: string): string {
|
|
37
37
|
if (line.startsWith('+')) {
|
|
38
|
-
return 'text-
|
|
38
|
+
return 'text-chat-added';
|
|
39
39
|
}
|
|
40
40
|
if (line.startsWith('-')) {
|
|
41
|
-
return 'text-
|
|
41
|
+
return 'text-chat-removed';
|
|
42
42
|
}
|
|
43
43
|
return 'text-text-muted';
|
|
44
44
|
}
|
|
@@ -103,7 +103,7 @@ function ToolBody({ tool }: { tool: ChatToolItem }) {
|
|
|
103
103
|
data-find-field="output"
|
|
104
104
|
className={clsx(
|
|
105
105
|
'max-h-64 overflow-auto whitespace-pre-wrap border-t border-border px-3 py-2 font-mono text-code select-text',
|
|
106
|
-
tool.state === 'error' ? 'text-
|
|
106
|
+
tool.state === 'error' ? 'text-chat-removed' : 'text-chat-output'
|
|
107
107
|
)}
|
|
108
108
|
>
|
|
109
109
|
<AnsiOutput text={tool.output} limit={OUTPUT_LIMIT} />
|
|
@@ -161,7 +161,7 @@ export function WorkLiveRow({ chatId, tool: derived }: { chatId: string; tool: C
|
|
|
161
161
|
/>
|
|
162
162
|
{open && <ToolBody tool={tool} />}
|
|
163
163
|
{tail !== null && !open && (
|
|
164
|
-
<pre className="mt-1.5 mb-2 ml-6 max-h-48 overflow-auto rounded-md border border-border bg-surface-raised px-3 py-2 font-mono text-code whitespace-pre-wrap text-
|
|
164
|
+
<pre className="mt-1.5 mb-2 ml-6 max-h-48 overflow-auto rounded-md border border-border bg-surface-raised px-3 py-2 font-mono text-code whitespace-pre-wrap text-chat-output select-text">
|
|
165
165
|
<AnsiOutput text={tail} tailLines={12} />
|
|
166
166
|
</pre>
|
|
167
167
|
)}
|
|
@@ -267,8 +267,8 @@ function ChangedFilesCard({
|
|
|
267
267
|
<span className="shrink-0 font-medium text-text">{t('work.changedFiles', { count })}</span>
|
|
268
268
|
{truncated && <span className="min-w-0 truncate text-text-faint">{t('work.andMore')}</span>}
|
|
269
269
|
<span className="grow" />
|
|
270
|
-
{added !== undefined && <span className="text-
|
|
271
|
-
{deleted !== undefined && <span className="text-
|
|
270
|
+
{added !== undefined && <span className="text-chat-added tabular-nums">+{added}</span>}
|
|
271
|
+
{deleted !== undefined && <span className="text-chat-removed tabular-nums">-{deleted}</span>}
|
|
272
272
|
<Icon icon={ChevronRight} size={12} className={clsx('shrink-0 text-text-faint transition-transform', expanded && 'rotate-90')} />
|
|
273
273
|
</button>
|
|
274
274
|
{expanded && children}
|
|
@@ -343,8 +343,8 @@ export function ChangedFilesRow({
|
|
|
343
343
|
/>
|
|
344
344
|
<span className="min-w-0 truncate font-mono text-text">{file.path}</span>
|
|
345
345
|
<span className="grow" />
|
|
346
|
-
<span className="text-
|
|
347
|
-
<span className="text-
|
|
346
|
+
<span className="text-chat-added tabular-nums">+{file.added}</span>
|
|
347
|
+
<span className="text-chat-removed tabular-nums">-{file.deleted}</span>
|
|
348
348
|
</button>
|
|
349
349
|
{open[file.path] && (
|
|
350
350
|
<div className="border-t border-border">
|
package/src/theme.css
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
--chart-context-files: #9a63e0;
|
|
19
19
|
--chart-context-conversation: #2f9e5a;
|
|
20
20
|
--chart-context-system: var(--text-faint);
|
|
21
|
+
/* The current hit of a find, which has to stand out from the accent of a bookmark next to it. */
|
|
22
|
+
--chat-find: #facc15;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
[data-theme="dark"] {
|
|
@@ -31,6 +33,16 @@
|
|
|
31
33
|
--chart-context-files: #b98af3;
|
|
32
34
|
--chart-context-conversation: #6dd08b;
|
|
33
35
|
--chart-context-system: var(--text-faint);
|
|
36
|
+
--chat-find: #f5c542;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Output and diffs take the terminal's colors when the app loads `@adecore/terminal/terminal.css`,
|
|
40
|
+
and the base theme's otherwise. Declared on every themed element, so a nested theme resolves its own. */
|
|
41
|
+
:root, [data-theme] {
|
|
42
|
+
--chat-code-bg: var(--term-bg, var(--surface-sunken));
|
|
43
|
+
--chat-output: var(--term-fg, var(--text));
|
|
44
|
+
--chat-added: var(--term-ansi-green, var(--positive));
|
|
45
|
+
--chat-removed: var(--term-ansi-red, var(--status-error));
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
@theme inline {
|
|
@@ -44,6 +56,10 @@
|
|
|
44
56
|
--color-chart-context-files: var(--chart-context-files);
|
|
45
57
|
--color-chart-context-conversation: var(--chart-context-conversation);
|
|
46
58
|
--color-chart-context-system: var(--chart-context-system);
|
|
59
|
+
--color-chat-find: var(--chat-find);
|
|
60
|
+
--color-chat-output: var(--chat-output);
|
|
61
|
+
--color-chat-added: var(--chat-added);
|
|
62
|
+
--color-chat-removed: var(--chat-removed);
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
@layer components {
|
|
@@ -160,7 +176,7 @@
|
|
|
160
176
|
.chat-markdown :is(th, td) { border: 1px solid var(--tw-prose-td-borders); padding: 4px 8px; text-align: left; }
|
|
161
177
|
|
|
162
178
|
/* A code block is shiki's own `<pre>`, which brings a background of its own to override. */
|
|
163
|
-
.chat-code { border-radius: 8px; border: 1px solid var(--border); background: var(--
|
|
179
|
+
.chat-code { border-radius: 8px; border: 1px solid var(--border); background: var(--chat-code-bg); overflow: auto; }
|
|
164
180
|
.chat-code pre { margin: 0; padding: round(0.625rem, 1px) round(0.75rem, 1px); background: transparent !important; font-family: var(--font-mono); font-size: var(--text-code); line-height: var(--text-code--line-height); }
|
|
165
181
|
.chat-code code { font-family: inherit; }
|
|
166
182
|
|