fix(ui): polish tool call cards

This commit is contained in:
Val Alexander
2026-04-24 20:17:54 -05:00
parent 640df81912
commit 2c5b780804
4 changed files with 114 additions and 8 deletions

View File

@@ -23,8 +23,10 @@
flex-direction: column;
gap: 2px;
flex: 1 1 auto;
width: 100%;
max-width: min(900px, 68%);
align-items: flex-start;
min-width: 0;
}
/* User messages align content right */
@@ -32,6 +34,10 @@
align-items: flex-end;
}
.chat-group.tool .chat-group-messages {
max-width: min(980px, calc(100% - 46px));
}
.chat-group.user .chat-group-footer {
justify-content: flex-end;
}
@@ -193,9 +199,23 @@ img.chat-avatar {
width: auto;
max-width: 100%;
box-sizing: border-box;
min-width: 0;
word-wrap: break-word;
}
.chat-bubble--tool-shell {
align-self: stretch;
width: min(100%, 760px);
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
}
.chat-bubble--tool-shell:hover {
background: transparent;
}
.chat-bubble.has-copy {
padding-right: 70px;
}
@@ -297,10 +317,19 @@ img.chat-avatar {
box-shadow: inset 0 1px 0 var(--card-highlight);
}
:root[data-theme-mode="light"] .chat-bubble--tool-shell {
border-color: transparent;
box-shadow: none;
}
.chat-bubble:hover {
background: var(--bg-hover);
}
.chat-bubble--tool-shell:hover {
background: transparent;
}
/* User bubbles have different styling */
.chat-group.user .chat-bubble {
background: var(--accent-subtle);
@@ -488,6 +517,10 @@ img.chat-avatar {
.chat-group-messages {
max-width: 82%;
}
.chat-group.tool .chat-group-messages {
max-width: calc(100% - 46px);
}
}
@media (max-width: 400px) {

View File

@@ -1,5 +1,8 @@
/* Tool Card Styles */
.chat-tool-card {
box-sizing: border-box;
min-width: 0;
max-width: 100%;
border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
border-radius: var(--radius-md);
padding: 12px 14px;
@@ -45,6 +48,7 @@
justify-content: space-between;
align-items: flex-start;
gap: 10px;
min-width: 0;
}
.chat-tool-card__actions {
@@ -62,6 +66,7 @@
font-weight: 600;
font-size: 14px;
line-height: 1.2;
min-width: 0;
}
.chat-tool-card__icon {
@@ -170,6 +175,7 @@
.chat-tool-card__block {
margin-top: 12px;
min-width: 0;
}
.chat-tool-card__preview,
@@ -333,6 +339,9 @@
.chat-tool-card__block-preview,
.chat-tool-card__block-content,
.chat-tool-card__block-empty {
box-sizing: border-box;
max-width: 100%;
min-width: 0;
margin: 0;
padding: 11px 12px;
border-radius: var(--radius-md);
@@ -341,6 +350,7 @@
font-size: 11px;
line-height: 1.45;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
}
@@ -522,14 +532,19 @@
}
.chat-tool-msg-collapse {
margin-top: 2px;
width: 100%;
min-width: 0;
max-width: 100%;
margin-top: 6px;
}
.chat-tool-msg-summary {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
gap: 8px;
min-width: 0;
box-sizing: border-box;
padding: 8px 11px;
cursor: pointer;
font-size: 12px;
color: var(--muted);
@@ -537,7 +552,16 @@
list-style: none;
border: 1px solid color-mix(in srgb, var(--border) 75%, transparent);
border-radius: var(--radius-md);
background: color-mix(in srgb, var(--bg-hover) 35%, transparent);
background:
linear-gradient(
90deg,
color-mix(in srgb, var(--accent) 9%, transparent),
transparent 34%
),
color-mix(in srgb, var(--card) 86%, var(--secondary) 14%);
box-shadow:
inset 0 1px 0 color-mix(in srgb, var(--bg) 76%, transparent),
0 8px 22px color-mix(in srgb, black 12%, transparent);
width: 100%;
text-align: left;
appearance: none;
@@ -550,7 +574,13 @@
}
.chat-tool-msg-summary[type="button"] {
background: color-mix(in srgb, var(--bg-hover) 35%, transparent);
background:
linear-gradient(
90deg,
color-mix(in srgb, var(--accent) 9%, transparent),
transparent 34%
),
color-mix(in srgb, var(--card) 86%, var(--secondary) 14%);
}
.chat-tool-msg-summary::-webkit-details-marker {
@@ -579,10 +609,22 @@
.chat-tool-msg-summary:hover {
color: var(--text);
background: color-mix(in srgb, var(--bg-hover) 60%, transparent);
background:
linear-gradient(
90deg,
color-mix(in srgb, var(--accent) 13%, transparent),
transparent 38%
),
color-mix(in srgb, var(--card) 76%, var(--bg-hover) 24%);
border-color: color-mix(in srgb, var(--border-strong) 70%, transparent);
}
.chat-tool-msg-collapse--manual.is-open > .chat-tool-msg-summary,
.chat-tool-msg-collapse[open] > .chat-tool-msg-summary {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.chat-tool-msg-summary__icon {
display: inline-flex;
align-items: center;
@@ -669,9 +711,34 @@
}
.chat-tool-msg-body {
box-sizing: border-box;
min-width: 0;
max-width: 100%;
padding-top: 8px;
}
.chat-bubble--tool-shell .chat-tool-msg-body {
margin-top: 0;
padding: 12px 14px 14px;
border: 1px solid color-mix(in srgb, var(--border) 75%, transparent);
border-top: 0;
border-radius: 0 0 var(--radius-md) var(--radius-md);
background: color-mix(in srgb, var(--card) 82%, var(--secondary) 18%);
box-shadow: inset 0 1px 0 color-mix(in srgb, var(--bg) 68%, transparent);
overflow: hidden;
}
.chat-bubble--tool-shell .chat-tool-msg-body > .chat-text {
max-height: min(46vh, 540px);
margin: 0;
overflow: auto;
font-family: var(--mono);
font-size: 12px;
line-height: 1.55;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
/* Reading Indicator */
.chat-reading-indicator {
background: transparent;

View File

@@ -491,6 +491,7 @@ describe("grouped chat rendering", () => {
isToolMessageExpanded: () => false,
});
expect(container.querySelector(".chat-bubble--tool-shell")).not.toBeNull();
const summary = container.querySelector<HTMLElement>(".chat-tool-msg-summary");
expect(summary?.textContent).toContain("Tool call");
expect(container.textContent).not.toContain('"thread": true');

View File

@@ -1404,7 +1404,13 @@ function renderGroupedMessage(
// Detect pure-JSON messages and render as collapsible block
const jsonResult = markdown && !opts.isStreaming ? detectJson(markdown) : null;
const bubbleClasses = ["chat-bubble", opts.isStreaming ? "streaming" : "", "fade-in"]
const isToolMessage = normalizedRole === "tool" || isToolResult;
const bubbleClasses = [
"chat-bubble",
isToolMessage ? "chat-bubble--tool-shell" : "",
opts.isStreaming ? "streaming" : "",
"fade-in",
]
.filter(Boolean)
.join(" ");
@@ -1421,7 +1427,6 @@ function renderGroupedMessage(
return nothing;
}
const isToolMessage = normalizedRole === "tool" || isToolResult;
const toolMessageDisclosureId = `toolmsg:${messageKey}`;
const toolMessageExpanded = opts.isToolMessageExpanded?.(toolMessageDisclosureId) ?? false;
const toolNames = [...new Set(toolCards.map((c) => c.name))];