diff --git a/ui/src/styles/chat/grouped.css b/ui/src/styles/chat/grouped.css index 2decc8f01b6..24c2626b91b 100644 --- a/ui/src/styles/chat/grouped.css +++ b/ui/src/styles/chat/grouped.css @@ -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) { diff --git a/ui/src/styles/chat/tool-cards.css b/ui/src/styles/chat/tool-cards.css index 540a4391660..b032d9217ee 100644 --- a/ui/src/styles/chat/tool-cards.css +++ b/ui/src/styles/chat/tool-cards.css @@ -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; diff --git a/ui/src/ui/chat/grouped-render.test.ts b/ui/src/ui/chat/grouped-render.test.ts index 62be0cd51f0..7a22a95132f 100644 --- a/ui/src/ui/chat/grouped-render.test.ts +++ b/ui/src/ui/chat/grouped-render.test.ts @@ -491,6 +491,7 @@ describe("grouped chat rendering", () => { isToolMessageExpanded: () => false, }); + expect(container.querySelector(".chat-bubble--tool-shell")).not.toBeNull(); const summary = container.querySelector(".chat-tool-msg-summary"); expect(summary?.textContent).toContain("Tool call"); expect(container.textContent).not.toContain('"thread": true'); diff --git a/ui/src/ui/chat/grouped-render.ts b/ui/src/ui/chat/grouped-render.ts index cc89d7f61e5..aa240a6bcf9 100644 --- a/ui/src/ui/chat/grouped-render.ts +++ b/ui/src/ui/chat/grouped-render.ts @@ -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))];