@tangle-network/ui 11.50.3 → 11.51.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/CHANGELOG.md +10 -0
- package/dist/chat/agent-timeline.d.ts +26 -1
- package/dist/chat/agent-timeline.js +76 -3
- package/dist/chat/user-message.d.ts +6 -1
- package/dist/chat/user-message.js +61 -15
- package/dist/chat.d.ts +2 -2
- package/dist/chat.js +2 -2
- package/dist/files/file-tabs.js +30 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/primitives/segmented-control.js +1 -1
- package/dist/run/run-row-shell.js +2 -2
- package/package.json +1 -1
- package/src/chat/agent-timeline.stories.tsx +39 -0
- package/src/chat/agent-timeline.test.tsx +24 -0
- package/src/chat/agent-timeline.tsx +103 -1
- package/src/chat/index.ts +2 -0
- package/src/chat/user-message.tsx +64 -14
- package/src/files/file-tabs.stories.tsx +28 -0
- package/src/files/file-tabs.test.tsx +17 -0
- package/src/files/file-tabs.tsx +31 -2
- package/src/primitives/segmented-control.tsx +1 -1
- package/src/run/run-row-shell.tsx +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 11.51.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ee52171: AgentTimeline folds a run of steps into one row: a `fold` item names what its tool calls did ("Read 6 files · Ran 2 commands", `foldSummary`) and opens to the rows; a failed call keeps the row in the failure ink with its error under it. A user bubble can fold a long message to its first `collapsedLines` lines, with a fade and a "Show more" inside the bubble. The bubble's hover time reads at 14px.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- d5798a3: FileTabs tells two open files with one name apart by the nearest folder that differs ("CHECK.md · route-a-lead-2"), and an inactive tab keeps a tab's slot with a 1px divider. A SegmentedControl's count reads at 14px in tabular figures.
|
|
12
|
+
|
|
3
13
|
## 11.50.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -11,6 +11,9 @@ export interface AgentTimelineMessageItem {
|
|
|
11
11
|
content: string;
|
|
12
12
|
toolCalls?: ReactNode;
|
|
13
13
|
isStreaming?: boolean;
|
|
14
|
+
/** A user bubble longer than this many lines shows them with a fade and a
|
|
15
|
+
* "Show more" inside the bubble; omit to show the whole message. */
|
|
16
|
+
collapsedLines?: number;
|
|
14
17
|
/** Shown beside a user bubble on hover. Assistant prose carries no
|
|
15
18
|
* timestamp of its own — the turn's user message dates the exchange. */
|
|
16
19
|
timestamp?: Date;
|
|
@@ -39,6 +42,22 @@ export interface AgentTimelineToolGroupItem {
|
|
|
39
42
|
/** Source tool parts, parallel to `calls`. */
|
|
40
43
|
parts?: ToolPart[];
|
|
41
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* A run of steps folded into one quiet row that names what they did ("Read 6
|
|
47
|
+
* files · Ran 2 commands") and opens to the rows themselves, as an agent app
|
|
48
|
+
* folds a settled multi-step turn. A failed call keeps the fold in the failure
|
|
49
|
+
* ink with its error under the row, so a failure never hides behind it.
|
|
50
|
+
*/
|
|
51
|
+
export interface AgentTimelineFoldItem {
|
|
52
|
+
id: string;
|
|
53
|
+
kind: "fold";
|
|
54
|
+
/** The folded rows in order: tool calls, reasoning and other run rows. */
|
|
55
|
+
items: AgentTimelineItem[];
|
|
56
|
+
/** The row's words; by default what its tool calls did (`foldSummary`). */
|
|
57
|
+
summary?: string;
|
|
58
|
+
/** Start open, e.g. when a link points at one of its steps. */
|
|
59
|
+
defaultOpen?: boolean;
|
|
60
|
+
}
|
|
42
61
|
export interface AgentTimelineStatusItem {
|
|
43
62
|
id: string;
|
|
44
63
|
kind: "status";
|
|
@@ -65,7 +84,7 @@ export interface AgentTimelineCustomItem {
|
|
|
65
84
|
* edge. */
|
|
66
85
|
content: ReactNode;
|
|
67
86
|
}
|
|
68
|
-
export type AgentTimelineItem = AgentTimelineMessageItem | AgentTimelineToolItem | AgentTimelineToolGroupItem | AgentTimelineStatusItem | AgentTimelineArtifactItem | AgentTimelineCustomItem;
|
|
87
|
+
export type AgentTimelineItem = AgentTimelineMessageItem | AgentTimelineToolItem | AgentTimelineToolGroupItem | AgentTimelineFoldItem | AgentTimelineStatusItem | AgentTimelineArtifactItem | AgentTimelineCustomItem;
|
|
69
88
|
export interface AgentTimelineProps {
|
|
70
89
|
items: AgentTimelineItem[];
|
|
71
90
|
isThinking?: boolean;
|
|
@@ -93,6 +112,12 @@ export interface AgentTimelineProps {
|
|
|
93
112
|
export type AgentTimelineStepKind = "user" | "prose" | "tool" | "card";
|
|
94
113
|
/** What a row is, for the gap above the next one. */
|
|
95
114
|
export declare function agentTimelineStepKind(item: AgentTimelineItem): AgentTimelineStepKind;
|
|
115
|
+
/**
|
|
116
|
+
* What a fold's tool calls did, by verb in the order they first ran:
|
|
117
|
+
* "Read 6 files · Ran 2 commands". A fold without tool calls reads by its
|
|
118
|
+
* row count.
|
|
119
|
+
*/
|
|
120
|
+
export declare function foldSummary(items: readonly AgentTimelineItem[]): string;
|
|
96
121
|
/** The transcript column every timeline sits in: centred, at the reading width. */
|
|
97
122
|
export declare function AgentTimelineColumn({ className, children }: {
|
|
98
123
|
className?: string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { focusRing } from "../lib/focus.js";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { TextShimmer } from "../primitives/text-shimmer.js";
|
|
3
4
|
import { statusMeta } from "../run/status-meta.js";
|
|
5
|
+
import { RunRowShell } from "../run/run-row-shell.js";
|
|
4
6
|
import { Markdown } from "../markdown/markdown.js";
|
|
5
7
|
import "./transcript-text.js";
|
|
6
8
|
import { MessageAuthor } from "./message-author.js";
|
|
@@ -9,7 +11,7 @@ import { ThinkingIndicator } from "./thinking-indicator.js";
|
|
|
9
11
|
import { ToolCallGroup, ToolCallStep } from "../run/tool-call-step.js";
|
|
10
12
|
import { useState } from "react";
|
|
11
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
-
import { AlertTriangle, CircleDot, FileText, Info } from "lucide-react";
|
|
14
|
+
import { AlertTriangle, CircleDot, FileText, Info, Layers } from "lucide-react";
|
|
13
15
|
// src/chat/agent-timeline.tsx
|
|
14
16
|
const TONE_STYLES = {
|
|
15
17
|
default: {
|
|
@@ -153,6 +155,71 @@ function ArtifactCard({ item }) {
|
|
|
153
155
|
children: content
|
|
154
156
|
});
|
|
155
157
|
}
|
|
158
|
+
const FOLD_VERBS = {
|
|
159
|
+
read: (n) => `Read ${n} ${n === 1 ? "file" : "files"}`,
|
|
160
|
+
write: (n) => `Wrote ${n} ${n === 1 ? "file" : "files"}`,
|
|
161
|
+
edit: (n) => `Edited ${n} ${n === 1 ? "file" : "files"}`,
|
|
162
|
+
bash: (n) => `Ran ${n} ${n === 1 ? "command" : "commands"}`,
|
|
163
|
+
grep: (n) => `Searched ${n} ${n === 1 ? "time" : "times"}`,
|
|
164
|
+
glob: (n) => `Matched ${n} ${n === 1 ? "pattern" : "patterns"}`,
|
|
165
|
+
list: (n) => `Listed ${n} ${n === 1 ? "folder" : "folders"}`,
|
|
166
|
+
download: (n) => `Downloaded ${n} ${n === 1 ? "file" : "files"}`,
|
|
167
|
+
inspect: (n) => `Inspected ${n} ${n === 1 ? "item" : "items"}`,
|
|
168
|
+
audit: (n) => `Audited ${n} ${n === 1 ? "item" : "items"}`,
|
|
169
|
+
unknown: (n) => `Used ${n} ${n === 1 ? "tool" : "tools"}`
|
|
170
|
+
};
|
|
171
|
+
/** The tool calls inside a fold, in order (a tool group's calls included). */
|
|
172
|
+
function foldCalls(items) {
|
|
173
|
+
return items.flatMap((item) => item.kind === "tool" ? [{
|
|
174
|
+
call: item.call,
|
|
175
|
+
part: item.part
|
|
176
|
+
}] : item.kind === "tool_group" ? item.calls.map((call, i) => ({
|
|
177
|
+
call,
|
|
178
|
+
part: item.parts?.[i]
|
|
179
|
+
})) : item.kind === "fold" ? foldCalls(item.items) : []);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* What a fold's tool calls did, by verb in the order they first ran:
|
|
183
|
+
* "Read 6 files · Ran 2 commands". A fold without tool calls reads by its
|
|
184
|
+
* row count.
|
|
185
|
+
*/
|
|
186
|
+
function foldSummary(items) {
|
|
187
|
+
const counts = /* @__PURE__ */ new Map();
|
|
188
|
+
for (const { call } of foldCalls(items)) counts.set(call.type, (counts.get(call.type) ?? 0) + 1);
|
|
189
|
+
if (!counts.size) return `${items.length} ${items.length === 1 ? "step" : "steps"}`;
|
|
190
|
+
const phrases = [...counts].map(([type, n]) => FOLD_VERBS[type](n));
|
|
191
|
+
return phrases.length > 3 ? `${phrases.slice(0, 3).join(" · ")} · ${phrases.length - 3} more` : phrases.join(" · ");
|
|
192
|
+
}
|
|
193
|
+
function FoldStep({ item, renderToolActions, viewerId }) {
|
|
194
|
+
const calls = foldCalls(item.items);
|
|
195
|
+
const failed = calls.filter(({ call }) => call.status === "error");
|
|
196
|
+
const running = calls.some(({ call }) => call.status === "running");
|
|
197
|
+
const firstError = failed.map(({ part }) => part?.state.status === "error" ? part.state.error : void 0).find(Boolean);
|
|
198
|
+
const total = calls.reduce((sum, { call }) => sum + (call.duration ?? 0), 0);
|
|
199
|
+
const title = item.summary ?? foldSummary(item.items);
|
|
200
|
+
const targets = [...new Set(calls.map(({ call }) => call.detail).filter((detail) => Boolean(detail)))];
|
|
201
|
+
return /* @__PURE__ */ jsx(RunRowShell, {
|
|
202
|
+
icon: /* @__PURE__ */ jsx(Layers, { className: "h-3.5 w-3.5" }),
|
|
203
|
+
title: running ? /* @__PURE__ */ jsx(TextShimmer, { children: title }) : title,
|
|
204
|
+
description: targets.join(", ") || void 0,
|
|
205
|
+
descriptionMono: true,
|
|
206
|
+
status: failed.length ? "error" : running ? "running" : "success",
|
|
207
|
+
durationMs: total > 0 && !running ? total : void 0,
|
|
208
|
+
collapsedError: failed.length ? `${failed.length} failed${firstError ? `: ${firstError}` : ""}` : void 0,
|
|
209
|
+
defaultOpen: item.defaultOpen,
|
|
210
|
+
contentClassName: "px-3 py-1.5",
|
|
211
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
212
|
+
"data-timeline-fold": "",
|
|
213
|
+
className: "flex flex-col",
|
|
214
|
+
children: item.items.map((inner, index) => /* @__PURE__ */ jsx(AgentTimelineStep, {
|
|
215
|
+
item: inner,
|
|
216
|
+
previous: index > 0 ? agentTimelineStepKind(item.items[index - 1]) : void 0,
|
|
217
|
+
renderToolActions,
|
|
218
|
+
viewerId
|
|
219
|
+
}, inner.id))
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
}
|
|
156
223
|
/** The transcript column every timeline sits in: centred, at the reading width. */
|
|
157
224
|
function AgentTimelineColumn({ className, children }) {
|
|
158
225
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -183,7 +250,8 @@ function TimelineItem({ item, headed, renderToolActions, viewerId }) {
|
|
|
183
250
|
content: item.content,
|
|
184
251
|
timestamp: item.timestamp,
|
|
185
252
|
author: item.author,
|
|
186
|
-
viewerId
|
|
253
|
+
viewerId,
|
|
254
|
+
collapsedLines: item.collapsedLines
|
|
187
255
|
}) : /* @__PURE__ */ jsx(AssistantMessage, {
|
|
188
256
|
item,
|
|
189
257
|
author: headed ? item.author : void 0
|
|
@@ -214,6 +282,11 @@ function TimelineItem({ item, headed, renderToolActions, viewerId }) {
|
|
|
214
282
|
}, call.id);
|
|
215
283
|
})
|
|
216
284
|
});
|
|
285
|
+
if (item.kind === "fold") return /* @__PURE__ */ jsx(FoldStep, {
|
|
286
|
+
item,
|
|
287
|
+
renderToolActions,
|
|
288
|
+
viewerId
|
|
289
|
+
});
|
|
217
290
|
if (item.kind === "status") return /* @__PURE__ */ jsx(StatusCard, { item });
|
|
218
291
|
if (item.kind === "artifact") return /* @__PURE__ */ jsx(ArtifactCard, { item });
|
|
219
292
|
return item.content;
|
|
@@ -280,4 +353,4 @@ function AgentTimeline({ items, isThinking, emptyState, className, renderToolAct
|
|
|
280
353
|
}) : null]
|
|
281
354
|
});
|
|
282
355
|
}
|
|
283
|
-
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, agentTimelineStepKind };
|
|
356
|
+
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, agentTimelineStepKind, foldSummary };
|
|
@@ -19,9 +19,14 @@ export interface UserMessageProps {
|
|
|
19
19
|
* someone else's: it sits at the start, on a card, under its author.
|
|
20
20
|
*/
|
|
21
21
|
viewerId?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Show a long message's first lines with a fade and a "Show more" inside the
|
|
24
|
+
* bubble, which opens it in place. Omit to show the whole message.
|
|
25
|
+
*/
|
|
26
|
+
collapsedLines?: number;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* User speech retains the inverse-theme bubble and direct-content contract.
|
|
25
30
|
* Application notes interrupt the bubble in source order, outside its attribution.
|
|
26
31
|
*/
|
|
27
|
-
export declare const UserMessage: import("react").MemoExoticComponent<({ message: _message, parts, content, timestamp, actions, author, viewerId }: UserMessageProps) => import("react").JSX.Element | null>;
|
|
32
|
+
export declare const UserMessage: import("react").MemoExoticComponent<({ message: _message, parts, content, timestamp, actions, author, viewerId, collapsedLines }: UserMessageProps) => import("react").JSX.Element | null>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SyntheticText, userTextSegments } from "./synthetic-text.js";
|
|
2
2
|
import { TRANSCRIPT_TEXT } from "./transcript-text.js";
|
|
3
3
|
import { MessageAuthor, isViewerMessage } from "./message-author.js";
|
|
4
|
-
import { memo } from "react";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { memo, useLayoutEffect, useRef, useState } from "react";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
// src/chat/user-message.tsx
|
|
7
7
|
function formatTime(date) {
|
|
8
8
|
return date.toLocaleTimeString(void 0, {
|
|
@@ -16,7 +16,51 @@ function MessageActions({ children, align = "end" }) {
|
|
|
16
16
|
children
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* A message body that can fold to its first `lines` lines: the last shown line
|
|
21
|
+
* fades out and a "Show more" inside the bubble opens the rest in place. The
|
|
22
|
+
* button appears only when the text overflows.
|
|
23
|
+
*/
|
|
24
|
+
function BubbleText({ text, lines }) {
|
|
25
|
+
const body = useRef(null);
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
const [over, setOver] = useState(false);
|
|
28
|
+
useLayoutEffect(() => {
|
|
29
|
+
const element = body.current;
|
|
30
|
+
if (!element || !lines || open) return;
|
|
31
|
+
const measure = () => setOver(element.scrollHeight > element.clientHeight + 1);
|
|
32
|
+
measure();
|
|
33
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
34
|
+
const observer = new ResizeObserver(measure);
|
|
35
|
+
observer.observe(element);
|
|
36
|
+
return () => observer.disconnect();
|
|
37
|
+
}, [
|
|
38
|
+
lines,
|
|
39
|
+
open,
|
|
40
|
+
text
|
|
41
|
+
]);
|
|
42
|
+
const folded = Boolean(lines) && !open;
|
|
43
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
44
|
+
ref: body,
|
|
45
|
+
className: `break-words whitespace-pre-wrap ${TRANSCRIPT_TEXT}`,
|
|
46
|
+
style: folded ? {
|
|
47
|
+
maxHeight: `${lines}lh`,
|
|
48
|
+
overflow: "hidden",
|
|
49
|
+
...over ? {
|
|
50
|
+
maskImage: "linear-gradient(to bottom, #000 calc(100% - 1.5lh), transparent)",
|
|
51
|
+
WebkitMaskImage: "linear-gradient(to bottom, #000 calc(100% - 1.5lh), transparent)"
|
|
52
|
+
} : {}
|
|
53
|
+
} : void 0,
|
|
54
|
+
children: text
|
|
55
|
+
}), lines && (over || open) ? /* @__PURE__ */ jsx("button", {
|
|
56
|
+
type: "button",
|
|
57
|
+
"aria-expanded": open,
|
|
58
|
+
onClick: () => setOpen(!open),
|
|
59
|
+
className: "mt-1.5 text-sm font-medium underline-offset-2 opacity-75 transition-opacity hover:underline hover:opacity-100",
|
|
60
|
+
children: open ? "Show less" : "Show more"
|
|
61
|
+
}) : null] });
|
|
62
|
+
}
|
|
63
|
+
function UserBubble({ text, timestamp, actions, lines }) {
|
|
20
64
|
return /* @__PURE__ */ jsx("div", {
|
|
21
65
|
className: "flex justify-end",
|
|
22
66
|
children: /* @__PURE__ */ jsxs("div", {
|
|
@@ -25,11 +69,11 @@ function UserBubble({ text, timestamp, actions }) {
|
|
|
25
69
|
className: "relative w-full min-w-0 rounded-2xl bg-foreground px-4 py-3 text-background",
|
|
26
70
|
children: [timestamp ? /* @__PURE__ */ jsx("span", {
|
|
27
71
|
"data-user-message-time": "",
|
|
28
|
-
className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-
|
|
72
|
+
className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-sm text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
|
|
29
73
|
children: formatTime(timestamp)
|
|
30
|
-
}) : null, /* @__PURE__ */ jsx(
|
|
31
|
-
|
|
32
|
-
|
|
74
|
+
}) : null, /* @__PURE__ */ jsx(BubbleText, {
|
|
75
|
+
text,
|
|
76
|
+
lines
|
|
33
77
|
})]
|
|
34
78
|
}), actions ? /* @__PURE__ */ jsx(MessageActions, { children: actions }) : null]
|
|
35
79
|
})
|
|
@@ -40,7 +84,7 @@ function UserBubble({ text, timestamp, actions }) {
|
|
|
40
84
|
* than the reader's inverse fill, so "mine" and "theirs" differ before the
|
|
41
85
|
* names are read. The timestamp is revealed on the bubble's far side.
|
|
42
86
|
*/
|
|
43
|
-
function OtherPersonBubble({ text, timestamp, actions, author }) {
|
|
87
|
+
function OtherPersonBubble({ text, timestamp, actions, author, lines }) {
|
|
44
88
|
return /* @__PURE__ */ jsx("div", {
|
|
45
89
|
className: "flex justify-start",
|
|
46
90
|
children: /* @__PURE__ */ jsxs("div", {
|
|
@@ -51,11 +95,11 @@ function OtherPersonBubble({ text, timestamp, actions, author }) {
|
|
|
51
95
|
className: "relative min-w-0 max-w-full rounded-2xl border border-border bg-card px-4 py-3 text-foreground",
|
|
52
96
|
children: [timestamp ? /* @__PURE__ */ jsx("span", {
|
|
53
97
|
"data-user-message-time": "",
|
|
54
|
-
className: "pointer-events-none absolute left-full top-1/2 ml-3 -translate-y-1/2 whitespace-nowrap text-
|
|
98
|
+
className: "pointer-events-none absolute left-full top-1/2 ml-3 -translate-y-1/2 whitespace-nowrap text-sm text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
|
|
55
99
|
children: formatTime(timestamp)
|
|
56
|
-
}) : null, /* @__PURE__ */ jsx(
|
|
57
|
-
|
|
58
|
-
|
|
100
|
+
}) : null, /* @__PURE__ */ jsx(BubbleText, {
|
|
101
|
+
text,
|
|
102
|
+
lines
|
|
59
103
|
})]
|
|
60
104
|
}),
|
|
61
105
|
actions ? /* @__PURE__ */ jsx(MessageActions, {
|
|
@@ -70,17 +114,19 @@ function OtherPersonBubble({ text, timestamp, actions, author }) {
|
|
|
70
114
|
* User speech retains the inverse-theme bubble and direct-content contract.
|
|
71
115
|
* Application notes interrupt the bubble in source order, outside its attribution.
|
|
72
116
|
*/
|
|
73
|
-
const UserMessage = memo(({ message: _message, parts, content, timestamp, actions, author, viewerId }) => {
|
|
117
|
+
const UserMessage = memo(({ message: _message, parts, content, timestamp, actions, author, viewerId, collapsedLines }) => {
|
|
74
118
|
const other = isViewerMessage(author, viewerId) ? void 0 : author;
|
|
75
119
|
const bubble = (text, bubbleActions, headed = true, key) => other ? /* @__PURE__ */ jsx(OtherPersonBubble, {
|
|
76
120
|
text,
|
|
77
121
|
timestamp,
|
|
78
122
|
actions: bubbleActions,
|
|
79
|
-
author: headed ? other : void 0
|
|
123
|
+
author: headed ? other : void 0,
|
|
124
|
+
lines: collapsedLines
|
|
80
125
|
}, key) : /* @__PURE__ */ jsx(UserBubble, {
|
|
81
126
|
text,
|
|
82
127
|
timestamp,
|
|
83
|
-
actions: bubbleActions
|
|
128
|
+
actions: bubbleActions,
|
|
129
|
+
lines: collapsedLines
|
|
84
130
|
}, key);
|
|
85
131
|
if (content != null) return content.trim() ? bubble(content, actions) : null;
|
|
86
132
|
const segments = userTextSegments(parts ?? []);
|
package/dist/chat.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ import { ChatMessage, ChatMessageProps, MessageRole } from "./chat/chat-message.
|
|
|
6
6
|
import { MessageAuthor, MessageAuthorProps, isViewerMessage } from "./chat/message-author.js";
|
|
7
7
|
import { Speaker, SpeakerHeader, SpeakerHeaderProps, SpeakerPart, SpeakerSection, SpeakerState, partitionBySpeaker } from "./chat/speaker.js";
|
|
8
8
|
import { ThinkingIndicator, ThinkingIndicatorProps } from "./chat/thinking-indicator.js";
|
|
9
|
-
import { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineColumn, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineStep, AgentTimelineStepKind, AgentTimelineStepProps, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, agentTimelineStepKind } from "./chat/agent-timeline.js";
|
|
9
|
+
import { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineColumn, AgentTimelineCustomItem, AgentTimelineFoldItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineStep, AgentTimelineStepKind, AgentTimelineStepProps, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, agentTimelineStepKind, foldSummary } from "./chat/agent-timeline.js";
|
|
10
10
|
import { TRANSCRIPT_TEXT } from "./chat/transcript-text.js";
|
|
11
|
-
export { AgentTimeline, type AgentTimelineArtifactItem, AgentTimelineColumn, type AgentTimelineCustomItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, AgentTimelineStep, type AgentTimelineStepKind, type AgentTimelineStepProps, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, type ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, type Speaker, SpeakerHeader, type SpeakerHeaderProps, type SpeakerPart, type SpeakerSection, type SpeakerState, TRANSCRIPT_TEXT, ThinkingIndicator, type ThinkingIndicatorProps, UserMessage, type UserMessageProps, agentTimelineStepKind, isViewerMessage, partitionBySpeaker };
|
|
11
|
+
export { AgentTimeline, type AgentTimelineArtifactItem, AgentTimelineColumn, type AgentTimelineCustomItem, type AgentTimelineFoldItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, AgentTimelineStep, type AgentTimelineStepKind, type AgentTimelineStepProps, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, type ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, type Speaker, SpeakerHeader, type SpeakerHeaderProps, type SpeakerPart, type SpeakerSection, type SpeakerState, TRANSCRIPT_TEXT, ThinkingIndicator, type ThinkingIndicatorProps, UserMessage, type UserMessageProps, agentTimelineStepKind, foldSummary, isViewerMessage, partitionBySpeaker };
|
package/dist/chat.js
CHANGED
|
@@ -3,8 +3,8 @@ import { MessageAuthor, isViewerMessage } from "./chat/message-author.js";
|
|
|
3
3
|
import { UserMessage } from "./chat/user-message.js";
|
|
4
4
|
import { MessageList } from "./chat/message-list.js";
|
|
5
5
|
import { ThinkingIndicator } from "./chat/thinking-indicator.js";
|
|
6
|
-
import { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, agentTimelineStepKind } from "./chat/agent-timeline.js";
|
|
6
|
+
import { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, agentTimelineStepKind, foldSummary } from "./chat/agent-timeline.js";
|
|
7
7
|
import { ChatContainer } from "./chat/chat-container.js";
|
|
8
8
|
import { ChatMessage } from "./chat/chat-message.js";
|
|
9
9
|
import { SpeakerHeader, partitionBySpeaker } from "./chat/speaker.js";
|
|
10
|
-
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, ChatContainer, ChatMessage, MessageAuthor, MessageList, SpeakerHeader, TRANSCRIPT_TEXT, ThinkingIndicator, UserMessage, agentTimelineStepKind, isViewerMessage, partitionBySpeaker };
|
|
10
|
+
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, ChatContainer, ChatMessage, MessageAuthor, MessageList, SpeakerHeader, TRANSCRIPT_TEXT, ThinkingIndicator, UserMessage, agentTimelineStepKind, foldSummary, isViewerMessage, partitionBySpeaker };
|
package/dist/files/file-tabs.js
CHANGED
|
@@ -18,6 +18,26 @@ function getTabIcon(name) {
|
|
|
18
18
|
if (["csv", "xlsx"].includes(ext)) return FileSpreadsheet;
|
|
19
19
|
return FileCode;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Each tab's folder hint: two open files with one name ("CHECK.md" twice) each show the nearest parent folder that
|
|
23
|
+
* tells them apart, as an editor does ("CHECK.md · route-a-lead-2"). A name no other tab shares shows none.
|
|
24
|
+
*/
|
|
25
|
+
function folderHints(tabs) {
|
|
26
|
+
const hints = /* @__PURE__ */ new Map();
|
|
27
|
+
const byName = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const tab of tabs) byName.set(tab.name, [...byName.get(tab.name) ?? [], tab]);
|
|
29
|
+
for (const same of byName.values()) {
|
|
30
|
+
if (same.length < 2) continue;
|
|
31
|
+
const parents = same.map((tab) => tab.path.replace(/\/+$/, "").split("/").slice(0, -1).reverse());
|
|
32
|
+
same.forEach((tab, i) => {
|
|
33
|
+
const own = parents[i];
|
|
34
|
+
const depth = own.findIndex((folder, d) => parents.every((other, j) => j === i || other[d] !== folder));
|
|
35
|
+
const hint = own[depth >= 0 ? depth : 0];
|
|
36
|
+
if (hint) hints.set(tab.id, hint);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return hints;
|
|
40
|
+
}
|
|
21
41
|
/** A long name keeps its extension and the characters before it after the ellipsis. */
|
|
22
42
|
function splitName(name) {
|
|
23
43
|
if (name.length <= 18) return [name, ""];
|
|
@@ -33,6 +53,7 @@ function FileTabs({ tabs, activeId, onSelect, onClose, label = "Open files", cla
|
|
|
33
53
|
if (strip && tab) strip.scrollLeft = Math.min(tab.offsetLeft, Math.max(strip.scrollLeft, tab.offsetLeft + tab.offsetWidth - strip.clientWidth));
|
|
34
54
|
}, [activeId, tabs.length]);
|
|
35
55
|
if (tabs.length === 0) return null;
|
|
56
|
+
const hints = folderHints(tabs);
|
|
36
57
|
const tabButtons = () => [...stripRef.current?.querySelectorAll("[role=\"tab\"]") ?? []];
|
|
37
58
|
const onKeyDown = (event) => {
|
|
38
59
|
const at = tabButtons().indexOf(event.target);
|
|
@@ -59,18 +80,20 @@ function FileTabs({ tabs, activeId, onSelect, onClose, label = "Open files", cla
|
|
|
59
80
|
"aria-label": label,
|
|
60
81
|
onKeyDown,
|
|
61
82
|
className: cn("relative flex h-10 min-w-0 items-end gap-1 overflow-x-auto px-2 shadow-[inset_0_-1px_0_hsl(var(--border))] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", className),
|
|
62
|
-
children: tabs.map((tab) => {
|
|
83
|
+
children: tabs.map((tab, index) => {
|
|
63
84
|
const isActive = tab.id === activeId;
|
|
85
|
+
const divided = !isActive && tabs[index + 1] !== void 0 && tabs[index + 1].id !== activeId;
|
|
86
|
+
const hint = hints.get(tab.id);
|
|
64
87
|
const Icon = getTabIcon(tab.name);
|
|
65
88
|
const [head, tail] = splitName(tab.name);
|
|
66
89
|
return /* @__PURE__ */ jsxs("div", {
|
|
67
90
|
title: tab.path,
|
|
68
|
-
className: cn("group relative flex h-[calc(100%-0.375rem)] max-w-72 shrink-0 items-center rounded-t-lg border border-b-0 text-sm", controlMotion, isActive ? "border-border bg-card text-foreground" : "border-transparent text-muted-foreground hover:bg-[var(--ix-hover-bg)] hover:text-foreground"),
|
|
91
|
+
className: cn("group relative flex h-[calc(100%-0.375rem)] max-w-72 shrink-0 items-center rounded-t-lg border border-b-0 text-sm", controlMotion, isActive ? "border-border bg-card text-foreground" : "border-transparent text-muted-foreground hover:bg-[var(--ix-hover-bg)] hover:text-foreground", divided && "after:absolute after:-right-[3px] after:top-1/2 after:h-4 after:w-px after:-translate-y-1/2 after:bg-border"),
|
|
69
92
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
70
93
|
type: "button",
|
|
71
94
|
role: "tab",
|
|
72
95
|
"aria-selected": isActive,
|
|
73
|
-
"aria-label": tab.
|
|
96
|
+
"aria-label": `${tab.name}${hint ? ` in ${hint}` : ""}${tab.dirty ? ", unsaved changes" : ""}`,
|
|
74
97
|
tabIndex: isActive || !activeId && tab === tabs[0] ? 0 : -1,
|
|
75
98
|
onClick: () => onSelect(tab.id),
|
|
76
99
|
className: cn("flex h-full min-w-0 items-center gap-1.5 rounded-t-lg pl-3 pr-1", focusRingInset),
|
|
@@ -89,6 +112,10 @@ function FileTabs({ tabs, activeId, onSelect, onClose, label = "Open files", cla
|
|
|
89
112
|
children: tail
|
|
90
113
|
})]
|
|
91
114
|
}),
|
|
115
|
+
hint && /* @__PURE__ */ jsxs("span", {
|
|
116
|
+
className: "min-w-0 truncate text-muted-foreground",
|
|
117
|
+
children: ["· ", hint]
|
|
118
|
+
}),
|
|
92
119
|
tab.dirty && /* @__PURE__ */ jsx("span", {
|
|
93
120
|
"aria-hidden": true,
|
|
94
121
|
className: "size-1.5 shrink-0 rounded-full bg-[var(--accent-text)]"
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { Speaker, SpeakerHeader, SpeakerHeaderProps, SpeakerPart, SpeakerSection
|
|
|
20
20
|
import { ThinkingIndicator, ThinkingIndicatorProps } from "./chat/thinking-indicator.js";
|
|
21
21
|
import { ToolCallStatus, ToolCallType } from "./run/tool-call-step.js";
|
|
22
22
|
import { FeedSegment, ToolCallData, ToolCallFeed, ToolCallFeedProps, parseToolEvent } from "./run/tool-call-feed.js";
|
|
23
|
-
import { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineColumn, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineStep, AgentTimelineStepKind, AgentTimelineStepProps, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, agentTimelineStepKind } from "./chat/agent-timeline.js";
|
|
23
|
+
import { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineColumn, AgentTimelineCustomItem, AgentTimelineFoldItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineStep, AgentTimelineStepKind, AgentTimelineStepProps, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, agentTimelineStepKind, foldSummary } from "./chat/agent-timeline.js";
|
|
24
24
|
import { TRANSCRIPT_TEXT } from "./chat/transcript-text.js";
|
|
25
25
|
import "./chat.js";
|
|
26
26
|
import { codeBlockGrammar } from "./lib/code-presentation.js";
|
|
@@ -145,4 +145,4 @@ import { QuestionPreview, QuestionPreviewProps } from "./tool-previews/question-
|
|
|
145
145
|
import { DiffPreview, DiffPreviewProps } from "./tool-previews/diff-preview.js";
|
|
146
146
|
import { RedactedDocSegment, RedactedDocument, RedactedDocumentData, RedactedDocumentProps, RevealResult } from "./redaction/redacted-document.js";
|
|
147
147
|
import "./redaction.js";
|
|
148
|
-
export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, AgentTimelineColumn, type AgentTimelineCustomItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, AgentTimelineStep, type AgentTimelineStepKind, type AgentTimelineStepProps, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ApiSchema, type ApiSchemaProps, AppendUserMessageOptions, ApplySdkEventOptions, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, type ChartReferenceLine, ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ComplianceBadge, type ComplianceBadgeProps, type ComplianceProduct, type ComplianceStatus, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, CustomToolRenderer, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, Field, type FieldProps, FileArtifactPane, type FileArtifactPaneProps, FileCard, type FileCardProps, type FileDetails, type FileFormat, FileList, type FileListItem, type FileListProps, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, type FileUploader, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, MetricChart, type MetricChartProps, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, PdfViewer, type PdfViewerConfig, type PdfViewerProps, type PeriodDelta, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RadioGroup, RadioGroupItem, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, type RunStatus, RunStatusIcon, type RunStatusIconProps, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeriesExtent, type SeriesPoint, type SeriesPointInput, type SeriesScale, SeriesSparkline, type SeriesSparklineProps, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, SettingsList, type SettingsListAction, type SettingsListColumns, SettingsListItem, type SettingsListItemProps, type SettingsListMenuItem, type SettingsListProps, SettingsNote, type SettingsNoteProps, SettingsRow, type SettingsRowLayout, type SettingsRowProps, SettingsSection, type SettingsSectionProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, type Speaker, SpeakerHeader, type SpeakerHeaderProps, type SpeakerPart, type SpeakerSection, type SpeakerState, StatCard, type StatCardProps, type StatusMeta, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
|
148
|
+
export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, AgentTimelineColumn, type AgentTimelineCustomItem, type AgentTimelineFoldItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, AgentTimelineStep, type AgentTimelineStepKind, type AgentTimelineStepProps, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ApiSchema, type ApiSchemaProps, AppendUserMessageOptions, ApplySdkEventOptions, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, type ChartReferenceLine, ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ComplianceBadge, type ComplianceBadgeProps, type ComplianceProduct, type ComplianceStatus, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, CustomToolRenderer, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, Field, type FieldProps, FileArtifactPane, type FileArtifactPaneProps, FileCard, type FileCardProps, type FileDetails, type FileFormat, FileList, type FileListItem, type FileListProps, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, type FileUploader, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, MetricChart, type MetricChartProps, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, PdfViewer, type PdfViewerConfig, type PdfViewerProps, type PeriodDelta, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RadioGroup, RadioGroupItem, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, type RunStatus, RunStatusIcon, type RunStatusIconProps, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeriesExtent, type SeriesPoint, type SeriesPointInput, type SeriesScale, SeriesSparkline, type SeriesSparklineProps, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, SettingsList, type SettingsListAction, type SettingsListColumns, SettingsListItem, type SettingsListItemProps, type SettingsListMenuItem, type SettingsListProps, SettingsNote, type SettingsNoteProps, SettingsRow, type SettingsRowLayout, type SettingsRowProps, SettingsSection, type SettingsSectionProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, type Speaker, SpeakerHeader, type SpeakerHeaderProps, type SpeakerPart, type SpeakerSection, type SpeakerState, StatCard, type StatCardProps, type StatusMeta, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, foldSummary, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
package/dist/index.js
CHANGED
|
@@ -96,7 +96,7 @@ import { MessageAuthor, isViewerMessage } from "./chat/message-author.js";
|
|
|
96
96
|
import { UserMessage } from "./chat/user-message.js";
|
|
97
97
|
import { MessageList } from "./chat/message-list.js";
|
|
98
98
|
import { ThinkingIndicator } from "./chat/thinking-indicator.js";
|
|
99
|
-
import { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, agentTimelineStepKind } from "./chat/agent-timeline.js";
|
|
99
|
+
import { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, agentTimelineStepKind, foldSummary } from "./chat/agent-timeline.js";
|
|
100
100
|
import { ChatContainer } from "./chat/chat-container.js";
|
|
101
101
|
import { ChatMessage } from "./chat/chat-message.js";
|
|
102
102
|
import { SpeakerHeader, partitionBySpeaker } from "./chat/speaker.js";
|
|
@@ -129,4 +129,4 @@ import { addMessage, addParts, clearChat, isStreamingAtom, messagesAtom, partMap
|
|
|
129
129
|
import { copyText } from "./utils/copy-text.js";
|
|
130
130
|
import { timeAgo } from "./utils/time-ago.js";
|
|
131
131
|
import { RedactedDocument } from "./redaction/redacted-document.js";
|
|
132
|
-
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ApiSchema, ApprovalCard, ApprovalDiffSummary, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ComplianceBadge, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, Field, FileArtifactPane, FileCard, FileList, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, HelpText, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageAuthor, MessageList, Metric, MetricChart, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Pagination, PdfViewer, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RadioGroup, RadioGroupItem, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesSparkline, SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, SpeakerHeader, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TaskList, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, TimeSelect, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, Wizard, WizardStep, WriteFilePreview, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
|
132
|
+
export { AgentTimeline, AgentTimelineColumn, AgentTimelineStep, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ApiSchema, ApprovalCard, ApprovalDiffSummary, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ComplianceBadge, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, Field, FileArtifactPane, FileCard, FileList, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, HelpText, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageAuthor, MessageList, Metric, MetricChart, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Pagination, PdfViewer, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RadioGroup, RadioGroupItem, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, RunStatusIcon, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesSparkline, SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, SpeakerHeader, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TaskList, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, TimeSelect, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, Wizard, WizardStep, WriteFilePreview, activeSessionsAtom, addMessage, addParts, agentTimelineStepKind, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fieldMessageId, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, foldSummary, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, normalizeSeries, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, partitionBySpeaker, periodDelta, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, runStatus, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, statusMeta, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
|
|
@@ -159,7 +159,7 @@ function SegmentedControl({ value, onValueChange, options, variant = "row", clas
|
|
|
159
159
|
"hover:shadow-[inset_0_-2px_0_hsl(var(--hsl-ring)/0.4)] data-[state=active]:bg-[hsl(var(--hsl-ring)/0.07)] data-[state=active]:text-[var(--accent-text)] data-[state=active]:shadow-[inset_0_-2px_0_var(--ix-sel-ring)] aria-checked:bg-[hsl(var(--hsl-ring)/0.07)] aria-checked:text-[var(--accent-text)] aria-checked:shadow-[inset_0_-2px_0_var(--ix-sel-ring)]"
|
|
160
160
|
], active ? "text-[var(--accent-text)]" : "text-muted-foreground hover:text-foreground"),
|
|
161
161
|
children: [/* @__PURE__ */ jsx("span", { children: option.label }), option.adornment && /* @__PURE__ */ jsx("span", {
|
|
162
|
-
className: cn("text-
|
|
162
|
+
className: cn("text-sm tabular-nums", active ? "text-[var(--accent-text)]/80" : "text-muted-foreground"),
|
|
163
163
|
children: option.adornment
|
|
164
164
|
})]
|
|
165
165
|
}, option.value);
|
|
@@ -51,14 +51,14 @@ function RunRowShell({ icon, title, description, descriptionMono = false, status
|
|
|
51
51
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
52
52
|
"data-run-row": "",
|
|
53
53
|
"data-state": open ? "open" : "closed",
|
|
54
|
-
className: cn("
|
|
54
|
+
className: cn("min-w-0 flex-1 overflow-hidden border transition-colors", open ? "border-border bg-[var(--md3-surface-container)]" : "border-transparent bg-transparent", expandable && !open && "hover:bg-[var(--md3-surface-container)]", SHAPE_CLASS[groupPosition], className),
|
|
55
55
|
children: [
|
|
56
56
|
/* @__PURE__ */ jsx(Collapsible.Trigger, {
|
|
57
57
|
asChild: true,
|
|
58
58
|
disabled: !expandable,
|
|
59
59
|
children: /* @__PURE__ */ jsxs("button", {
|
|
60
60
|
type: "button",
|
|
61
|
-
className: cn("flex w-full items-center gap-2 text-left", HEADER_INSET, expandable ? "cursor-pointer" : "cursor-default"),
|
|
61
|
+
className: cn("group flex w-full items-center gap-2 text-left", HEADER_INSET, expandable ? "cursor-pointer" : "cursor-default"),
|
|
62
62
|
children: [
|
|
63
63
|
/* @__PURE__ */ jsxs("span", {
|
|
64
64
|
"aria-hidden": true,
|
package/package.json
CHANGED
|
@@ -295,6 +295,45 @@ export const WithToolGroup: Story = {
|
|
|
295
295
|
},
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
// A settled multi-step turn folded into one row, under a long prompt that folds too
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
const read = (id: string, detail: string, status: 'success' | 'error' = 'success'): AgentTimelineItem => ({
|
|
303
|
+
id,
|
|
304
|
+
kind: 'tool',
|
|
305
|
+
call: { id, type: 'read', label: 'Read', status, detail, duration: 40 },
|
|
306
|
+
part: { type: 'tool', id, tool: 'read', state: status === 'error' ? { status: 'error', input: { path: detail }, error: `File not found: ${detail}` } : { status: 'completed', input: { path: detail }, output: '…' } },
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
export const FoldedSteps: Story = {
|
|
310
|
+
args: {
|
|
311
|
+
items: [
|
|
312
|
+
{
|
|
313
|
+
id: 'user-1',
|
|
314
|
+
kind: 'message',
|
|
315
|
+
role: 'user',
|
|
316
|
+
collapsedLines: 4,
|
|
317
|
+
content:
|
|
318
|
+
'Build the most correct, most complete zkVM in Rust: every component of the paper, sound end to end, at 120 bits, measured against the paper\'s own tables, and then better. Start from the commission and the previous handoff; read the open blockers first. Keep one owner per path, and re-measure every gain on the same benchmark before you integrate it. Report the proving rate, proof size, verify time and peak memory at every release.',
|
|
319
|
+
timestamp: t(300),
|
|
320
|
+
},
|
|
321
|
+
{ id: 'think-1', kind: 'custom', content: <p className="text-sm text-muted-foreground">Reasoning rows sit inside the fold with the calls they led to.</p> },
|
|
322
|
+
{
|
|
323
|
+
id: 'fold-1',
|
|
324
|
+
kind: 'fold',
|
|
325
|
+
items: [read('r1', 'inputs/commission.md'), read('r2', 'inputs/previous.md'), read('r3', 'inputs/claims.json'), read('r4', 'inputs/sources/handoff.md'), { id: 'b1', kind: 'tool', call: { id: 'b1', type: 'bash', label: 'Shell', status: 'success', detail: 'git -C repo log --oneline -5', duration: 120 } }],
|
|
326
|
+
},
|
|
327
|
+
{ id: 'asst-1', kind: 'message', role: 'assistant', content: 'The handoff names four open blockers. I will take the proof-size one first, since the other three depend on it.' },
|
|
328
|
+
{
|
|
329
|
+
id: 'fold-2',
|
|
330
|
+
kind: 'fold',
|
|
331
|
+
items: [read('r5', 'inputs/sources/open-blockers.md'), read('r6', 'inputs/profile-examples/README.md', 'error'), read('r7', 'repo/README.md')],
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
|
|
298
337
|
// ---------------------------------------------------------------------------
|
|
299
338
|
// Error state
|
|
300
339
|
// ---------------------------------------------------------------------------
|
|
@@ -316,3 +316,27 @@ describe("AgentTimeline status cards", () => {
|
|
|
316
316
|
expect(glyph("warning")).toContain("lucide-triangle-alert")
|
|
317
317
|
})
|
|
318
318
|
})
|
|
319
|
+
|
|
320
|
+
describe("AgentTimeline folds", () => {
|
|
321
|
+
const call = (id: string, type: "read" | "bash", detail: string, status: "success" | "error" = "success"): AgentTimelineItem => ({
|
|
322
|
+
id,
|
|
323
|
+
kind: "tool",
|
|
324
|
+
call: { id, type, label: type === "read" ? "Read" : "Shell", status, detail },
|
|
325
|
+
part: { type: "tool", id, tool: type, state: status === "error" ? { status: "error", input: { path: detail }, error: `File not found: ${detail}` } : { status: "completed", input: { path: detail }, output: "ok" } },
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it("folds a run of calls into one row that names what they did and opens to the rows", async () => {
|
|
329
|
+
const user = userEvent.setup()
|
|
330
|
+
render(<AgentTimeline items={[{ id: "f", kind: "fold", items: [call("a", "read", "inputs/commission.md"), call("b", "read", "inputs/claims.json"), call("c", "bash", "ls repo")] }]} />)
|
|
331
|
+
const row = screen.getByRole("button", { name: /Read 2 files · Ran 1 command/ })
|
|
332
|
+
expect(screen.queryByText("Shell")).not.toBeInTheDocument()
|
|
333
|
+
await user.click(row)
|
|
334
|
+
expect(screen.getByText("Shell")).toBeInTheDocument()
|
|
335
|
+
expect(screen.getAllByText("Read")).toHaveLength(2)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it("keeps a failure visible under a closed fold", () => {
|
|
339
|
+
render(<AgentTimeline items={[{ id: "f", kind: "fold", items: [call("a", "read", "a.md"), call("b", "read", "b.md", "error")] }]} />)
|
|
340
|
+
expect(screen.getByText("1 failed: File not found: b.md")).toBeInTheDocument()
|
|
341
|
+
})
|
|
342
|
+
})
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
CircleDot,
|
|
6
6
|
FileText,
|
|
7
7
|
Info,
|
|
8
|
+
Layers,
|
|
8
9
|
} from "lucide-react";
|
|
9
10
|
import { focusRing } from "../lib/focus";
|
|
10
11
|
import { cn } from "../lib/utils";
|
|
@@ -16,6 +17,8 @@ import { ThinkingIndicator } from "./thinking-indicator";
|
|
|
16
17
|
import { type ToolCallData } from "../run/tool-call-feed";
|
|
17
18
|
import { ToolCallGroup, ToolCallStep } from "../run/tool-call-step";
|
|
18
19
|
import { statusMeta } from "../run/status-meta";
|
|
20
|
+
import { RunRowShell } from "../run/run-row-shell";
|
|
21
|
+
import { TextShimmer } from "../primitives/text-shimmer";
|
|
19
22
|
import type { ChatAuthor } from "../types/message";
|
|
20
23
|
import type { ToolPart } from "../types/parts";
|
|
21
24
|
|
|
@@ -28,6 +31,9 @@ export interface AgentTimelineMessageItem {
|
|
|
28
31
|
content: string;
|
|
29
32
|
toolCalls?: ReactNode;
|
|
30
33
|
isStreaming?: boolean;
|
|
34
|
+
/** A user bubble longer than this many lines shows them with a fade and a
|
|
35
|
+
* "Show more" inside the bubble; omit to show the whole message. */
|
|
36
|
+
collapsedLines?: number;
|
|
31
37
|
/** Shown beside a user bubble on hover. Assistant prose carries no
|
|
32
38
|
* timestamp of its own — the turn's user message dates the exchange. */
|
|
33
39
|
timestamp?: Date;
|
|
@@ -59,6 +65,23 @@ export interface AgentTimelineToolGroupItem {
|
|
|
59
65
|
parts?: ToolPart[];
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A run of steps folded into one quiet row that names what they did ("Read 6
|
|
70
|
+
* files · Ran 2 commands") and opens to the rows themselves, as an agent app
|
|
71
|
+
* folds a settled multi-step turn. A failed call keeps the fold in the failure
|
|
72
|
+
* ink with its error under the row, so a failure never hides behind it.
|
|
73
|
+
*/
|
|
74
|
+
export interface AgentTimelineFoldItem {
|
|
75
|
+
id: string;
|
|
76
|
+
kind: "fold";
|
|
77
|
+
/** The folded rows in order: tool calls, reasoning and other run rows. */
|
|
78
|
+
items: AgentTimelineItem[];
|
|
79
|
+
/** The row's words; by default what its tool calls did (`foldSummary`). */
|
|
80
|
+
summary?: string;
|
|
81
|
+
/** Start open, e.g. when a link points at one of its steps. */
|
|
82
|
+
defaultOpen?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
62
85
|
export interface AgentTimelineStatusItem {
|
|
63
86
|
id: string;
|
|
64
87
|
kind: "status";
|
|
@@ -92,6 +115,7 @@ export type AgentTimelineItem =
|
|
|
92
115
|
| AgentTimelineMessageItem
|
|
93
116
|
| AgentTimelineToolItem
|
|
94
117
|
| AgentTimelineToolGroupItem
|
|
118
|
+
| AgentTimelineFoldItem
|
|
95
119
|
| AgentTimelineStatusItem
|
|
96
120
|
| AgentTimelineArtifactItem
|
|
97
121
|
| AgentTimelineCustomItem;
|
|
@@ -285,6 +309,82 @@ function ArtifactCard({ item }: { item: AgentTimelineArtifactItem }) {
|
|
|
285
309
|
);
|
|
286
310
|
}
|
|
287
311
|
|
|
312
|
+
const FOLD_VERBS: Record<ToolCallData["type"], (n: number) => string> = {
|
|
313
|
+
read: (n) => `Read ${n} ${n === 1 ? "file" : "files"}`,
|
|
314
|
+
write: (n) => `Wrote ${n} ${n === 1 ? "file" : "files"}`,
|
|
315
|
+
edit: (n) => `Edited ${n} ${n === 1 ? "file" : "files"}`,
|
|
316
|
+
bash: (n) => `Ran ${n} ${n === 1 ? "command" : "commands"}`,
|
|
317
|
+
grep: (n) => `Searched ${n} ${n === 1 ? "time" : "times"}`,
|
|
318
|
+
glob: (n) => `Matched ${n} ${n === 1 ? "pattern" : "patterns"}`,
|
|
319
|
+
list: (n) => `Listed ${n} ${n === 1 ? "folder" : "folders"}`,
|
|
320
|
+
download: (n) => `Downloaded ${n} ${n === 1 ? "file" : "files"}`,
|
|
321
|
+
inspect: (n) => `Inspected ${n} ${n === 1 ? "item" : "items"}`,
|
|
322
|
+
audit: (n) => `Audited ${n} ${n === 1 ? "item" : "items"}`,
|
|
323
|
+
unknown: (n) => `Used ${n} ${n === 1 ? "tool" : "tools"}`,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/** The tool calls inside a fold, in order (a tool group's calls included). */
|
|
327
|
+
function foldCalls(items: readonly AgentTimelineItem[]): { call: ToolCallData; part?: ToolPart }[] {
|
|
328
|
+
return items.flatMap((item) =>
|
|
329
|
+
item.kind === "tool"
|
|
330
|
+
? [{ call: item.call, part: item.part }]
|
|
331
|
+
: item.kind === "tool_group"
|
|
332
|
+
? item.calls.map((call, i) => ({ call, part: item.parts?.[i] }))
|
|
333
|
+
: item.kind === "fold"
|
|
334
|
+
? foldCalls(item.items)
|
|
335
|
+
: [],
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* What a fold's tool calls did, by verb in the order they first ran:
|
|
341
|
+
* "Read 6 files · Ran 2 commands". A fold without tool calls reads by its
|
|
342
|
+
* row count.
|
|
343
|
+
*/
|
|
344
|
+
export function foldSummary(items: readonly AgentTimelineItem[]): string {
|
|
345
|
+
const counts = new Map<ToolCallData["type"], number>();
|
|
346
|
+
for (const { call } of foldCalls(items)) counts.set(call.type, (counts.get(call.type) ?? 0) + 1);
|
|
347
|
+
if (!counts.size) return `${items.length} ${items.length === 1 ? "step" : "steps"}`;
|
|
348
|
+
const phrases = [...counts].map(([type, n]) => FOLD_VERBS[type](n));
|
|
349
|
+
return phrases.length > 3 ? `${phrases.slice(0, 3).join(" · ")} · ${phrases.length - 3} more` : phrases.join(" · ");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function FoldStep({ item, renderToolActions, viewerId }: { item: AgentTimelineFoldItem; renderToolActions?: (part: ToolPart) => ReactNode; viewerId?: string }) {
|
|
353
|
+
const calls = foldCalls(item.items);
|
|
354
|
+
const failed = calls.filter(({ call }) => call.status === "error");
|
|
355
|
+
const running = calls.some(({ call }) => call.status === "running");
|
|
356
|
+
const firstError = failed.map(({ part }) => (part?.state.status === "error" ? part.state.error : undefined)).find(Boolean);
|
|
357
|
+
const total = calls.reduce((sum, { call }) => sum + (call.duration ?? 0), 0);
|
|
358
|
+
const title = item.summary ?? foldSummary(item.items);
|
|
359
|
+
// What the calls acted on, in order and once each: the row's quiet second half.
|
|
360
|
+
const targets = [...new Set(calls.map(({ call }) => call.detail).filter((detail): detail is string => Boolean(detail)))];
|
|
361
|
+
return (
|
|
362
|
+
<RunRowShell
|
|
363
|
+
icon={<Layers className="h-3.5 w-3.5" />}
|
|
364
|
+
title={running ? <TextShimmer>{title}</TextShimmer> : title}
|
|
365
|
+
description={targets.join(", ") || undefined}
|
|
366
|
+
descriptionMono
|
|
367
|
+
status={failed.length ? "error" : running ? "running" : "success"}
|
|
368
|
+
durationMs={total > 0 && !running ? total : undefined}
|
|
369
|
+
collapsedError={failed.length ? `${failed.length} failed${firstError ? `: ${firstError}` : ""}` : undefined}
|
|
370
|
+
defaultOpen={item.defaultOpen}
|
|
371
|
+
contentClassName="px-3 py-1.5"
|
|
372
|
+
>
|
|
373
|
+
<div data-timeline-fold="" className="flex flex-col">
|
|
374
|
+
{item.items.map((inner, index) => (
|
|
375
|
+
<AgentTimelineStep
|
|
376
|
+
key={inner.id}
|
|
377
|
+
item={inner}
|
|
378
|
+
previous={index > 0 ? agentTimelineStepKind(item.items[index - 1]) : undefined}
|
|
379
|
+
renderToolActions={renderToolActions}
|
|
380
|
+
viewerId={viewerId}
|
|
381
|
+
/>
|
|
382
|
+
))}
|
|
383
|
+
</div>
|
|
384
|
+
</RunRowShell>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
288
388
|
/** The transcript column every timeline sits in: centred, at the reading width. */
|
|
289
389
|
export function AgentTimelineColumn({ className, children }: { className?: string; children?: ReactNode }) {
|
|
290
390
|
return <div className={cn("mx-auto flex w-full max-w-5xl flex-col px-4 py-4", className)}>{children}</div>;
|
|
@@ -317,7 +417,7 @@ export function AgentTimelineStep({ item, previous, headed, renderToolActions, v
|
|
|
317
417
|
function TimelineItem({ item, headed, renderToolActions, viewerId }: Omit<AgentTimelineStepProps, "previous">): ReactNode {
|
|
318
418
|
if (item.kind === "message") {
|
|
319
419
|
return item.role === "user" ? (
|
|
320
|
-
<UserMessage content={item.content} timestamp={item.timestamp} author={item.author} viewerId={viewerId} />
|
|
420
|
+
<UserMessage content={item.content} timestamp={item.timestamp} author={item.author} viewerId={viewerId} collapsedLines={item.collapsedLines} />
|
|
321
421
|
) : (
|
|
322
422
|
<AssistantMessage item={item} author={headed ? item.author : undefined} />
|
|
323
423
|
);
|
|
@@ -361,6 +461,8 @@ function TimelineItem({ item, headed, renderToolActions, viewerId }: Omit<AgentT
|
|
|
361
461
|
);
|
|
362
462
|
}
|
|
363
463
|
|
|
464
|
+
if (item.kind === "fold") return <FoldStep item={item} renderToolActions={renderToolActions} viewerId={viewerId} />;
|
|
465
|
+
|
|
364
466
|
if (item.kind === "status") return <StatusCard item={item} />;
|
|
365
467
|
if (item.kind === "artifact") return <ArtifactCard item={item} />;
|
|
366
468
|
return item.content;
|
package/src/chat/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export {
|
|
|
19
19
|
AgentTimelineColumn,
|
|
20
20
|
AgentTimelineStep,
|
|
21
21
|
agentTimelineStepKind,
|
|
22
|
+
foldSummary,
|
|
22
23
|
type AgentTimelineProps,
|
|
23
24
|
type AgentTimelineStepProps,
|
|
24
25
|
type AgentTimelineStepKind,
|
|
@@ -26,6 +27,7 @@ export {
|
|
|
26
27
|
type AgentTimelineMessageItem,
|
|
27
28
|
type AgentTimelineToolItem,
|
|
28
29
|
type AgentTimelineToolGroupItem,
|
|
30
|
+
type AgentTimelineFoldItem,
|
|
29
31
|
type AgentTimelineStatusItem,
|
|
30
32
|
type AgentTimelineArtifactItem,
|
|
31
33
|
type AgentTimelineCustomItem,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TRANSCRIPT_TEXT } from "./transcript-text";
|
|
2
|
-
import { memo, type ReactNode } from "react";
|
|
2
|
+
import { memo, useLayoutEffect, useRef, useState, type ReactNode } from "react";
|
|
3
3
|
import type { ChatAuthor, SessionMessage } from "../types/message";
|
|
4
4
|
import type { SessionPart } from "../types/parts";
|
|
5
5
|
import { isViewerMessage, MessageAuthor } from "./message-author";
|
|
@@ -23,6 +23,11 @@ export interface UserMessageProps {
|
|
|
23
23
|
* someone else's: it sits at the start, on a card, under its author.
|
|
24
24
|
*/
|
|
25
25
|
viewerId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Show a long message's first lines with a fade and a "Show more" inside the
|
|
28
|
+
* bubble, which opens it in place. Omit to show the whole message.
|
|
29
|
+
*/
|
|
30
|
+
collapsedLines?: number;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
function formatTime(date: Date): string {
|
|
@@ -37,10 +42,58 @@ function MessageActions({ children, align = "end" }: { children: ReactNode; alig
|
|
|
37
42
|
);
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
/**
|
|
46
|
+
* A message body that can fold to its first `lines` lines: the last shown line
|
|
47
|
+
* fades out and a "Show more" inside the bubble opens the rest in place. The
|
|
48
|
+
* button appears only when the text overflows.
|
|
49
|
+
*/
|
|
50
|
+
function BubbleText({ text, lines }: { text: string; lines?: number }) {
|
|
51
|
+
const body = useRef<HTMLDivElement>(null);
|
|
52
|
+
const [open, setOpen] = useState(false);
|
|
53
|
+
const [over, setOver] = useState(false);
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
const element = body.current;
|
|
56
|
+
if (!element || !lines || open) return;
|
|
57
|
+
const measure = () => setOver(element.scrollHeight > element.clientHeight + 1);
|
|
58
|
+
measure();
|
|
59
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
60
|
+
const observer = new ResizeObserver(measure);
|
|
61
|
+
observer.observe(element);
|
|
62
|
+
return () => observer.disconnect();
|
|
63
|
+
}, [lines, open, text]);
|
|
64
|
+
const folded = Boolean(lines) && !open;
|
|
65
|
+
return (
|
|
66
|
+
<>
|
|
67
|
+
<div
|
|
68
|
+
ref={body}
|
|
69
|
+
className={`break-words whitespace-pre-wrap ${TRANSCRIPT_TEXT}`}
|
|
70
|
+
style={folded ? {
|
|
71
|
+
maxHeight: `${lines}lh`,
|
|
72
|
+
overflow: "hidden",
|
|
73
|
+
...(over ? { maskImage: "linear-gradient(to bottom, #000 calc(100% - 1.5lh), transparent)", WebkitMaskImage: "linear-gradient(to bottom, #000 calc(100% - 1.5lh), transparent)" } : {}),
|
|
74
|
+
} : undefined}
|
|
75
|
+
>
|
|
76
|
+
{text}
|
|
77
|
+
</div>
|
|
78
|
+
{lines && (over || open) ? (
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
aria-expanded={open}
|
|
82
|
+
onClick={() => setOpen(!open)}
|
|
83
|
+
className="mt-1.5 text-sm font-medium underline-offset-2 opacity-75 transition-opacity hover:underline hover:opacity-100"
|
|
84
|
+
>
|
|
85
|
+
{open ? "Show less" : "Show more"}
|
|
86
|
+
</button>
|
|
87
|
+
) : null}
|
|
88
|
+
</>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function UserBubble({ text, timestamp, actions, lines }: {
|
|
41
93
|
text: string;
|
|
42
94
|
timestamp?: Date;
|
|
43
95
|
actions?: ReactNode;
|
|
96
|
+
lines?: number;
|
|
44
97
|
}) {
|
|
45
98
|
return (
|
|
46
99
|
<div className="flex justify-end">
|
|
@@ -49,14 +102,12 @@ function UserBubble({ text, timestamp, actions }: {
|
|
|
49
102
|
{timestamp ? (
|
|
50
103
|
<span
|
|
51
104
|
data-user-message-time=""
|
|
52
|
-
className="pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-
|
|
105
|
+
className="pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-sm text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"
|
|
53
106
|
>
|
|
54
107
|
{formatTime(timestamp)}
|
|
55
108
|
</span>
|
|
56
109
|
) : null}
|
|
57
|
-
<
|
|
58
|
-
{text}
|
|
59
|
-
</div>
|
|
110
|
+
<BubbleText text={text} lines={lines} />
|
|
60
111
|
</div>
|
|
61
112
|
{actions ? <MessageActions>{actions}</MessageActions> : null}
|
|
62
113
|
</div>
|
|
@@ -69,10 +120,11 @@ function UserBubble({ text, timestamp, actions }: {
|
|
|
69
120
|
* than the reader's inverse fill, so "mine" and "theirs" differ before the
|
|
70
121
|
* names are read. The timestamp is revealed on the bubble's far side.
|
|
71
122
|
*/
|
|
72
|
-
function OtherPersonBubble({ text, timestamp, actions, author }: {
|
|
123
|
+
function OtherPersonBubble({ text, timestamp, actions, author, lines }: {
|
|
73
124
|
text: string;
|
|
74
125
|
timestamp?: Date;
|
|
75
126
|
actions?: ReactNode;
|
|
127
|
+
lines?: number;
|
|
76
128
|
/** The author line; absent on a later segment of the same message. */
|
|
77
129
|
author?: ChatAuthor;
|
|
78
130
|
}) {
|
|
@@ -84,14 +136,12 @@ function OtherPersonBubble({ text, timestamp, actions, author }: {
|
|
|
84
136
|
{timestamp ? (
|
|
85
137
|
<span
|
|
86
138
|
data-user-message-time=""
|
|
87
|
-
className="pointer-events-none absolute left-full top-1/2 ml-3 -translate-y-1/2 whitespace-nowrap text-
|
|
139
|
+
className="pointer-events-none absolute left-full top-1/2 ml-3 -translate-y-1/2 whitespace-nowrap text-sm text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"
|
|
88
140
|
>
|
|
89
141
|
{formatTime(timestamp)}
|
|
90
142
|
</span>
|
|
91
143
|
) : null}
|
|
92
|
-
<
|
|
93
|
-
{text}
|
|
94
|
-
</div>
|
|
144
|
+
<BubbleText text={text} lines={lines} />
|
|
95
145
|
</div>
|
|
96
146
|
{actions ? <MessageActions align="start">{actions}</MessageActions> : null}
|
|
97
147
|
</div>
|
|
@@ -104,14 +154,14 @@ function OtherPersonBubble({ text, timestamp, actions, author }: {
|
|
|
104
154
|
* Application notes interrupt the bubble in source order, outside its attribution.
|
|
105
155
|
*/
|
|
106
156
|
export const UserMessage = memo(
|
|
107
|
-
({ message: _message, parts, content, timestamp, actions, author, viewerId }: UserMessageProps) => {
|
|
157
|
+
({ message: _message, parts, content, timestamp, actions, author, viewerId, collapsedLines }: UserMessageProps) => {
|
|
108
158
|
const other = isViewerMessage(author, viewerId) ? undefined : author;
|
|
109
159
|
// `headed` puts the author line on the first spoken segment only.
|
|
110
160
|
const bubble = (text: string, bubbleActions?: ReactNode, headed = true, key?: number) =>
|
|
111
161
|
other ? (
|
|
112
|
-
<OtherPersonBubble key={key} text={text} timestamp={timestamp} actions={bubbleActions} author={headed ? other : undefined} />
|
|
162
|
+
<OtherPersonBubble key={key} text={text} timestamp={timestamp} actions={bubbleActions} author={headed ? other : undefined} lines={collapsedLines} />
|
|
113
163
|
) : (
|
|
114
|
-
<UserBubble key={key} text={text} timestamp={timestamp} actions={bubbleActions} />
|
|
164
|
+
<UserBubble key={key} text={text} timestamp={timestamp} actions={bubbleActions} lines={collapsedLines} />
|
|
115
165
|
);
|
|
116
166
|
// Explicit direct content (including empty content) still overrides parts.
|
|
117
167
|
if (content != null) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { FileTabs, type FileTabData } from './file-tabs'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FileTabs> = { title: 'Files/FileTabs', component: FileTabs, parameters: { layout: 'fullscreen' } }
|
|
6
|
+
export default meta
|
|
7
|
+
|
|
8
|
+
const TABS: FileTabData[] = [
|
|
9
|
+
{ id: 'a', name: 'CHECK.md', path: 'code/route-a-lead-2/CHECK.md' },
|
|
10
|
+
{ id: 'b', name: 'CHECK.md', path: 'code/rs3-merge-lead/CHECK.md' },
|
|
11
|
+
{ id: 'c', name: 'approaches.md', path: 'pages/magnetar-rust/approaches.md' },
|
|
12
|
+
{ id: 'd', name: 'gaps.json', path: 'code/route-a-lead-2/gaps.json', dirty: true },
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
/** Two files of one name tell themselves apart by folder; inactive tabs keep their slot with a divider. */
|
|
16
|
+
export const SameNames: StoryObj<typeof FileTabs> = {
|
|
17
|
+
render: () => {
|
|
18
|
+
const [active, setActive] = useState('c')
|
|
19
|
+
return (
|
|
20
|
+
<div className="bg-[var(--bg-root)] p-6">
|
|
21
|
+
<div className="rounded-[var(--radius-xl)] border border-border bg-background">
|
|
22
|
+
<FileTabs tabs={TABS} activeId={active} onSelect={setActive} onClose={() => {}} />
|
|
23
|
+
<div className="h-40 bg-card" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
},
|
|
28
|
+
}
|
|
@@ -63,4 +63,21 @@ describe("FileTabs", () => {
|
|
|
63
63
|
fireEvent.click(screen.getByRole("button", { name: "Close README.md" }));
|
|
64
64
|
expect(screen.getAllByRole("tab")).toHaveLength(2);
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
it("tells two open files of one name apart by their nearest differing folder", () => {
|
|
68
|
+
render(
|
|
69
|
+
<FileTabs
|
|
70
|
+
tabs={[
|
|
71
|
+
{ id: "x", name: "CHECK.md", path: "code/route-a-lead-2/CHECK.md" },
|
|
72
|
+
{ id: "y", name: "CHECK.md", path: "code/rs3-merge-lead/CHECK.md" },
|
|
73
|
+
{ id: "z", name: "plan.md", path: "pages/plan.md" },
|
|
74
|
+
]}
|
|
75
|
+
activeId="x"
|
|
76
|
+
onSelect={() => {}}
|
|
77
|
+
onClose={() => {}}
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
expect(screen.getAllByRole("tab").map((tab) => tab.getAttribute("aria-label"))).toEqual(["CHECK.md in route-a-lead-2", "CHECK.md in rs3-merge-lead", "plan.md"]);
|
|
81
|
+
expect(screen.getByText("· route-a-lead-2")).toBeInTheDocument();
|
|
82
|
+
});
|
|
66
83
|
});
|
package/src/files/file-tabs.tsx
CHANGED
|
@@ -36,6 +36,28 @@ function getTabIcon(name: string) {
|
|
|
36
36
|
return FileCode;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Each tab's folder hint: two open files with one name ("CHECK.md" twice) each show the nearest parent folder that
|
|
41
|
+
* tells them apart, as an editor does ("CHECK.md · route-a-lead-2"). A name no other tab shares shows none.
|
|
42
|
+
*/
|
|
43
|
+
function folderHints(tabs: readonly FileTabData[]): Map<string, string> {
|
|
44
|
+
const hints = new Map<string, string>();
|
|
45
|
+
const byName = new Map<string, FileTabData[]>();
|
|
46
|
+
for (const tab of tabs) byName.set(tab.name, [...(byName.get(tab.name) ?? []), tab]);
|
|
47
|
+
for (const same of byName.values()) {
|
|
48
|
+
if (same.length < 2) continue;
|
|
49
|
+
const parents = same.map((tab) => tab.path.replace(/\/+$/, "").split("/").slice(0, -1).reverse());
|
|
50
|
+
same.forEach((tab, i) => {
|
|
51
|
+
const own = parents[i]!;
|
|
52
|
+
// The first folder, walking up, that no other same-named tab has at that depth.
|
|
53
|
+
const depth = own.findIndex((folder, d) => parents.every((other, j) => j === i || other[d] !== folder));
|
|
54
|
+
const hint = own[depth >= 0 ? depth : 0];
|
|
55
|
+
if (hint) hints.set(tab.id, hint);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return hints;
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
/** A long name keeps its extension and the characters before it after the ellipsis. */
|
|
40
62
|
function splitName(name: string): [string, string] {
|
|
41
63
|
if (name.length <= 18) return [name, ""];
|
|
@@ -55,6 +77,7 @@ export function FileTabs({ tabs, activeId, onSelect, onClose, label = "Open file
|
|
|
55
77
|
}, [activeId, tabs.length]);
|
|
56
78
|
|
|
57
79
|
if (tabs.length === 0) return null;
|
|
80
|
+
const hints = folderHints(tabs);
|
|
58
81
|
|
|
59
82
|
const tabButtons = () => [...(stripRef.current?.querySelectorAll<HTMLButtonElement>('[role="tab"]') ?? [])];
|
|
60
83
|
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -84,8 +107,12 @@ export function FileTabs({ tabs, activeId, onSelect, onClose, label = "Open file
|
|
|
84
107
|
className,
|
|
85
108
|
)}
|
|
86
109
|
>
|
|
87
|
-
{tabs.map((tab) => {
|
|
110
|
+
{tabs.map((tab, index) => {
|
|
88
111
|
const isActive = tab.id === activeId;
|
|
112
|
+
// An inactive tab keeps a tab's slot: a 1px divider on its right, except beside the active tab, so the strip
|
|
113
|
+
// reads as tabs rather than loose words.
|
|
114
|
+
const divided = !isActive && tabs[index + 1] !== undefined && tabs[index + 1]!.id !== activeId;
|
|
115
|
+
const hint = hints.get(tab.id);
|
|
89
116
|
const Icon = getTabIcon(tab.name);
|
|
90
117
|
const [head, tail] = splitName(tab.name);
|
|
91
118
|
|
|
@@ -99,13 +126,14 @@ export function FileTabs({ tabs, activeId, onSelect, onClose, label = "Open file
|
|
|
99
126
|
isActive
|
|
100
127
|
? "border-border bg-card text-foreground"
|
|
101
128
|
: "border-transparent text-muted-foreground hover:bg-[var(--ix-hover-bg)] hover:text-foreground",
|
|
129
|
+
divided && "after:absolute after:-right-[3px] after:top-1/2 after:h-4 after:w-px after:-translate-y-1/2 after:bg-border",
|
|
102
130
|
)}
|
|
103
131
|
>
|
|
104
132
|
<button
|
|
105
133
|
type="button"
|
|
106
134
|
role="tab"
|
|
107
135
|
aria-selected={isActive}
|
|
108
|
-
aria-label={tab.
|
|
136
|
+
aria-label={`${tab.name}${hint ? ` in ${hint}` : ""}${tab.dirty ? ", unsaved changes" : ""}`}
|
|
109
137
|
tabIndex={isActive || (!activeId && tab === tabs[0]) ? 0 : -1}
|
|
110
138
|
onClick={() => onSelect(tab.id)}
|
|
111
139
|
className={cn("flex h-full min-w-0 items-center gap-1.5 rounded-t-lg pl-3 pr-1", focusRingInset)}
|
|
@@ -115,6 +143,7 @@ export function FileTabs({ tabs, activeId, onSelect, onClose, label = "Open file
|
|
|
115
143
|
<span className="overflow-hidden text-ellipsis whitespace-pre">{head}</span>
|
|
116
144
|
{tail && <span className="shrink-0 whitespace-pre">{tail}</span>}
|
|
117
145
|
</span>
|
|
146
|
+
{hint && <span className="min-w-0 truncate text-muted-foreground">· {hint}</span>}
|
|
118
147
|
{tab.dirty && <span aria-hidden className="size-1.5 shrink-0 rounded-full bg-[var(--accent-text)]" />}
|
|
119
148
|
</button>
|
|
120
149
|
<button
|
|
@@ -113,7 +113,7 @@ export function RunRowShell({
|
|
|
113
113
|
data-run-row=""
|
|
114
114
|
data-state={open ? "open" : "closed"}
|
|
115
115
|
className={cn(
|
|
116
|
-
"
|
|
116
|
+
"min-w-0 flex-1 overflow-hidden border transition-colors",
|
|
117
117
|
open
|
|
118
118
|
? "border-border bg-[var(--md3-surface-container)]"
|
|
119
119
|
: "border-transparent bg-transparent",
|
|
@@ -125,8 +125,11 @@ export function RunRowShell({
|
|
|
125
125
|
<Collapsible.Trigger asChild disabled={!expandable}>
|
|
126
126
|
<button
|
|
127
127
|
type="button"
|
|
128
|
+
// The header is the hover group, not the whole card: rows nested in an
|
|
129
|
+
// open row (a fold's steps) keep their own glyphs while the
|
|
130
|
+
// pointer is over them.
|
|
128
131
|
className={cn(
|
|
129
|
-
"flex w-full items-center gap-2 text-left",
|
|
132
|
+
"group flex w-full items-center gap-2 text-left",
|
|
130
133
|
HEADER_INSET,
|
|
131
134
|
expandable ? "cursor-pointer" : "cursor-default",
|
|
132
135
|
)}
|