@tangle-network/ui 11.16.3 → 11.16.4
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 +8 -0
- package/dist/chat/agent-timeline.js +3 -2
- package/dist/chat/chat-message.js +2 -2
- package/dist/chat/thinking-indicator.js +1 -1
- package/dist/chat/user-message.js +2 -2
- package/dist/markdown/code-block.js +9 -1
- package/dist/run/expandable-content.js +2 -2
- package/dist/run/expanded-tool-detail.js +3 -0
- package/package.json +1 -1
- package/src/chat/agent-timeline.test.tsx +0 -14
- package/src/chat/agent-timeline.tsx +18 -2
- package/src/chat/chat-message.tsx +2 -2
- package/src/chat/thinking-indicator.tsx +1 -1
- package/src/chat/user-message.tsx +2 -2
- package/src/markdown/code-block.tsx +14 -2
- package/src/run/expandable-content.tsx +8 -1
- package/src/run/expanded-tool-detail.tsx +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 11.16.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 11fbba5: Chat text renders at the 15px body size. The user bubble, assistant prose, legacy `ChatMessage` and thinking timer wrote their size tokens as `text-[var(--font-size-*)]`, which Tailwind compiles to a color, so assistant prose fell back to the Markdown default of 14px and the user bubble inherited 16px. They now use `text-[length:var(...)]`, and the dist check fails on the untyped form.
|
|
8
|
+
|
|
9
|
+
The streaming caret follows the last word instead of starting a new line. A code block that scrolls sideways takes keyboard focus as a named region (axe `scrollable-region-focusable`). The Show more toggle under a tool's output or error sits inside the card's inset instead of against its edge.
|
|
10
|
+
|
|
3
11
|
## 11.16.3
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -53,13 +53,14 @@ function spacingBefore(prev, next) {
|
|
|
53
53
|
return "mt-3";
|
|
54
54
|
}
|
|
55
55
|
const ROW_BLEED = "-mx-3";
|
|
56
|
+
const STREAMING_CARET = "[&>:last-child:not(ul,ol)]:after:ml-0.5 [&>:last-child:not(ul,ol)]:after:inline-block [&>:last-child:not(ul,ol)]:after:h-[1em] [&>:last-child:not(ul,ol)]:after:w-0.5 [&>:last-child:not(ul,ol)]:after:animate-pulse [&>:last-child:not(ul,ol)]:after:rounded-full [&>:last-child:not(ul,ol)]:after:bg-muted-foreground [&>:last-child:not(ul,ol)]:after:align-text-bottom [&>:last-child:not(ul,ol)]:after:content-[''] [&>:is(ul,ol):last-child>li:last-child]:after:ml-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:inline-block [&>:is(ul,ol):last-child>li:last-child]:after:h-[1em] [&>:is(ul,ol):last-child>li:last-child]:after:w-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:animate-pulse [&>:is(ul,ol):last-child>li:last-child]:after:rounded-full [&>:is(ul,ol):last-child>li:last-child]:after:bg-muted-foreground [&>:is(ul,ol):last-child>li:last-child]:after:align-text-bottom [&>:is(ul,ol):last-child>li:last-child]:after:content-['']";
|
|
56
57
|
function AssistantMessage({ item }) {
|
|
57
58
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
58
59
|
item.content && /* @__PURE__ */ jsx(Markdown, {
|
|
59
|
-
className: "tangle-prose text-[var(--font-size-base)] leading-[1.5]",
|
|
60
|
+
className: cn("tangle-prose text-[length:var(--font-size-base)] leading-[1.5]", item.isStreaming && STREAMING_CARET),
|
|
60
61
|
children: item.content
|
|
61
62
|
}),
|
|
62
|
-
item.isStreaming && /* @__PURE__ */ jsx("span", {
|
|
63
|
+
item.isStreaming && !item.content && /* @__PURE__ */ jsx("span", {
|
|
63
64
|
"aria-hidden": true,
|
|
64
65
|
"data-streaming-caret": "",
|
|
65
66
|
className: "ml-0.5 inline-block h-[1em] w-0.5 animate-pulse rounded-full bg-muted-foreground align-text-bottom"
|
|
@@ -19,10 +19,10 @@ function ChatMessage({ role, content, toolCalls, isStreaming, timestamp, classNa
|
|
|
19
19
|
}), /* @__PURE__ */ jsxs("div", {
|
|
20
20
|
className: cn("min-w-0 max-w-[85%] space-y-1", isUser ? "rounded-[var(--radius-lg)] bg-foreground px-[var(--chat-message-px)] py-[var(--chat-message-py)] text-background" : isAssistant ? "text-foreground" : "rounded-[var(--radius-lg)] border border-border bg-card px-[var(--chat-message-px)] py-[var(--chat-message-py)]"),
|
|
21
21
|
children: [isUser ? /* @__PURE__ */ jsx("div", {
|
|
22
|
-
className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)]",
|
|
22
|
+
className: "whitespace-pre-wrap text-[length:var(--font-size-base)] leading-[var(--line-height-base)]",
|
|
23
23
|
children: content
|
|
24
24
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [content && /* @__PURE__ */ jsx(Markdown, {
|
|
25
|
-
className: "tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]",
|
|
25
|
+
className: "tangle-prose text-[length:var(--font-size-base)] leading-[var(--line-height-base)]",
|
|
26
26
|
children: content
|
|
27
27
|
}), isStreaming && /* @__PURE__ */ jsx("span", { className: "ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-[var(--brand-cool)] align-text-bottom" })] }), toolCalls]
|
|
28
28
|
})]
|
|
@@ -29,7 +29,7 @@ function ThinkingIndicator({ className }) {
|
|
|
29
29
|
})
|
|
30
30
|
]
|
|
31
31
|
}), elapsed > 3 && /* @__PURE__ */ jsxs("span", {
|
|
32
|
-
className: "text-[var(--font-size-xs)] tabular-nums text-muted-foreground",
|
|
32
|
+
className: "text-[length:var(--font-size-xs)] tabular-nums text-muted-foreground",
|
|
33
33
|
children: [elapsed, "s"]
|
|
34
34
|
})]
|
|
35
35
|
});
|
|
@@ -23,10 +23,10 @@ function UserBubble({ text, timestamp, actions }) {
|
|
|
23
23
|
className: "relative w-full min-w-0 rounded-2xl bg-foreground px-4 py-3 text-background",
|
|
24
24
|
children: [timestamp ? /* @__PURE__ */ jsx("span", {
|
|
25
25
|
"data-user-message-time": "",
|
|
26
|
-
className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-[var(--font-size-xs)] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
|
|
26
|
+
className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-[length:var(--font-size-xs)] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
|
|
27
27
|
children: formatTime(timestamp)
|
|
28
28
|
}) : null, /* @__PURE__ */ jsx("div", {
|
|
29
|
-
className: "break-words whitespace-pre-wrap text-[var(--font-size-base)] leading-[1.5]",
|
|
29
|
+
className: "break-words whitespace-pre-wrap text-[length:var(--font-size-base)] leading-[1.5]",
|
|
30
30
|
children: text
|
|
31
31
|
})]
|
|
32
32
|
}), actions ? /* @__PURE__ */ jsx(MessageActions, { children: actions }) : null]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { focusRing } from "../lib/focus.js";
|
|
1
|
+
import { focusRing, focusRingInset } from "../lib/focus.js";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -93,6 +93,12 @@ const CodeBlock = memo(({ code, language, label, showLineNumbers = false, light,
|
|
|
93
93
|
overflowX: "auto"
|
|
94
94
|
};
|
|
95
95
|
const codeTagProps = { style: { fontFamily: "var(--font-mono, 'JetBrains Mono', ui-monospace, monospace)" } };
|
|
96
|
+
const scrollRegion = {
|
|
97
|
+
tabIndex: 0,
|
|
98
|
+
role: "region",
|
|
99
|
+
"aria-label": headerLabel ? `${headerLabel} code` : "Code",
|
|
100
|
+
className: focusRingInset
|
|
101
|
+
};
|
|
96
102
|
return /* @__PURE__ */ jsxs("div", {
|
|
97
103
|
className: cn("group relative overflow-hidden rounded-lg border font-mono", "bg-card border-border", className),
|
|
98
104
|
...props,
|
|
@@ -121,8 +127,10 @@ const CodeBlock = memo(({ code, language, label, showLineNumbers = false, light,
|
|
|
121
127
|
customStyle,
|
|
122
128
|
codeTagProps,
|
|
123
129
|
wrapLines: false,
|
|
130
|
+
...scrollRegion,
|
|
124
131
|
children: code
|
|
125
132
|
}) : /* @__PURE__ */ jsx("pre", {
|
|
133
|
+
...scrollRegion,
|
|
126
134
|
style: {
|
|
127
135
|
...syntaxTheme.hljs,
|
|
128
136
|
...customStyle
|
|
@@ -7,7 +7,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
* "Show more / Show less" toggle. Content shorter than the cap renders
|
|
8
8
|
* as-is with no toggle.
|
|
9
9
|
*/
|
|
10
|
-
function ExpandableContent({ children, collapsedMaxPx = 180, fadeClassName = "from-muted", className }) {
|
|
10
|
+
function ExpandableContent({ children, collapsedMaxPx = 180, fadeClassName = "from-muted", className, toggleClassName }) {
|
|
11
11
|
const [expanded, setExpanded] = useState(false);
|
|
12
12
|
const [overflowing, setOverflowing] = useState(false);
|
|
13
13
|
const contentRef = useRef(null);
|
|
@@ -32,7 +32,7 @@ function ExpandableContent({ children, collapsedMaxPx = 180, fadeClassName = "fr
|
|
|
32
32
|
}), overflowing ? /* @__PURE__ */ jsx("button", {
|
|
33
33
|
type: "button",
|
|
34
34
|
onClick: () => setExpanded((value) => !value),
|
|
35
|
-
className: "mt-1 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
35
|
+
className: cn("mt-1 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground", toggleClassName),
|
|
36
36
|
children: expanded ? "Show less" : "Show more"
|
|
37
37
|
}) : null]
|
|
38
38
|
});
|
|
@@ -13,6 +13,7 @@ import { memo } from "react";
|
|
|
13
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
14
|
import { AlertCircle, ArrowLeft, ArrowRight, FileText, Loader2 } from "lucide-react";
|
|
15
15
|
//#region src/run/expanded-tool-detail.tsx
|
|
16
|
+
const FLUSH_TOGGLE = "mx-3 mb-2";
|
|
16
17
|
const EXT_LANG = {
|
|
17
18
|
ts: "typescript",
|
|
18
19
|
tsx: "tsx",
|
|
@@ -131,6 +132,7 @@ const ExpandedToolDetail = memo(({ part }) => {
|
|
|
131
132
|
})]
|
|
132
133
|
}), /* @__PURE__ */ jsx(ExpandableContent, {
|
|
133
134
|
fadeClassName: "from-card",
|
|
135
|
+
toggleClassName: FLUSH_TOGGLE,
|
|
134
136
|
children: /* @__PURE__ */ jsx(CodeBlock, {
|
|
135
137
|
code: outputStr.length > 2e3 ? outputStr.slice(0, 2e3) + "\n...(truncated)" : outputStr,
|
|
136
138
|
language: "json",
|
|
@@ -148,6 +150,7 @@ const ExpandedToolDetail = memo(({ part }) => {
|
|
|
148
150
|
})]
|
|
149
151
|
}), /* @__PURE__ */ jsx(ExpandableContent, {
|
|
150
152
|
fadeClassName: "from-[var(--surface-danger-bg)]",
|
|
153
|
+
toggleClassName: FLUSH_TOGGLE,
|
|
151
154
|
children: /* @__PURE__ */ jsx("pre", {
|
|
152
155
|
className: "bg-[var(--surface-danger-bg)] p-3 text-xs font-mono whitespace-pre-wrap break-all text-[var(--surface-danger-text)]",
|
|
153
156
|
children: error
|
package/package.json
CHANGED
|
@@ -253,20 +253,6 @@ describe("AgentTimeline tool actions", () => {
|
|
|
253
253
|
expect(classes[4]).toContain("mt-4") // prose → user
|
|
254
254
|
})
|
|
255
255
|
|
|
256
|
-
it("softens the streaming cursor to a muted caret", () => {
|
|
257
|
-
const { container } = render(
|
|
258
|
-
<AgentTimeline
|
|
259
|
-
items={[
|
|
260
|
-
{ id: "a-1", kind: "message", role: "assistant", content: "Typing", isStreaming: true },
|
|
261
|
-
]}
|
|
262
|
-
/>,
|
|
263
|
-
)
|
|
264
|
-
const caret = container.querySelector("[data-streaming-caret]")
|
|
265
|
-
expect(caret).not.toBeNull()
|
|
266
|
-
expect(caret!.className).toContain("bg-muted-foreground")
|
|
267
|
-
expect(caret!.className).not.toContain("bg-primary")
|
|
268
|
-
})
|
|
269
|
-
|
|
270
256
|
it("renders the source part's real input in the expanded detail", async () => {
|
|
271
257
|
const user = userEvent.setup()
|
|
272
258
|
const probePart: ToolPart = {
|
|
@@ -162,13 +162,29 @@ function spacingBefore(prev: StepKind | undefined, next: StepKind): string {
|
|
|
162
162
|
// wider than the bleed, so nothing clips.
|
|
163
163
|
const ROW_BLEED = "-mx-3";
|
|
164
164
|
|
|
165
|
+
// The streaming caret ends the last line of text. It is drawn as the `::after`
|
|
166
|
+
// of the prose's last block (the last item of a trailing list), so it follows
|
|
167
|
+
// the final word instead of starting a line of its own below the paragraph.
|
|
168
|
+
const STREAMING_CARET =
|
|
169
|
+
"[&>:last-child:not(ul,ol)]:after:ml-0.5 [&>:last-child:not(ul,ol)]:after:inline-block [&>:last-child:not(ul,ol)]:after:h-[1em] [&>:last-child:not(ul,ol)]:after:w-0.5 [&>:last-child:not(ul,ol)]:after:animate-pulse [&>:last-child:not(ul,ol)]:after:rounded-full [&>:last-child:not(ul,ol)]:after:bg-muted-foreground [&>:last-child:not(ul,ol)]:after:align-text-bottom [&>:last-child:not(ul,ol)]:after:content-[''] " +
|
|
170
|
+
"[&>:is(ul,ol):last-child>li:last-child]:after:ml-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:inline-block [&>:is(ul,ol):last-child>li:last-child]:after:h-[1em] [&>:is(ul,ol):last-child>li:last-child]:after:w-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:animate-pulse [&>:is(ul,ol):last-child>li:last-child]:after:rounded-full [&>:is(ul,ol):last-child>li:last-child]:after:bg-muted-foreground [&>:is(ul,ol):last-child>li:last-child]:after:align-text-bottom [&>:is(ul,ol):last-child>li:last-child]:after:content-['']";
|
|
171
|
+
|
|
165
172
|
function AssistantMessage({ item }: { item: AgentTimelineMessageItem }) {
|
|
166
173
|
return (
|
|
167
174
|
<div>
|
|
168
175
|
{item.content && (
|
|
169
|
-
<Markdown
|
|
176
|
+
<Markdown
|
|
177
|
+
// `length:` types the token: a bare `text-[var(--x)]` compiles to a
|
|
178
|
+
// color, which left the reading size at the Markdown default.
|
|
179
|
+
className={cn(
|
|
180
|
+
"tangle-prose text-[length:var(--font-size-base)] leading-[1.5]",
|
|
181
|
+
item.isStreaming && STREAMING_CARET,
|
|
182
|
+
)}
|
|
183
|
+
>
|
|
184
|
+
{item.content}
|
|
185
|
+
</Markdown>
|
|
170
186
|
)}
|
|
171
|
-
{item.isStreaming && (
|
|
187
|
+
{item.isStreaming && !item.content && (
|
|
172
188
|
<span
|
|
173
189
|
aria-hidden
|
|
174
190
|
data-streaming-caret=""
|
|
@@ -75,12 +75,12 @@ export function ChatMessage({
|
|
|
75
75
|
)}
|
|
76
76
|
>
|
|
77
77
|
{isUser ? (
|
|
78
|
-
<div className="whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)]">
|
|
78
|
+
<div className="whitespace-pre-wrap text-[length:var(--font-size-base)] leading-[var(--line-height-base)]">
|
|
79
79
|
{content}
|
|
80
80
|
</div>
|
|
81
81
|
) : (
|
|
82
82
|
<>
|
|
83
|
-
{content && <Markdown className="tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]">{content}</Markdown>}
|
|
83
|
+
{content && <Markdown className="tangle-prose text-[length:var(--font-size-base)] leading-[var(--line-height-base)]">{content}</Markdown>}
|
|
84
84
|
{isStreaming && (
|
|
85
85
|
<span className="ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-[var(--brand-cool)] align-text-bottom" />
|
|
86
86
|
)}
|
|
@@ -25,7 +25,7 @@ export function ThinkingIndicator({ className }: ThinkingIndicatorProps) {
|
|
|
25
25
|
<span className="h-[var(--indicator-dot-size)] w-[var(--indicator-dot-size)] animate-bounce rounded-full bg-[var(--brand-glow)]" style={{ animationDelay: "300ms" }} />
|
|
26
26
|
</div>
|
|
27
27
|
{elapsed > 3 && (
|
|
28
|
-
<span className="text-[var(--font-size-xs)] tabular-nums text-muted-foreground">
|
|
28
|
+
<span className="text-[length:var(--font-size-xs)] tabular-nums text-muted-foreground">
|
|
29
29
|
{elapsed}s
|
|
30
30
|
</span>
|
|
31
31
|
)}
|
|
@@ -37,12 +37,12 @@ function UserBubble({ text, timestamp, actions }: {
|
|
|
37
37
|
{timestamp ? (
|
|
38
38
|
<span
|
|
39
39
|
data-user-message-time=""
|
|
40
|
-
className="pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-[var(--font-size-xs)] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"
|
|
40
|
+
className="pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-[length:var(--font-size-xs)] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"
|
|
41
41
|
>
|
|
42
42
|
{formatTime(timestamp)}
|
|
43
43
|
</span>
|
|
44
44
|
) : null}
|
|
45
|
-
<div className="break-words whitespace-pre-wrap text-[var(--font-size-base)] leading-[1.5]">
|
|
45
|
+
<div className="break-words whitespace-pre-wrap text-[length:var(--font-size-base)] leading-[1.5]">
|
|
46
46
|
{text}
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "react";
|
|
10
10
|
import type SyntaxHighlighter from "react-syntax-highlighter";
|
|
11
11
|
import { Check, Copy } from "lucide-react";
|
|
12
|
-
import { focusRing } from "../lib/focus";
|
|
12
|
+
import { focusRing, focusRingInset } from "../lib/focus";
|
|
13
13
|
import { cn } from "../lib/utils";
|
|
14
14
|
|
|
15
15
|
// Leave variables unresolved: the browser resolves them on each token span,
|
|
@@ -118,6 +118,17 @@ export const CodeBlock = memo(
|
|
|
118
118
|
overflowX: "auto" as const,
|
|
119
119
|
};
|
|
120
120
|
const codeTagProps = { style: { fontFamily: "var(--font-mono, 'JetBrains Mono', ui-monospace, monospace)" } };
|
|
121
|
+
// A long line scrolls the block sideways, which a keyboard reaches only if
|
|
122
|
+
// the block takes focus (axe scrollable-region-focusable). A label on a bare
|
|
123
|
+
// <pre> is prohibited ARIA, so the block is a named region, as the terminal
|
|
124
|
+
// output regions are.
|
|
125
|
+
const scrollRegion = {
|
|
126
|
+
tabIndex: 0,
|
|
127
|
+
role: "region",
|
|
128
|
+
"aria-label": headerLabel ? `${headerLabel} code` : "Code",
|
|
129
|
+
// Inset: the rounded, overflow-clipped card would cut an outer ring.
|
|
130
|
+
className: focusRingInset,
|
|
131
|
+
} as const;
|
|
121
132
|
|
|
122
133
|
return (
|
|
123
134
|
<div
|
|
@@ -151,11 +162,12 @@ export const CodeBlock = memo(
|
|
|
151
162
|
customStyle={customStyle}
|
|
152
163
|
codeTagProps={codeTagProps}
|
|
153
164
|
wrapLines={false}
|
|
165
|
+
{...scrollRegion}
|
|
154
166
|
>
|
|
155
167
|
{code}
|
|
156
168
|
</Highlighter>
|
|
157
169
|
) : (
|
|
158
|
-
<pre style={{ ...syntaxTheme.hljs, ...customStyle }}>
|
|
170
|
+
<pre {...scrollRegion} style={{ ...syntaxTheme.hljs, ...customStyle }}>
|
|
159
171
|
<code style={{ ...codeTagProps.style, whiteSpace: "pre" }}>{code}</code>
|
|
160
172
|
</pre>
|
|
161
173
|
)}
|
|
@@ -11,6 +11,9 @@ export interface ExpandableContentProps {
|
|
|
11
11
|
*/
|
|
12
12
|
fadeClassName?: string;
|
|
13
13
|
className?: string;
|
|
14
|
+
/** Classes for the Show more / Show less toggle, e.g. an inset when the
|
|
15
|
+
* content sits flush against its card's edges. */
|
|
16
|
+
toggleClassName?: string;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
/**
|
|
@@ -23,6 +26,7 @@ export function ExpandableContent({
|
|
|
23
26
|
collapsedMaxPx = 180,
|
|
24
27
|
fadeClassName = "from-muted",
|
|
25
28
|
className,
|
|
29
|
+
toggleClassName,
|
|
26
30
|
}: ExpandableContentProps) {
|
|
27
31
|
const [expanded, setExpanded] = useState(false);
|
|
28
32
|
const [overflowing, setOverflowing] = useState(false);
|
|
@@ -63,7 +67,10 @@ export function ExpandableContent({
|
|
|
63
67
|
<button
|
|
64
68
|
type="button"
|
|
65
69
|
onClick={() => setExpanded((value) => !value)}
|
|
66
|
-
className=
|
|
70
|
+
className={cn(
|
|
71
|
+
"mt-1 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
72
|
+
toggleClassName,
|
|
73
|
+
)}
|
|
67
74
|
>
|
|
68
75
|
{expanded ? "Show less" : "Show more"}
|
|
69
76
|
</button>
|
|
@@ -23,6 +23,10 @@ export interface ExpandedToolDetailProps {
|
|
|
23
23
|
part: ToolPart;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// The clamped output sits flush against its card's edges; the toggle under it
|
|
27
|
+
// takes the card's 12px content inset so it does not touch the border.
|
|
28
|
+
const FLUSH_TOGGLE = "mx-3 mb-2";
|
|
29
|
+
|
|
26
30
|
const EXT_LANG: Record<string, string> = {
|
|
27
31
|
ts: "typescript", tsx: "tsx", js: "javascript", jsx: "jsx",
|
|
28
32
|
rs: "rust", py: "python", go: "go", rb: "ruby",
|
|
@@ -147,7 +151,7 @@ export const ExpandedToolDetail = memo(({ part }: ExpandedToolDetailProps) => {
|
|
|
147
151
|
Output
|
|
148
152
|
</span>
|
|
149
153
|
</div>
|
|
150
|
-
<ExpandableContent fadeClassName="from-card">
|
|
154
|
+
<ExpandableContent fadeClassName="from-card" toggleClassName={FLUSH_TOGGLE}>
|
|
151
155
|
<CodeBlock
|
|
152
156
|
code={
|
|
153
157
|
outputStr.length > 2000
|
|
@@ -170,7 +174,7 @@ export const ExpandedToolDetail = memo(({ part }: ExpandedToolDetailProps) => {
|
|
|
170
174
|
Error
|
|
171
175
|
</span>
|
|
172
176
|
</div>
|
|
173
|
-
<ExpandableContent fadeClassName="from-[var(--surface-danger-bg)]">
|
|
177
|
+
<ExpandableContent fadeClassName="from-[var(--surface-danger-bg)]" toggleClassName={FLUSH_TOGGLE}>
|
|
174
178
|
<pre className="bg-[var(--surface-danger-bg)] p-3 text-xs font-mono whitespace-pre-wrap break-all text-[var(--surface-danger-text)]">
|
|
175
179
|
{error}
|
|
176
180
|
</pre>
|