@adecore/agents-react 0.21.1 → 0.21.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/dist/agents/status-look.js +2 -3
- package/dist/chat/chat-client.d.ts +1 -1
- package/dist/chat/chat-client.js +2 -2
- package/dist/chat/drafts.js +6 -12
- package/dist/chat/logic/ansi.js +5 -1
- package/dist/chat/logic/fork.js +8 -9
- package/dist/chat/logic/limit.js +17 -14
- package/dist/chat/logic/resume-compaction.js +2 -11
- package/dist/chat/logic/scrubber.js +9 -18
- package/dist/chat/logic/timeline.js +15 -10
- package/dist/chat/logic/tool-catalog.js +3 -4
- package/dist/chat/mentions.js +5 -6
- package/dist/chat/runtime-modes.js +2 -4
- package/dist/chat/stash.js +1 -3
- package/dist/chat/thumbnails.js +3 -4
- package/dist/chat/timeline-scroll.js +39 -45
- package/dist/chat/ui/BookmarkSubmenu.js +1 -5
- package/dist/chat/ui/CodeBlock.js +2 -3
- package/dist/chat/ui/Composer.js +57 -69
- package/dist/chat/ui/Markdown.js +11 -11
- package/dist/chat/ui/Pickers.js +5 -9
- package/dist/chat/ui/QuoteButton.js +2 -3
- package/dist/chat/ui/RunSettings.js +8 -2
- package/dist/chat/ui/Scrubber.js +2 -3
- package/dist/chat/ui/SubagentControls.js +1 -3
- package/dist/chat/ui/Timeline.js +15 -18
- package/dist/chat/ui/TimelineMenu.js +3 -9
- package/dist/chat/ui/UnifiedDiff.js +3 -5
- package/dist/chat/ui/VisualFrame.js +3 -4
- package/dist/chat/ui/composer/keys.js +19 -11
- package/dist/chat/ui/intelligent-ui/render-context.d.ts +1 -1
- package/dist/chat/ui/rows/MessageRows.js +2 -3
- package/dist/chat/ui/rows/WorkRows.d.ts +4 -3
- package/dist/chat/ui/rows/WorkRows.js +19 -14
- package/dist/chat/visual-viewport.d.ts +1 -1
- package/dist/chat/visual-viewport.js +9 -8
- package/dist/port-transport.js +4 -8
- package/dist/state/chats.js +10 -14
- package/dist/state/usage.js +44 -49
- package/dist/storage.js +7 -7
- package/dist/theme.css +6 -14
- package/dist/usage/LimitsList.js +1 -2
- package/dist/usage/UsageBreakdown.js +1 -3
- package/dist/usage/UsageDialog.js +2 -4
- package/dist/usage/format.js +6 -6
- package/dist/usage/money.js +1 -5
- package/dist/usage/summary.js +2 -4
- package/package.json +4 -4
- package/src/agents/status-look.ts +2 -3
- package/src/chat/chat-client.ts +2 -3
- package/src/chat/drafts.ts +6 -14
- package/src/chat/logic/ansi.ts +5 -1
- package/src/chat/logic/fork.ts +8 -9
- package/src/chat/logic/limit.ts +17 -16
- package/src/chat/logic/resume-compaction.ts +2 -11
- package/src/chat/logic/scrubber.ts +13 -17
- package/src/chat/logic/timeline.ts +22 -11
- package/src/chat/logic/tool-catalog.ts +7 -6
- package/src/chat/mentions.ts +5 -6
- package/src/chat/runtime-modes.ts +2 -4
- package/src/chat/stash.ts +1 -3
- package/src/chat/thumbnails.ts +3 -4
- package/src/chat/timeline-scroll.ts +43 -52
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -5
- package/src/chat/ui/CodeBlock.tsx +2 -3
- package/src/chat/ui/Composer.tsx +66 -86
- package/src/chat/ui/Markdown.tsx +13 -17
- package/src/chat/ui/Pickers.tsx +5 -9
- package/src/chat/ui/QuoteButton.tsx +2 -3
- package/src/chat/ui/RunSettings.tsx +9 -2
- package/src/chat/ui/Scrubber.tsx +2 -3
- package/src/chat/ui/SubagentControls.tsx +1 -3
- package/src/chat/ui/SubagentTimeline.tsx +1 -2
- package/src/chat/ui/Timeline.tsx +18 -20
- package/src/chat/ui/TimelineMenu.tsx +3 -9
- package/src/chat/ui/UnifiedDiff.tsx +3 -5
- package/src/chat/ui/VisualFrame.tsx +3 -4
- package/src/chat/ui/composer/keys.ts +21 -11
- package/src/chat/ui/intelligent-ui/render-context.ts +2 -2
- package/src/chat/ui/rehype-chips.ts +1 -1
- package/src/chat/ui/rehype-fade.ts +1 -1
- package/src/chat/ui/remark-html-as-text.ts +1 -1
- package/src/chat/ui/rows/MessageRows.tsx +3 -4
- package/src/chat/ui/rows/WorkRows.tsx +86 -69
- package/src/chat/visual-viewport.ts +10 -8
- package/src/host.ts +1 -3
- package/src/port-transport.ts +4 -8
- package/src/state/chats.ts +11 -14
- package/src/state/usage.ts +49 -60
- package/src/storage.ts +7 -7
- package/src/theme.css +6 -14
- package/src/usage/LimitsList.tsx +2 -4
- package/src/usage/UsageBreakdown.tsx +2 -5
- package/src/usage/UsageDialog.tsx +2 -4
- package/src/usage/format.ts +6 -6
- package/src/usage/money.ts +1 -5
- package/src/usage/summary.ts +2 -4
|
@@ -30,24 +30,32 @@ export function enterAction(keys, { inOpenFence, list, column }) {
|
|
|
30
30
|
}
|
|
31
31
|
return list.empty ? 'leave-list' : 'continue-list';
|
|
32
32
|
}
|
|
33
|
-
/*
|
|
34
|
-
|
|
33
|
+
/* The innermost fenced code block around `pos`, if any. */
|
|
34
|
+
function enclosingFence(tree, pos) {
|
|
35
35
|
for (let node = tree.resolveInner(pos, -1); node !== null; node = node.parent) {
|
|
36
36
|
if (node.name === 'FencedCode') {
|
|
37
|
-
return
|
|
37
|
+
return node;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
return
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
/* A fence nobody has closed yet has its opening mark only. */
|
|
43
|
+
function isOpenFence(fence) {
|
|
44
|
+
return fence.getChildren('CodeMark').length < 2;
|
|
45
|
+
}
|
|
46
|
+
/* Whether `pos` sits inside a fenced code block that has an opening fence and no closing one yet. */
|
|
47
|
+
export function inOpenFence(tree, pos) {
|
|
48
|
+
const fence = enclosingFence(tree, pos);
|
|
49
|
+
return fence !== null && pos > fence.from && isOpenFence(fence);
|
|
41
50
|
}
|
|
42
51
|
/* Whether `pos` sits in the body of a fenced code block, open or closed: below its opening line and before its end. */
|
|
43
52
|
export function inFenceBody(tree, text, pos) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return openingEnd !== -1 && pos > openingEnd && (pos < node.to || node.getChildren('CodeMark').length < 2);
|
|
48
|
-
}
|
|
53
|
+
const fence = enclosingFence(tree, pos);
|
|
54
|
+
if (fence === null) {
|
|
55
|
+
return false;
|
|
49
56
|
}
|
|
50
|
-
|
|
57
|
+
const openingEnd = text.indexOf('\n', fence.from);
|
|
58
|
+
return openingEnd !== -1 && pos > openingEnd && (pos < fence.to || isOpenFence(fence));
|
|
51
59
|
}
|
|
52
60
|
/* The spaces Tab inserts at `column`, up to the next stop of four, the way a code editor lines up. */
|
|
53
61
|
export function tabSpaces(column) {
|
|
@@ -63,7 +71,7 @@ export function inCode(tree, text, pos) {
|
|
|
63
71
|
let blockFrom = null;
|
|
64
72
|
for (let node = tree.resolveInner(pos, -1); node !== null; node = node.parent) {
|
|
65
73
|
if (node.name === 'FencedCode') {
|
|
66
|
-
return pos > node.from && (pos < node.to || node
|
|
74
|
+
return pos > node.from && (pos < node.to || isOpenFence(node));
|
|
67
75
|
}
|
|
68
76
|
if (node.name === 'InlineCode' && pos > node.from && pos < node.to) {
|
|
69
77
|
return true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { UiComponentName, UiProps, UiViewNode } from '@adecore/intelligent-ui';
|
|
3
|
-
export type { UiLinkTarget } from '@adecore/intelligent-ui/links';
|
|
4
3
|
import type { UiLinkTarget } from '@adecore/intelligent-ui/links';
|
|
4
|
+
export type { UiLinkTarget };
|
|
5
5
|
export interface UiLink {
|
|
6
6
|
state: 'chip' | 'plain';
|
|
7
7
|
label?: string;
|
|
@@ -186,9 +186,8 @@ function OpenChatButton({ chatId }) {
|
|
|
186
186
|
return (_jsx("button", { className: "text-text-muted hover:text-text", onClick: place.go, children: t('rows.note.openFork') }));
|
|
187
187
|
}
|
|
188
188
|
/*
|
|
189
|
-
* The header of a turn the agent started itself, in place of
|
|
190
|
-
*
|
|
191
|
-
* "Show result". When the CLI named the sub-agent it woke up about, the header opens that row.
|
|
189
|
+
* The header of a turn the agent started itself, in place of a message of the person. What the
|
|
190
|
+
* sub-agent came back with lives on its own row, which the header opens when the CLI named it.
|
|
192
191
|
*/
|
|
193
192
|
export function AgentTurnRow({ label, onOpen }) {
|
|
194
193
|
const line = 'mb-0.5 flex h-7 w-full items-center gap-2 text-left text-xs text-text-muted';
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
import type { ChatCheckpointDiff, ChatToolItem, ChatTurnItem } from '@adecore/agent-contracts';
|
|
2
3
|
export declare function ToggleLine({ icon, label, detail, open, onToggle, failed, live, trailing, className }: {
|
|
3
|
-
icon:
|
|
4
|
+
icon: ReactNode;
|
|
4
5
|
label: string;
|
|
5
6
|
detail?: string;
|
|
6
7
|
open: boolean;
|
|
7
8
|
onToggle(): void;
|
|
8
9
|
failed?: boolean;
|
|
9
10
|
live?: boolean;
|
|
10
|
-
trailing?:
|
|
11
|
+
trailing?: ReactNode;
|
|
11
12
|
className?: string;
|
|
12
13
|
}): import("react").JSX.Element;
|
|
13
14
|
export declare function WorkRow({ tool, nested, detail, icon, label, failed }: {
|
|
14
15
|
tool: ChatToolItem;
|
|
15
16
|
nested?: boolean;
|
|
16
17
|
detail?: string;
|
|
17
|
-
icon?:
|
|
18
|
+
icon?: ReactNode;
|
|
18
19
|
label?: string;
|
|
19
20
|
failed?: boolean;
|
|
20
21
|
}): import("react").JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Suspense, useEffect, useState } from 'react';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import i18next from 'i18next';
|
|
@@ -30,15 +30,24 @@ export function ToggleLine({ icon, label, detail, open, onToggle, failed, live,
|
|
|
30
30
|
// Fix the width so long commands reach the truncation inside the negative margins.
|
|
31
31
|
'-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 hover:bg-surface-hover', failed && 'text-status-error', className), onClick: onToggle, children: [_jsx("span", { className: clsx(ROW_GUTTER, live && 'text-accent'), children: icon }), _jsx("span", { className: clsx('shrink-0', live && 'chat-live-text'), children: label }), detail && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-text-faint", children: detail })), _jsx("span", { className: "grow" }), trailing, failed && _jsx(Icon, { icon: X, size: 12, className: "shrink-0" }), _jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90') })] }));
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
/* The diff renderers load lazily, so a diff waits on them under a line that says so. */
|
|
34
|
+
function DiffSuspense({ children }) {
|
|
34
35
|
const { t } = useTranslation('agent-chat');
|
|
36
|
+
return _jsx(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: children });
|
|
37
|
+
}
|
|
38
|
+
/* A file in a changed files card, which opens its diff under it. */
|
|
39
|
+
function ChangedFileLine({ path, open, onToggle, trailing, children }) {
|
|
40
|
+
return (_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": path, onClick: onToggle, children: [_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90') }), _jsx("span", { className: "min-w-0 truncate font-mono text-text", children: path }), _jsx("span", { className: "grow" }), trailing] }), open && _jsx("div", { className: "border-t border-border", children: children })] }));
|
|
41
|
+
}
|
|
42
|
+
function ToolBody({ tool }) {
|
|
35
43
|
const patches = unifiedChanges(tool);
|
|
36
44
|
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(
|
|
45
|
+
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(DiffSuspense, { children: patches.map((change, index) => (_jsx(UnifiedDiff, { change: change }, index))) })) : changes.length > 0 ? (_jsx(DiffSuspense, { 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
46
|
}
|
|
39
|
-
/*
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
/*
|
|
48
|
+
* One settled tool call: a line, and its input and output behind it. An image the call looked at is
|
|
49
|
+
* drawn under the line. `icon`, `label` and `failed` let a row that knows the call better name it.
|
|
50
|
+
*/
|
|
42
51
|
export function WorkRow({ tool, nested, detail, icon, label, failed }) {
|
|
43
52
|
const [open, setOpen] = useState(false);
|
|
44
53
|
const image = tool.state === 'done' ? readImagePath(tool.name, tool.input) : null;
|
|
@@ -76,17 +85,16 @@ function omittedLabel(reason) {
|
|
|
76
85
|
}
|
|
77
86
|
/* One file of a turn's checkpoint diff: its patch, or the reason there is none. */
|
|
78
87
|
function CheckpointFileBody({ file }) {
|
|
79
|
-
const { t } = useTranslation('agent-chat');
|
|
80
88
|
if (file.omitted) {
|
|
81
89
|
return _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: omittedLabel(file.omitted) });
|
|
82
90
|
}
|
|
83
|
-
return (_jsx(
|
|
91
|
+
return (_jsx(DiffSuspense, { children: _jsx(UnifiedDiff, { change: file }) }));
|
|
84
92
|
}
|
|
85
93
|
/* The frame of a changed files card: a header that folds the list of files away. */
|
|
86
94
|
function ChangedFilesCard({ count, truncated, added, deleted, revealNonce = 0, children }) {
|
|
87
95
|
const { t } = useTranslation('agent-chat');
|
|
88
96
|
const [expanded, setExpanded] = useState(false);
|
|
89
|
-
// A jump
|
|
97
|
+
// A jump opens the card, also one that mounted it by scrolling it on screen; the flash is gone by the next visit.
|
|
90
98
|
const [seenReveal, setSeenReveal] = useState(0);
|
|
91
99
|
if (revealNonce > 0 && revealNonce !== seenReveal) {
|
|
92
100
|
setSeenReveal(revealNonce);
|
|
@@ -127,7 +135,7 @@ export function ChangedFilesRow({ tools, diff, checkpoint, chatId, turnId }) {
|
|
|
127
135
|
const revealNonce = flash !== null && flash.opens && flash.key === keyOf(chatId) && flash.rowId === `files-${turnId}` ? flash.nonce : 0;
|
|
128
136
|
if (checkpointDiff !== null && checkpointDiff.files.length > 0) {
|
|
129
137
|
const files = checkpointDiff.files;
|
|
130
|
-
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) => (
|
|
138
|
+
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) => (_jsx(ChangedFileLine, { path: file.path, open: open[file.path] === true, onToggle: () => setOpen((o) => ({ ...o, [file.path]: !o[file.path] })), trailing: _jsxs(_Fragment, { children: [_jsxs("span", { className: "text-chat-added tabular-nums", children: ["+", file.added] }), _jsxs("span", { className: "text-chat-removed tabular-nums", children: ["-", file.deleted] })] }), children: _jsx(CheckpointFileBody, { file: file }) }, file.path))) }));
|
|
131
139
|
}
|
|
132
140
|
return _jsx(ProviderChangedFiles, { tools: tools, open: open, setOpen: setOpen, revealNonce: revealNonce });
|
|
133
141
|
}
|
|
@@ -156,10 +164,7 @@ function ProviderChangedFiles({ tools, open, setOpen, revealNonce }) {
|
|
|
156
164
|
entryFor(change.path || tool.id).edits.push(change);
|
|
157
165
|
}
|
|
158
166
|
}
|
|
159
|
-
return (_jsx(ChangedFilesCard, { count: byPath.size, revealNonce: revealNonce, children: [...byPath].map(([path, entry]) => {
|
|
160
|
-
const count = entry.edits.length + entry.patches.length;
|
|
161
|
-
return (_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": path, onClick: () => setOpen((o) => ({ ...o, [path]: !o[path] })), children: [_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open[path] && 'rotate-90') }), _jsx("span", { className: "min-w-0 truncate font-mono text-text", children: path }), _jsx("span", { className: "grow" }), _jsx("span", { className: "text-text-faint", children: t('work.edits', { count }) })] }), open[path] && (_jsx("div", { className: "border-t border-border", children: _jsxs(Suspense, { fallback: _jsx("div", { className: "px-3 py-2 text-xs text-text-faint", children: t('work.loadingDiff') }), children: [entry.patches.map((change, index) => (_jsx(UnifiedDiff, { change: change }, `patch-${index}`))), entry.edits.map((change, index) => (_jsx(EditDiff, { change: change }, `edit-${index}`)))] }) }))] }, path));
|
|
162
|
-
}) }));
|
|
167
|
+
return (_jsx(ChangedFilesCard, { count: byPath.size, revealNonce: revealNonce, children: [...byPath].map(([path, entry]) => (_jsx(ChangedFileLine, { path: path, open: open[path] === true, onToggle: () => setOpen((o) => ({ ...o, [path]: !o[path] })), trailing: _jsx("span", { className: "text-text-faint", children: t('work.edits', { count: entry.edits.length + entry.patches.length }) }), children: _jsxs(DiffSuspense, { children: [entry.patches.map((change, index) => (_jsx(UnifiedDiff, { change: change }, `patch-${index}`))), entry.edits.map((change, index) => (_jsx(EditDiff, { change: change }, `edit-${index}`)))] }) }, path))) }));
|
|
163
168
|
}
|
|
164
169
|
/* "Working for 00:14". */
|
|
165
170
|
export function WorkingRow({ startedAt }) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { VisualScrollRequest } from '@adecore/agent-contracts/visual';
|
|
2
2
|
import type { VisualBridge } from './visual-bridge.js';
|
|
3
|
-
/** Keeps one small frame in a full-height timeline row, without rendering React on scroll. */
|
|
4
3
|
export declare class VisualViewportController {
|
|
5
4
|
private readonly box;
|
|
6
5
|
private readonly viewport;
|
|
@@ -18,5 +17,6 @@ export declare class VisualViewportController {
|
|
|
18
17
|
refresh(): void;
|
|
19
18
|
scroll(request: VisualScrollRequest): void;
|
|
20
19
|
dispose(): void;
|
|
20
|
+
private offsetInScroller;
|
|
21
21
|
private schedule;
|
|
22
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { visualViewportGeometry } from './logic/visual-viewport.js';
|
|
2
|
-
|
|
2
|
+
/* Keeps one small frame in a full-height timeline row, without rendering React on scroll. */
|
|
3
3
|
export class VisualViewportController {
|
|
4
4
|
box;
|
|
5
5
|
viewport;
|
|
@@ -35,11 +35,7 @@ export class VisualViewportController {
|
|
|
35
35
|
if (this.disposed) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
const
|
|
39
|
-
const box = this.box.getBoundingClientRect();
|
|
40
|
-
// A chat on a canvas can be scaled; messages inside its frame use unscaled CSS pixels.
|
|
41
|
-
const scale = outer.height / this.scroller.offsetHeight || 1;
|
|
42
|
-
const top = (box.top - outer.top) / scale - this.scroller.clientTop;
|
|
38
|
+
const top = this.offsetInScroller(this.scroller.getBoundingClientRect(), this.box.getBoundingClientRect());
|
|
43
39
|
const available = this.scroller.clientHeight;
|
|
44
40
|
const geometry = visualViewportGeometry(this.box.offsetHeight, available, top);
|
|
45
41
|
this.viewport.style.height = `${geometry.height}px`;
|
|
@@ -61,8 +57,7 @@ export class VisualViewportController {
|
|
|
61
57
|
return;
|
|
62
58
|
}
|
|
63
59
|
this.pendingDelta = 0;
|
|
64
|
-
const
|
|
65
|
-
const start = this.scroller.scrollTop + (box.top - outer.top) / scale - this.scroller.clientTop;
|
|
60
|
+
const start = this.scroller.scrollTop + this.offsetInScroller(outer, box);
|
|
66
61
|
const top = 'to' in request ? start + request.to : request.edge === 'start' ? 0 : this.scroller.scrollHeight;
|
|
67
62
|
this.scroller.scrollTo({ top, behavior: 'instant' });
|
|
68
63
|
this.refresh();
|
|
@@ -75,6 +70,12 @@ export class VisualViewportController {
|
|
|
75
70
|
this.scroller.removeEventListener('scroll', this.onScroll);
|
|
76
71
|
this.bridge.setViewport(null);
|
|
77
72
|
}
|
|
73
|
+
/* The box's top below the scroller's visible top, in the scroller's own pixels. */
|
|
74
|
+
offsetInScroller(outer, box) {
|
|
75
|
+
// A chat on a canvas can be scaled; messages inside its frame use unscaled CSS pixels.
|
|
76
|
+
const scale = outer.height / this.scroller.offsetHeight || 1;
|
|
77
|
+
return (box.top - outer.top) / scale - this.scroller.clientTop;
|
|
78
|
+
}
|
|
78
79
|
schedule() {
|
|
79
80
|
if (this.frame || this.disposed) {
|
|
80
81
|
return;
|
package/dist/port-transport.js
CHANGED
|
@@ -93,15 +93,11 @@ export function portTransport(port) {
|
|
|
93
93
|
return promise;
|
|
94
94
|
},
|
|
95
95
|
on(event, handler) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
eventHandlers.set(event, handlers);
|
|
100
|
-
}
|
|
101
|
-
const set = handlers;
|
|
102
|
-
set.add(handler);
|
|
96
|
+
const handlers = eventHandlers.get(event) ?? new Set();
|
|
97
|
+
eventHandlers.set(event, handlers);
|
|
98
|
+
handlers.add(handler);
|
|
103
99
|
return () => {
|
|
104
|
-
|
|
100
|
+
handlers.delete(handler);
|
|
105
101
|
};
|
|
106
102
|
},
|
|
107
103
|
subscribeStatus(handler) {
|
package/dist/state/chats.js
CHANGED
|
@@ -113,6 +113,14 @@ export function applyEvent(state, event) {
|
|
|
113
113
|
return stateOf(event.info, event.items);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
/* Fields of a held chat that are not part of its thread; a chat this client does not hold stays as it is. */
|
|
117
|
+
function withFields(store, key, fields) {
|
|
118
|
+
const current = store.byKey[key];
|
|
119
|
+
if (!current) {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
return { byKey: { ...store.byKey, [key]: { ...current, ...fields } } };
|
|
123
|
+
}
|
|
116
124
|
export const useChats = create((set) => ({
|
|
117
125
|
byKey: {},
|
|
118
126
|
statusByKey: {},
|
|
@@ -166,22 +174,10 @@ export const useChats = create((set) => ({
|
|
|
166
174
|
});
|
|
167
175
|
},
|
|
168
176
|
bookmarks(key, bookmarks) {
|
|
169
|
-
set((s) => {
|
|
170
|
-
const current = s.byKey[key];
|
|
171
|
-
if (!current) {
|
|
172
|
-
return {};
|
|
173
|
-
}
|
|
174
|
-
return { byKey: { ...s.byKey, [key]: { ...current, bookmarks } } };
|
|
175
|
-
});
|
|
177
|
+
set((s) => withFields(s, key, { bookmarks }));
|
|
176
178
|
},
|
|
177
179
|
visuals(key, visuals) {
|
|
178
|
-
set((s) => {
|
|
179
|
-
const current = s.byKey[key];
|
|
180
|
-
if (!current) {
|
|
181
|
-
return {};
|
|
182
|
-
}
|
|
183
|
-
return { byKey: { ...s.byKey, [key]: { ...current, visuals } } };
|
|
184
|
-
});
|
|
180
|
+
set((s) => withFields(s, key, { visuals }));
|
|
185
181
|
},
|
|
186
182
|
forget(key) {
|
|
187
183
|
set((s) => {
|
package/dist/state/usage.js
CHANGED
|
@@ -32,12 +32,6 @@ function parsePreferences(raw) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
const storage = persistedJson(storageKey, parsePreferences, DEFAULTS, () => chatStorageLegacyKeys('usage'));
|
|
35
|
-
function readPreferences() {
|
|
36
|
-
return storage.read();
|
|
37
|
-
}
|
|
38
|
-
function persist(preferences) {
|
|
39
|
-
storage.write(preferences);
|
|
40
|
-
}
|
|
41
35
|
/* The three stored choices out of the store, so a setter writes the other two back unchanged. */
|
|
42
36
|
function chosen(state) {
|
|
43
37
|
return { period: state.period, metric: state.metric, currency: state.currency };
|
|
@@ -49,10 +43,7 @@ function pad(value) {
|
|
|
49
43
|
export function dayOf(date) {
|
|
50
44
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
51
45
|
}
|
|
52
|
-
/*
|
|
53
|
-
* What a period asks for. Today counts from local midnight in hours, the way the commit log already
|
|
54
|
-
* groups days, so every other period reads as one bar per calendar day.
|
|
55
|
-
*/
|
|
46
|
+
/* What a period asks for: today in hours from local midnight, every other period a bar per calendar day. */
|
|
56
47
|
export function windowFor(period, now = new Date()) {
|
|
57
48
|
const days = USAGE_PERIODS.find((entry) => entry.id === period)?.days ?? 7;
|
|
58
49
|
const from = new Date(now.getFullYear(), now.getMonth(), now.getDate() - (days - 1));
|
|
@@ -75,46 +66,50 @@ export function askedKey(payload) {
|
|
|
75
66
|
const EMPTY = { asked: null, summary: null, loading: false, failed: false, limits: null };
|
|
76
67
|
/* Reads the period, metric and currency again, once another window of the same origin wrote them. */
|
|
77
68
|
export function reloadUsagePreferences() {
|
|
78
|
-
useUsageStore.setState(
|
|
69
|
+
useUsageStore.setState(storage.read());
|
|
79
70
|
}
|
|
80
|
-
export const useUsageStore = createChatStore((set, get) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
71
|
+
export const useUsageStore = createChatStore((set, get) => {
|
|
72
|
+
const choosePreference = (change) => {
|
|
73
|
+
storage.write({ ...chosen(get()), ...change });
|
|
74
|
+
set(change);
|
|
75
|
+
};
|
|
76
|
+
const patchRow = (scopeId, change) => {
|
|
77
|
+
set({ byScope: { ...get().byScope, [scopeId]: { ...(get().byScope[scopeId] ?? EMPTY), ...change } } });
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
...DEFAULTS,
|
|
81
|
+
byScope: {},
|
|
82
|
+
chosen: null,
|
|
83
|
+
choose(scopeId) {
|
|
84
|
+
set({ chosen: scopeId });
|
|
85
|
+
},
|
|
86
|
+
setPeriod(period) {
|
|
87
|
+
choosePreference({ period });
|
|
88
|
+
},
|
|
89
|
+
setMetric(metric) {
|
|
90
|
+
choosePreference({ metric });
|
|
91
|
+
},
|
|
92
|
+
setCurrency(currency) {
|
|
93
|
+
choosePreference({ currency });
|
|
94
|
+
},
|
|
95
|
+
setLoading(scopeId, loading) {
|
|
96
|
+
patchRow(scopeId, { loading });
|
|
97
|
+
},
|
|
98
|
+
receive(scopeId, asked, summary) {
|
|
99
|
+
patchRow(scopeId, { asked, summary, loading: false, failed: false });
|
|
100
|
+
},
|
|
101
|
+
fail(scopeId) {
|
|
102
|
+
patchRow(scopeId, { loading: false, failed: true });
|
|
103
|
+
},
|
|
104
|
+
setLimits(scopeId, limits) {
|
|
105
|
+
patchRow(scopeId, { limits });
|
|
106
|
+
},
|
|
107
|
+
forget(scopeId) {
|
|
108
|
+
const { [scopeId]: _gone, ...rest } = get().byScope;
|
|
109
|
+
set({ byScope: rest });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}, (store) => store.setState(storage.read()));
|
|
118
113
|
export function useUsage(select) {
|
|
119
114
|
const { id } = useChatScope();
|
|
120
115
|
return useUsageStore((s) => select({ period: s.period, metric: s.metric, currency: s.currency, ...(s.byScope[id] ?? EMPTY) }));
|
package/dist/storage.js
CHANGED
|
@@ -64,25 +64,25 @@ export function createChatStore(initial, hydrate) {
|
|
|
64
64
|
hydrate(store);
|
|
65
65
|
};
|
|
66
66
|
hydrators.push(hydrateOnce);
|
|
67
|
-
const
|
|
67
|
+
const ready = () => {
|
|
68
68
|
hydrateChatStorage();
|
|
69
69
|
hydrateOnce();
|
|
70
|
+
};
|
|
71
|
+
const useStore = ((...args) => {
|
|
72
|
+
ready();
|
|
70
73
|
return store(...args);
|
|
71
74
|
});
|
|
72
75
|
Object.assign(useStore, store);
|
|
73
76
|
useStore.getState = () => {
|
|
74
|
-
|
|
75
|
-
hydrateOnce();
|
|
77
|
+
ready();
|
|
76
78
|
return store.getState();
|
|
77
79
|
};
|
|
78
80
|
useStore.subscribe = (...args) => {
|
|
79
|
-
|
|
80
|
-
hydrateOnce();
|
|
81
|
+
ready();
|
|
81
82
|
return store.subscribe(...args);
|
|
82
83
|
};
|
|
83
84
|
useStore.setState = ((...args) => {
|
|
84
|
-
|
|
85
|
-
hydrateOnce();
|
|
85
|
+
ready();
|
|
86
86
|
store.setState(...args);
|
|
87
87
|
});
|
|
88
88
|
return useStore;
|
package/dist/theme.css
CHANGED
|
@@ -76,8 +76,6 @@
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
@layer components {
|
|
79
|
-
/* ---------------------------------------------------------------- Chat */
|
|
80
|
-
|
|
81
79
|
/* Text in a chat is there to be read and copied, so the thread selects even though the app does
|
|
82
80
|
not. Its chrome stays out of it: a drag on a fold header or a chip would select the row.
|
|
83
81
|
The pretty wrap from `body` would be recomputed on the growing paragraph every frame a reply
|
|
@@ -160,18 +158,12 @@
|
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
/*
|
|
163
|
-
* Markdown, outside every layer on purpose
|
|
164
|
-
* and
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* Markdown anywhere in the app: the chat timeline, a note node and the file preview all render
|
|
170
|
-
* the same `Markdown` component, which carries `prose prose-sm max-w-none text-sm` next to this
|
|
171
|
-
* class. Prose brings the rhythm; everything here is what the app decides instead of the plugin.
|
|
172
|
-
* The semantic tokens flip with the theme on their own, so the prose variables read from them
|
|
173
|
-
* and `prose-invert` is a second source of truth the app does not need. The size comes from the
|
|
174
|
-
* `text-sm` utility, which a node, a view and the file preview each move.
|
|
161
|
+
* Markdown, outside every layer on purpose: the typography plugin registers `.prose` as a utility,
|
|
162
|
+
* and only unlayered styles are sure to land on top of it.
|
|
163
|
+
*
|
|
164
|
+
* Every `Markdown` carries `prose prose-sm max-w-none text-sm` next to this class. Prose brings the
|
|
165
|
+
* rhythm; everything here is what the app decides instead. The semantic tokens flip with the theme on
|
|
166
|
+
* their own, so the prose variables read from them and `prose-invert` is not needed.
|
|
175
167
|
*/
|
|
176
168
|
.prose.chat-markdown {
|
|
177
169
|
--tw-prose-body: var(--text);
|
package/dist/usage/LimitsList.js
CHANGED
|
@@ -9,8 +9,7 @@ import { formatClock, formatWeekdayClock, isSameDay, formatCountdown, formatPerc
|
|
|
9
9
|
import { ProviderLogo } from '../agents/ProviderLogo.js';
|
|
10
10
|
import { PROVIDER_COLORS, PROVIDER_LABELS } from './format.js';
|
|
11
11
|
import { accountNote, explain, isSignedOut, nextReset } from './limit-groups.js';
|
|
12
|
-
/* Red where a window is nearly spent, amber where it is worth knowing
|
|
13
|
-
is not news, so it takes the text color rather than a hue that competes with the two that are. */
|
|
12
|
+
/* Red where a window is nearly spent, amber where it is worth knowing; room to spare is not news and takes the text color. */
|
|
14
13
|
function toneOf(used) {
|
|
15
14
|
return used >= 0.9 ? 'bg-status-error' : used >= 0.7 ? 'bg-status-needs-you' : 'bg-text';
|
|
16
15
|
}
|
|
@@ -12,11 +12,9 @@ import { modelNameFromSlug } from '../agents/model-name.js';
|
|
|
12
12
|
import { formatCount, formatTokens, PROVIDER_COLORS, PROVIDER_LABELS, shortPath, slotLabel } from './format.js';
|
|
13
13
|
import { useMoney } from './money.js';
|
|
14
14
|
import { deriveDays } from './summary.js';
|
|
15
|
-
/* The row of every table, the same height and the same hover as a commit row in the git panel. */
|
|
16
15
|
const ROW = 'flex h-8 items-center gap-2 rounded-md px-2 text-xs hover:bg-surface-hover';
|
|
17
16
|
const HEAD = 'flex h-6 items-center gap-2 px-2 text-xs text-text-faint';
|
|
18
|
-
/*
|
|
19
|
-
again. The first column then starts on the same line as every other section of the page. */
|
|
17
|
+
/* The table pulls the padding that gives a row's hover room back off, so its first column lines up with the page. */
|
|
20
18
|
const TABLE = '-mx-2 flex flex-col';
|
|
21
19
|
const TABS = ['models', 'projects', 'day'];
|
|
22
20
|
/* A share of the biggest row, never under one pixel. A row that did something has to be visible. */
|
|
@@ -2,10 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import i18next from 'i18next';
|
|
3
3
|
import { Dialog } from '@adecore/ui';
|
|
4
4
|
/*
|
|
5
|
-
* The frame of the usage page
|
|
6
|
-
*
|
|
7
|
-
* open, so nothing is scanned behind a closed dialog. It is a dialog and not a view, since none of it
|
|
8
|
-
* belongs to one project.
|
|
5
|
+
* The frame of the usage page, wider than the settings for the chart and the breakdown. What goes in
|
|
6
|
+
* it mounts only while it is open, so nothing is scanned behind a closed dialog.
|
|
9
7
|
*/
|
|
10
8
|
export function UsageDialog({ open, onOpenChange, children }) {
|
|
11
9
|
return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Popup, { className: "flex h-[820px] w-[1080px] flex-col", children: [children, _jsx(Dialog.Description, { className: "sr-only", children: i18next.t('agent-usage:dialog.description') })] }) }));
|
package/dist/usage/format.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { formatDay, formatHour, formatWeekdayDay, formatMoney, formatNumber, formatTokens } from '@adecore/ui/format';
|
|
2
2
|
export { formatClock } from '@adecore/ui/format';
|
|
3
|
-
/*
|
|
4
|
-
would be: `$5,480.96` in the US, `$ 5.480,96` in the Netherlands. The labels stay English. */
|
|
3
|
+
/* Read in the region of the machine looking at it: `$5,480.96` in the US, `$ 5.480,96` in the Netherlands. */
|
|
5
4
|
export function formatCount(value) {
|
|
6
5
|
return formatNumber(value);
|
|
7
6
|
}
|
|
@@ -23,8 +22,10 @@ export function moneyFormat(currency, rate) {
|
|
|
23
22
|
}
|
|
24
23
|
export const PROVIDER_LABELS = { claude: 'Claude Code', codex: 'Codex' };
|
|
25
24
|
export const PROVIDER_COLORS = { claude: 'var(--chart-claude)', codex: 'var(--chart-codex)' };
|
|
26
|
-
/*
|
|
27
|
-
|
|
25
|
+
/*
|
|
26
|
+
* A slot is `2026-09-10` or `2026-09-10T14`, already in the viewer's own zone, so it is read as plain
|
|
27
|
+
* wall clock and never handed to a parser that would shift it back.
|
|
28
|
+
*/
|
|
28
29
|
function dateOfSlot(slot) {
|
|
29
30
|
return new Date(Number(slot.slice(0, 4)), Number(slot.slice(5, 7)) - 1, Number(slot.slice(8, 10)));
|
|
30
31
|
}
|
|
@@ -47,8 +48,7 @@ export function slotAxisLabel(slot) {
|
|
|
47
48
|
export function formatDate(at) {
|
|
48
49
|
return formatDay(at);
|
|
49
50
|
}
|
|
50
|
-
/*
|
|
51
|
-
same repository apart, without the home directory nobody needs to read again. */
|
|
51
|
+
/* Enough of a path's tail to tell two checkouts of the same repository apart, under a row that already carries the name. */
|
|
52
52
|
export function shortPath(path) {
|
|
53
53
|
const parts = path.split('/').filter(Boolean);
|
|
54
54
|
return parts.length <= 3 ? path : `…/${parts.slice(-3).join('/')}`;
|
package/dist/usage/money.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useUsage } from '../state/usage.js';
|
|
3
3
|
import { moneyFormat } from './format.js';
|
|
4
|
-
/*
|
|
5
|
-
* The formatter every amount on the page goes through: the currency that was chosen, at the rate
|
|
6
|
-
* the summary on screen carries. It is a hook rather than a prop because every row wants it and
|
|
7
|
-
* none of them wants to know where the rate came from.
|
|
8
|
-
*/
|
|
4
|
+
/* The formatter every amount on the page goes through: the chosen currency, at the rate the summary on screen carries. */
|
|
9
5
|
export function useMoney() {
|
|
10
6
|
const currency = useUsage((s) => s.currency);
|
|
11
7
|
const rate = useUsage((s) => s.summary?.rate ?? null);
|
package/dist/usage/summary.js
CHANGED
|
@@ -74,10 +74,8 @@ export function deriveUsage(summary, metric) {
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
/*
|
|
77
|
-
* The
|
|
78
|
-
*
|
|
79
|
-
* happened in is left out, since the chart already shows the gap, and a quiet fortnight would otherwise
|
|
80
|
-
* be fourteen rows of zeroes between the days worth reading.
|
|
77
|
+
* The chart's buckets as a table of calendar days, newest first; an hour slot folds onto the date it
|
|
78
|
+
* names. A day nothing happened in is left out, since the chart already shows the gap.
|
|
81
79
|
*/
|
|
82
80
|
export function deriveDays(summary) {
|
|
83
81
|
const rows = new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -1011,8 +1011,8 @@
|
|
|
1011
1011
|
"test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
|
|
1012
1012
|
},
|
|
1013
1013
|
"dependencies": {
|
|
1014
|
-
"@adecore/agent-contracts": "0.21.
|
|
1015
|
-
"@adecore/intelligent-ui": "0.21.
|
|
1014
|
+
"@adecore/agent-contracts": "0.21.2",
|
|
1015
|
+
"@adecore/intelligent-ui": "0.21.2",
|
|
1016
1016
|
"@codemirror/commands": "^6.11.1",
|
|
1017
1017
|
"@codemirror/language": "^6.12.4",
|
|
1018
1018
|
"@codemirror/state": "^6.7.6",
|
|
@@ -1033,7 +1033,7 @@
|
|
|
1033
1033
|
"zustand": "^5.0.15"
|
|
1034
1034
|
},
|
|
1035
1035
|
"peerDependencies": {
|
|
1036
|
-
"@adecore/ui": "^0.21.
|
|
1036
|
+
"@adecore/ui": "^0.21.2",
|
|
1037
1037
|
"i18next": "^26.4.2",
|
|
1038
1038
|
"react": "^19.3.0",
|
|
1039
1039
|
"react-dom": "^19.3.0",
|