diff --git a/ui/src/styles/chat.css b/ui/src/styles/chat.css index 694bd110d..b23ea3eaa 100644 --- a/ui/src/styles/chat.css +++ b/ui/src/styles/chat.css @@ -1,789 +1,6 @@ -/* ============================================= - CHAT CARD LAYOUT - Flex container with sticky compose - ============================================= */ - -/* Main chat card - flex column layout, transparent background */ -.chat { - position: relative; - display: flex; - flex-direction: column; - flex: 1 1 0; - height: 100%; - min-height: 0; /* Allow flex shrinking */ - overflow: hidden; - background: transparent !important; - border: none !important; - box-shadow: none !important; -} - -/* Chat header - fixed at top, transparent */ -.chat-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - flex-wrap: nowrap; - flex-shrink: 0; - padding-bottom: 12px; - margin-bottom: 12px; - background: transparent; -} - -.chat-header__left { - display: flex; - align-items: center; - gap: 12px; - flex-wrap: wrap; - min-width: 0; -} - -.chat-header__right { - display: flex; - align-items: center; - gap: 8px; -} - -.chat-session { - min-width: 180px; -} - -/* Chat thread - scrollable middle section, transparent */ -.chat-thread { - flex: 1 1 0; /* Grow, shrink, and use 0 base for proper scrolling */ - overflow-y: auto; - overflow-x: hidden; - padding: 12px; - margin: 0 -12px; - min-height: 0; /* Allow shrinking for flex scroll behavior */ - border-radius: 12px; - background: transparent; -} - -/* Focus mode exit button */ -.chat-focus-exit { - position: absolute; - top: 12px; - right: 12px; - z-index: 100; - width: 32px; - height: 32px; - border-radius: 50%; - border: 1px solid var(--border); - background: var(--panel); - color: var(--muted); - font-size: 20px; - line-height: 1; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background 150ms ease-out, color 150ms ease-out, border-color 150ms ease-out; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); -} - -.chat-focus-exit:hover { - background: var(--panel-strong); - color: var(--text); - border-color: var(--accent); -} - -/* Chat compose - sticky at bottom */ -.chat-compose { - position: sticky; - bottom: 0; - flex-shrink: 0; - display: flex; - align-items: flex-end; - gap: 12px; - margin-top: auto; /* Push to bottom of flex container */ - padding: 16px 0 4px; - background: linear-gradient(to bottom, transparent, var(--bg) 20%); - z-index: 10; -} - -.chat-compose__field { - flex: 1 1 auto; - min-width: 0; -} - -/* Hide the "Message" label - keep textarea only */ -.chat-compose__field > span { - display: none; -} - -/* Override .field textarea min-height (180px) from components.css */ -.chat-compose .chat-compose__field textarea { - width: 100%; - min-height: 36px; - max-height: 150px; - padding: 8px 12px; - border-radius: 10px; - resize: vertical; - white-space: pre-wrap; - font-family: var(--font-body); - font-size: 14px; - line-height: 1.45; -} - -.chat-compose__field textarea:disabled { - opacity: 0.7; - cursor: not-allowed; -} - -.chat-compose__actions { - flex-shrink: 0; - display: flex; - align-items: stretch; -} - -.chat-compose .chat-compose__actions .btn { - padding: 8px 16px; - font-size: 13px; - min-height: 36px; - white-space: nowrap; -} - -/* Chat controls - moved to content-header area, left aligned */ -.chat-controls { - display: flex; - align-items: center; - justify-content: flex-start; - gap: 12px; - flex-wrap: wrap; -} - -.chat-controls__session { - min-width: 140px; -} - -.chat-controls__thinking { - display: flex; - align-items: center; - gap: 6px; - font-size: 13px; -} - -/* Icon button style */ -.btn--icon { - padding: 8px !important; - min-width: 36px; - height: 36px; - display: inline-flex; - align-items: center; - justify-content: center; - border: 1px solid var(--border); - background: rgba(255, 255, 255, 0.06); -} - -/* Controls separator */ -.chat-controls__separator { - color: rgba(255, 255, 255, 0.4); - font-size: 18px; - margin: 0 8px; - font-weight: 300; -} - -:root[data-theme="light"] .chat-controls__separator { - color: rgba(16, 24, 40, 0.3); -} - -.btn--icon:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.2); -} - -/* Light theme icon button overrides */ -:root[data-theme="light"] .btn--icon { - background: rgba(255, 255, 255, 0.9); - border-color: rgba(16, 24, 40, 0.2); - box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05); - color: rgba(16, 24, 40, 0.7); -} - -:root[data-theme="light"] .btn--icon:hover { - background: rgba(255, 255, 255, 1); - border-color: rgba(16, 24, 40, 0.3); - color: rgba(16, 24, 40, 0.9); -} - -.btn--icon svg { - display: block; -} - -.chat-controls__session select { - padding: 6px 10px; - font-size: 13px; -} - -.chat-controls__thinking { - display: flex; - align-items: center; - gap: 4px; - font-size: 12px; - padding: 4px 10px; - background: rgba(255, 255, 255, 0.04); - border-radius: 6px; - border: 1px solid var(--border); -} - -/* Light theme thinking indicator override */ -:root[data-theme="light"] .chat-controls__thinking { - background: rgba(255, 255, 255, 0.9); - border-color: rgba(16, 24, 40, 0.15); -} - -@media (max-width: 640px) { - .chat-session { - min-width: 140px; - } - - .chat-compose { - grid-template-columns: 1fr; - } - - .chat-controls { - flex-wrap: wrap; - gap: 8px; - } - - .chat-controls__session { - min-width: 120px; - } -} - -/* ============================================= - LEGACY CHAT LINE LAYOUT (non-grouped) - ============================================= */ - -.chat-line { - display: flex; - margin-bottom: 12px; -} - -.chat-line.user { - justify-content: flex-end; -} - -.chat-line.assistant, -.chat-line.other { - justify-content: flex-start; -} - -.chat-msg { - display: grid; - gap: 6px; - max-width: min(900px, 95%); -} - -.chat-line.user .chat-msg { - justify-items: end; -} - -.chat-stamp { - font-size: 11px; - color: var(--muted); -} - -.chat-line.user .chat-stamp { - text-align: right; -} - -/* ============================================= - CHAT TEXT STYLING - ============================================= */ - -.chat-text { - font-size: 14px; - line-height: 1.5; - word-wrap: break-word; - overflow-wrap: break-word; -} - -.chat-text :where(p, ul, ol, pre, blockquote, table) { - margin: 0; -} - -.chat-text :where(p + p, p + ul, p + ol, p + pre, p + blockquote) { - margin-top: 0.75em; -} - -.chat-text :where(ul, ol) { - padding-left: 1.5em; -} - -.chat-text :where(li + li) { - margin-top: 0.25em; -} - -.chat-text :where(a) { - color: var(--accent); - text-decoration: underline; - text-underline-offset: 2px; -} - -.chat-text :where(a:hover) { - opacity: 0.8; -} - -.chat-text :where(code) { - font-family: var(--mono); - font-size: 0.9em; -} - -.chat-text :where(:not(pre) > code) { - background: rgba(0, 0, 0, 0.15); - padding: 0.15em 0.4em; - border-radius: 4px; -} - -.chat-text :where(pre) { - background: rgba(0, 0, 0, 0.15); - border-radius: 6px; - padding: 10px 12px; - overflow-x: auto; -} - -.chat-text :where(pre code) { - background: none; - padding: 0; -} - -.chat-text :where(blockquote) { - border-left: 3px solid var(--border); - padding-left: 12px; - color: var(--muted); -} - -.chat-text :where(hr) { - border: none; - border-top: 1px solid var(--border); - margin: 1em 0; -} - -/* ============================================= - GROUPED CHAT LAYOUT (Slack-style) - ============================================= */ - -/* Chat Group Layout - default (assistant/other on left) */ -.chat-group { - display: flex; - gap: 12px; - align-items: flex-start; - margin-bottom: 16px; - margin-left: 16px; - margin-right: 16px; -} - -/* User messages on right */ -.chat-group.user { - flex-direction: row-reverse; - justify-content: flex-start; -} - -.chat-group-messages { - display: flex; - flex-direction: column; - gap: 2px; - max-width: min(900px, calc(100% - 60px)); -} - -/* User messages align content right */ -.chat-group.user .chat-group-messages { - align-items: flex-end; -} - -.chat-group.user .chat-group-footer { - justify-content: flex-end; -} - -/* Footer at bottom of message group (role + time) */ -.chat-group-footer { - display: flex; - gap: 8px; - align-items: baseline; - margin-top: 6px; -} - -.chat-sender-name { - font-weight: 500; - font-size: 12px; - color: var(--muted); -} - -.chat-group-timestamp { - font-size: 11px; - color: var(--muted); - opacity: 0.7; -} - -/* Avatar Styles */ -.chat-avatar { - width: 40px; - height: 40px; - border-radius: 8px; - background: var(--panel-strong); - display: grid; - place-items: center; - font-weight: 600; - font-size: 14px; - flex-shrink: 0; - align-self: flex-end; /* Align with last message in group */ - margin-bottom: 4px; /* Optical alignment */ -} - -.chat-avatar.user { - background: rgba(245, 159, 74, 0.2); - color: rgba(245, 159, 74, 1); -} - -.chat-avatar.assistant { - background: rgba(52, 199, 183, 0.2); - color: rgba(52, 199, 183, 1); -} - -.chat-avatar.other { - background: rgba(150, 150, 150, 0.2); - color: rgba(150, 150, 150, 1); -} - -/* Minimal Bubble Design - dynamic width based on content */ -.chat-bubble { - display: inline-block; - border: 1px solid var(--border); - background: rgba(0, 0, 0, 0.12); - border-radius: 12px; - padding: 10px 14px; - box-shadow: none; - transition: background 150ms ease-out, border-color 150ms ease-out; - max-width: 100%; - word-wrap: break-word; -} - -.chat-bubble:hover { - background: rgba(0, 0, 0, 0.18); -} - -/* User bubbles have different styling */ -.chat-group.user .chat-bubble { - background: rgba(245, 159, 74, 0.15); - border-color: rgba(245, 159, 74, 0.3); -} - -.chat-group.user .chat-bubble:hover { - background: rgba(245, 159, 74, 0.22); -} - -/* Streaming animation */ -.chat-bubble.streaming { - animation: pulsing-border 1.5s ease-out infinite; -} - -@keyframes pulsing-border { - 0%, 100% { - border-color: var(--border); - } - 50% { - border-color: var(--accent); - } -} - -/* Fade-in animation for new messages */ -.chat-bubble.fade-in { - animation: fade-in 200ms ease-out; -} - -@keyframes fade-in { - from { - opacity: 0; - transform: translateY(4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Tool Card Styles */ -.chat-tool-card { - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin-top: 8px; - transition: border-color 150ms ease-out, background 150ms ease-out; - /* Fixed max-height to ensure cards don't expand too much */ - max-height: 120px; - overflow: hidden; -} - -.chat-tool-card:hover { - border-color: var(--accent); - background: rgba(0, 0, 0, 0.06); -} - -/* First tool card in a group - no top margin */ -.chat-tool-card:first-child { - margin-top: 0; -} - -.chat-tool-card--clickable { - cursor: pointer; -} - -.chat-tool-card--clickable:focus { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -/* Header with title and chevron */ -.chat-tool-card__header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; -} - -.chat-tool-card__title { - display: inline-flex; - align-items: center; - gap: 6px; - font-weight: 600; - font-size: 13px; - line-height: 1.2; -} - -.chat-tool-card__icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - font-size: 14px; - line-height: 1; - font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif; - vertical-align: middle; - flex-shrink: 0; -} - -/* "View >" action link */ -.chat-tool-card__action { - font-size: 12px; - color: var(--accent); - opacity: 0.8; - transition: opacity 150ms ease-out; -} - -.chat-tool-card--clickable:hover .chat-tool-card__action { - opacity: 1; -} - -/* Status indicator for completed/empty results */ -.chat-tool-card__status { - font-size: 14px; - color: var(--ok); -} - -.chat-tool-card__status-text { - font-size: 11px; - margin-top: 4px; -} - -.chat-tool-card__detail { - font-size: 12px; - color: var(--muted); - margin-top: 4px; -} - -/* Collapsed preview - fixed height with truncation */ -.chat-tool-card__preview { - font-size: 11px; - color: var(--muted); - margin-top: 8px; - padding: 8px 10px; - background: rgba(0, 0, 0, 0.08); - border-radius: 6px; - white-space: pre-wrap; - overflow: hidden; - max-height: 44px; - line-height: 1.4; - border: 1px solid rgba(255, 255, 255, 0.04); -} - -.chat-tool-card--clickable:hover .chat-tool-card__preview { - background: rgba(0, 0, 0, 0.12); - border-color: rgba(255, 255, 255, 0.08); -} - -/* Short inline output */ -.chat-tool-card__inline { - font-size: 11px; - color: var(--text); - margin-top: 6px; - padding: 6px 8px; - background: rgba(0, 0, 0, 0.06); - border-radius: 4px; - white-space: pre-wrap; - word-break: break-word; -} - -/* Reading Indicator */ -.chat-reading-indicator { - background: transparent; - border: 1px solid var(--border); - padding: 12px; - display: inline-flex; -} - -.chat-reading-indicator__dots { - display: flex; - gap: 6px; - align-items: center; -} - -.chat-reading-indicator__dots span { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--muted); - animation: reading-pulse 1.4s ease-in-out infinite; -} - -.chat-reading-indicator__dots span:nth-child(1) { - animation-delay: 0s; -} - -.chat-reading-indicator__dots span:nth-child(2) { - animation-delay: 0.2s; -} - -.chat-reading-indicator__dots span:nth-child(3) { - animation-delay: 0.4s; -} - -@keyframes reading-pulse { - 0%, 60%, 100% { - opacity: 0.3; - transform: scale(0.8); - } - 30% { - opacity: 1; - transform: scale(1); - } -} - -/* Split View Layout */ -.chat-split-container { - display: flex; - gap: 0; - flex: 1; - min-height: 0; - height: 100%; -} - -.chat-main { - min-width: 400px; - display: flex; - flex-direction: column; - overflow: hidden; - /* Smooth transition when sidebar opens/closes */ - transition: flex 250ms ease-out; -} - -.chat-sidebar { - flex: 1; - min-width: 300px; - border-left: 1px solid var(--border); - display: flex; - flex-direction: column; - overflow: hidden; - animation: slide-in 200ms ease-out; -} - -@keyframes slide-in { - from { - opacity: 0; - transform: translateX(20px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -/* Sidebar Panel */ -.sidebar-panel { - display: flex; - flex-direction: column; - height: 100%; - background: var(--panel); -} - -.sidebar-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 16px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 0; - z-index: 10; - background: var(--panel); -} - -/* Smaller close button for sidebar */ -.sidebar-header .btn { - padding: 4px 8px; - font-size: 14px; - min-width: auto; - line-height: 1; -} - -.sidebar-title { - font-weight: 600; - font-size: 14px; -} - -.sidebar-content { - flex: 1; - overflow: auto; - padding: 16px; -} - -.sidebar-markdown { - font-size: 14px; - line-height: 1.5; -} - -.sidebar-markdown pre { - background: rgba(0, 0, 0, 0.12); - border-radius: 4px; - padding: 12px; - overflow-x: auto; -} - -.sidebar-markdown code { - font-family: var(--mono); - font-size: 13px; -} - -/* Mobile: Full-screen modal */ -@media (max-width: 768px) { - .chat-split-container { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 1000; - } - - .chat-main { - display: none; /* Hide chat on mobile when sidebar open */ - } - - .chat-sidebar { - width: 100%; - min-width: 0; - border-left: none; - } -} +@import "./chat/layout.css"; +@import "./chat/legacy.css"; +@import "./chat/text.css"; +@import "./chat/grouped.css"; +@import "./chat/tool-cards.css"; +@import "./chat/sidebar.css"; diff --git a/ui/src/styles/chat/grouped.css b/ui/src/styles/chat/grouped.css new file mode 100644 index 000000000..879a0c8bc --- /dev/null +++ b/ui/src/styles/chat/grouped.css @@ -0,0 +1,143 @@ +/* ============================================= + GROUPED CHAT LAYOUT (Slack-style) + ============================================= */ + +/* Chat Group Layout - default (assistant/other on left) */ +.chat-group { + display: flex; + gap: 12px; + align-items: flex-start; + margin-bottom: 16px; + margin-left: 16px; + margin-right: 16px; +} + +/* User messages on right */ +.chat-group.user { + flex-direction: row-reverse; + justify-content: flex-start; +} + +.chat-group-messages { + display: flex; + flex-direction: column; + gap: 2px; + max-width: min(900px, calc(100% - 60px)); +} + +/* User messages align content right */ +.chat-group.user .chat-group-messages { + align-items: flex-end; +} + +.chat-group.user .chat-group-footer { + justify-content: flex-end; +} + +/* Footer at bottom of message group (role + time) */ +.chat-group-footer { + display: flex; + gap: 8px; + align-items: baseline; + margin-top: 6px; +} + +.chat-sender-name { + font-weight: 500; + font-size: 12px; + color: var(--muted); +} + +.chat-group-timestamp { + font-size: 11px; + color: var(--muted); + opacity: 0.7; +} + +/* Avatar Styles */ +.chat-avatar { + width: 40px; + height: 40px; + border-radius: 8px; + background: var(--panel-strong); + display: grid; + place-items: center; + font-weight: 600; + font-size: 14px; + flex-shrink: 0; + align-self: flex-end; /* Align with last message in group */ + margin-bottom: 4px; /* Optical alignment */ +} + +.chat-avatar.user { + background: rgba(245, 159, 74, 0.2); + color: rgba(245, 159, 74, 1); +} + +.chat-avatar.assistant { + background: rgba(52, 199, 183, 0.2); + color: rgba(52, 199, 183, 1); +} + +.chat-avatar.other { + background: rgba(150, 150, 150, 0.2); + color: rgba(150, 150, 150, 1); +} + +/* Minimal Bubble Design - dynamic width based on content */ +.chat-bubble { + display: inline-block; + border: 1px solid var(--border); + background: rgba(0, 0, 0, 0.12); + border-radius: 12px; + padding: 10px 14px; + box-shadow: none; + transition: background 150ms ease-out, border-color 150ms ease-out; + max-width: 100%; + word-wrap: break-word; +} + +.chat-bubble:hover { + background: rgba(0, 0, 0, 0.18); +} + +/* User bubbles have different styling */ +.chat-group.user .chat-bubble { + background: rgba(245, 159, 74, 0.15); + border-color: rgba(245, 159, 74, 0.3); +} + +.chat-group.user .chat-bubble:hover { + background: rgba(245, 159, 74, 0.22); +} + +/* Streaming animation */ +.chat-bubble.streaming { + animation: pulsing-border 1.5s ease-out infinite; +} + +@keyframes pulsing-border { + 0%, 100% { + border-color: var(--border); + } + 50% { + border-color: var(--accent); + } +} + +/* Fade-in animation for new messages */ +.chat-bubble.fade-in { + animation: fade-in 200ms ease-out; +} + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css new file mode 100644 index 000000000..a416d92cd --- /dev/null +++ b/ui/src/styles/chat/layout.css @@ -0,0 +1,253 @@ +/* ============================================= + CHAT CARD LAYOUT - Flex container with sticky compose + ============================================= */ + +/* Main chat card - flex column layout, transparent background */ +.chat { + position: relative; + display: flex; + flex-direction: column; + flex: 1 1 0; + height: 100%; + min-height: 0; /* Allow flex shrinking */ + overflow: hidden; + background: transparent !important; + border: none !important; + box-shadow: none !important; +} + +/* Chat header - fixed at top, transparent */ +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + flex-wrap: nowrap; + flex-shrink: 0; + padding-bottom: 12px; + margin-bottom: 12px; + background: transparent; +} + +.chat-header__left { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + min-width: 0; +} + +.chat-header__right { + display: flex; + align-items: center; + gap: 8px; +} + +.chat-session { + min-width: 180px; +} + +/* Chat thread - scrollable middle section, transparent */ +.chat-thread { + flex: 1 1 0; /* Grow, shrink, and use 0 base for proper scrolling */ + overflow-y: auto; + overflow-x: hidden; + padding: 12px; + margin: 0 -12px; + min-height: 0; /* Allow shrinking for flex scroll behavior */ + border-radius: 12px; + background: transparent; +} + +/* Focus mode exit button */ +.chat-focus-exit { + position: absolute; + top: 12px; + right: 12px; + z-index: 100; + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid var(--border); + background: var(--panel); + color: var(--muted); + font-size: 20px; + line-height: 1; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 150ms ease-out, color 150ms ease-out, border-color 150ms ease-out; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +.chat-focus-exit:hover { + background: var(--panel-strong); + color: var(--text); + border-color: var(--accent); +} + +/* Chat compose - sticky at bottom */ +.chat-compose { + position: sticky; + bottom: 0; + flex-shrink: 0; + display: flex; + align-items: flex-end; + gap: 12px; + margin-top: auto; /* Push to bottom of flex container */ + padding: 16px 0 4px; + background: linear-gradient(to bottom, transparent, var(--bg) 20%); + z-index: 10; +} + +.chat-compose__field { + flex: 1 1 auto; + min-width: 0; +} + +/* Hide the "Message" label - keep textarea only */ +.chat-compose__field > span { + display: none; +} + +/* Override .field textarea min-height (180px) from components.css */ +.chat-compose .chat-compose__field textarea { + width: 100%; + min-height: 36px; + max-height: 150px; + padding: 8px 12px; + border-radius: 10px; + resize: vertical; + white-space: pre-wrap; + font-family: var(--font-body); + font-size: 14px; + line-height: 1.45; +} + +.chat-compose__field textarea:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.chat-compose__actions { + flex-shrink: 0; + display: flex; + align-items: stretch; +} + +.chat-compose .chat-compose__actions .btn { + padding: 8px 16px; + font-size: 13px; + min-height: 36px; + white-space: nowrap; +} + +/* Chat controls - moved to content-header area, left aligned */ +.chat-controls { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 12px; + flex-wrap: wrap; +} + +.chat-controls__session { + min-width: 140px; +} + +.chat-controls__thinking { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; +} + +/* Icon button style */ +.btn--icon { + padding: 8px !important; + min-width: 36px; + height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.06); +} + +/* Controls separator */ +.chat-controls__separator { + color: rgba(255, 255, 255, 0.4); + font-size: 18px; + margin: 0 8px; + font-weight: 300; +} + +:root[data-theme="light"] .chat-controls__separator { + color: rgba(16, 24, 40, 0.3); +} + +.btn--icon:hover { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.2); +} + +/* Light theme icon button overrides */ +:root[data-theme="light"] .btn--icon { + background: rgba(255, 255, 255, 0.9); + border-color: rgba(16, 24, 40, 0.2); + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05); + color: rgba(16, 24, 40, 0.7); +} + +:root[data-theme="light"] .btn--icon:hover { + background: rgba(255, 255, 255, 1); + border-color: rgba(16, 24, 40, 0.3); + color: rgba(16, 24, 40, 0.9); +} + +.btn--icon svg { + display: block; +} + +.chat-controls__session select { + padding: 6px 10px; + font-size: 13px; +} + +.chat-controls__thinking { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + padding: 4px 10px; + background: rgba(255, 255, 255, 0.04); + border-radius: 6px; + border: 1px solid var(--border); +} + +/* Light theme thinking indicator override */ +:root[data-theme="light"] .chat-controls__thinking { + background: rgba(255, 255, 255, 0.9); + border-color: rgba(16, 24, 40, 0.15); +} + +@media (max-width: 640px) { + .chat-session { + min-width: 140px; + } + + .chat-compose { + grid-template-columns: 1fr; + } + + .chat-controls { + flex-wrap: wrap; + gap: 8px; + } + + .chat-controls__session { + min-width: 120px; + } +} + diff --git a/ui/src/styles/chat/legacy.css b/ui/src/styles/chat/legacy.css new file mode 100644 index 000000000..90601d0ca --- /dev/null +++ b/ui/src/styles/chat/legacy.css @@ -0,0 +1,37 @@ +/* ============================================= + LEGACY CHAT LINE LAYOUT (non-grouped) + ============================================= */ + +.chat-line { + display: flex; + margin-bottom: 12px; +} + +.chat-line.user { + justify-content: flex-end; +} + +.chat-line.assistant, +.chat-line.other { + justify-content: flex-start; +} + +.chat-msg { + display: grid; + gap: 6px; + max-width: min(900px, 95%); +} + +.chat-line.user .chat-msg { + justify-items: end; +} + +.chat-stamp { + font-size: 11px; + color: var(--muted); +} + +.chat-line.user .chat-stamp { + text-align: right; +} + diff --git a/ui/src/styles/chat/sidebar.css b/ui/src/styles/chat/sidebar.css new file mode 100644 index 000000000..af7006778 --- /dev/null +++ b/ui/src/styles/chat/sidebar.css @@ -0,0 +1,118 @@ +/* Split View Layout */ +.chat-split-container { + display: flex; + gap: 0; + flex: 1; + min-height: 0; + height: 100%; +} + +.chat-main { + min-width: 400px; + display: flex; + flex-direction: column; + overflow: hidden; + /* Smooth transition when sidebar opens/closes */ + transition: flex 250ms ease-out; +} + +.chat-sidebar { + flex: 1; + min-width: 300px; + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + overflow: hidden; + animation: slide-in 200ms ease-out; +} + +@keyframes slide-in { + from { + opacity: 0; + transform: translateX(20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Sidebar Panel */ +.sidebar-panel { + display: flex; + flex-direction: column; + height: 100%; + background: var(--panel); +} + +.sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 0; + z-index: 10; + background: var(--panel); +} + +/* Smaller close button for sidebar */ +.sidebar-header .btn { + padding: 4px 8px; + font-size: 14px; + min-width: auto; + line-height: 1; +} + +.sidebar-title { + font-weight: 600; + font-size: 14px; +} + +.sidebar-content { + flex: 1; + overflow: auto; + padding: 16px; +} + +.sidebar-markdown { + font-size: 14px; + line-height: 1.5; +} + +.sidebar-markdown pre { + background: rgba(0, 0, 0, 0.12); + border-radius: 4px; + padding: 12px; + overflow-x: auto; +} + +.sidebar-markdown code { + font-family: var(--mono); + font-size: 13px; +} + +/* Mobile: Full-screen modal */ +@media (max-width: 768px) { + .chat-split-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1000; + } + + .chat-main { + display: none; /* Hide chat on mobile when sidebar open */ + } + + .chat-sidebar { + width: 100%; + min-width: 0; + border-left: none; + } +} + diff --git a/ui/src/styles/chat/text.css b/ui/src/styles/chat/text.css new file mode 100644 index 000000000..5e42b258d --- /dev/null +++ b/ui/src/styles/chat/text.css @@ -0,0 +1,72 @@ +/* ============================================= + CHAT TEXT STYLING + ============================================= */ + +.chat-text { + font-size: 14px; + line-height: 1.5; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.chat-text :where(p, ul, ol, pre, blockquote, table) { + margin: 0; +} + +.chat-text :where(p + p, p + ul, p + ol, p + pre, p + blockquote) { + margin-top: 0.75em; +} + +.chat-text :where(ul, ol) { + padding-left: 1.5em; +} + +.chat-text :where(li + li) { + margin-top: 0.25em; +} + +.chat-text :where(a) { + color: var(--accent); + text-decoration: underline; + text-underline-offset: 2px; +} + +.chat-text :where(a:hover) { + opacity: 0.8; +} + +.chat-text :where(code) { + font-family: var(--mono); + font-size: 0.9em; +} + +.chat-text :where(:not(pre) > code) { + background: rgba(0, 0, 0, 0.15); + padding: 0.15em 0.4em; + border-radius: 4px; +} + +.chat-text :where(pre) { + background: rgba(0, 0, 0, 0.15); + border-radius: 6px; + padding: 10px 12px; + overflow-x: auto; +} + +.chat-text :where(pre code) { + background: none; + padding: 0; +} + +.chat-text :where(blockquote) { + border-left: 3px solid var(--border); + padding-left: 12px; + color: var(--muted); +} + +.chat-text :where(hr) { + border: none; + border-top: 1px solid var(--border); + margin: 1em 0; +} + diff --git a/ui/src/styles/chat/tool-cards.css b/ui/src/styles/chat/tool-cards.css new file mode 100644 index 000000000..d6998a35c --- /dev/null +++ b/ui/src/styles/chat/tool-cards.css @@ -0,0 +1,167 @@ +/* Tool Card Styles */ +.chat-tool-card { + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + margin-top: 8px; + transition: border-color 150ms ease-out, background 150ms ease-out; + /* Fixed max-height to ensure cards don't expand too much */ + max-height: 120px; + overflow: hidden; +} + +.chat-tool-card:hover { + border-color: var(--accent); + background: rgba(0, 0, 0, 0.06); +} + +/* First tool card in a group - no top margin */ +.chat-tool-card:first-child { + margin-top: 0; +} + +.chat-tool-card--clickable { + cursor: pointer; +} + +.chat-tool-card--clickable:focus { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +/* Header with title and chevron */ +.chat-tool-card__header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} + +.chat-tool-card__title { + display: inline-flex; + align-items: center; + gap: 6px; + font-weight: 600; + font-size: 13px; + line-height: 1.2; +} + +.chat-tool-card__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + font-size: 14px; + line-height: 1; + font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif; + vertical-align: middle; + flex-shrink: 0; +} + +/* "View >" action link */ +.chat-tool-card__action { + font-size: 12px; + color: var(--accent); + opacity: 0.8; + transition: opacity 150ms ease-out; +} + +.chat-tool-card--clickable:hover .chat-tool-card__action { + opacity: 1; +} + +/* Status indicator for completed/empty results */ +.chat-tool-card__status { + font-size: 14px; + color: var(--ok); +} + +.chat-tool-card__status-text { + font-size: 11px; + margin-top: 4px; +} + +.chat-tool-card__detail { + font-size: 12px; + color: var(--muted); + margin-top: 4px; +} + +/* Collapsed preview - fixed height with truncation */ +.chat-tool-card__preview { + font-size: 11px; + color: var(--muted); + margin-top: 8px; + padding: 8px 10px; + background: rgba(0, 0, 0, 0.08); + border-radius: 6px; + white-space: pre-wrap; + overflow: hidden; + max-height: 44px; + line-height: 1.4; + border: 1px solid rgba(255, 255, 255, 0.04); +} + +.chat-tool-card--clickable:hover .chat-tool-card__preview { + background: rgba(0, 0, 0, 0.12); + border-color: rgba(255, 255, 255, 0.08); +} + +/* Short inline output */ +.chat-tool-card__inline { + font-size: 11px; + color: var(--text); + margin-top: 6px; + padding: 6px 8px; + background: rgba(0, 0, 0, 0.06); + border-radius: 4px; + white-space: pre-wrap; + word-break: break-word; +} + +/* Reading Indicator */ +.chat-reading-indicator { + background: transparent; + border: 1px solid var(--border); + padding: 12px; + display: inline-flex; +} + +.chat-reading-indicator__dots { + display: flex; + gap: 6px; + align-items: center; +} + +.chat-reading-indicator__dots span { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--muted); + animation: reading-pulse 1.4s ease-in-out infinite; +} + +.chat-reading-indicator__dots span:nth-child(1) { + animation-delay: 0s; +} + +.chat-reading-indicator__dots span:nth-child(2) { + animation-delay: 0.2s; +} + +.chat-reading-indicator__dots span:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes reading-pulse { + 0%, 60%, 100% { + opacity: 0.3; + transform: scale(0.8); + } + 30% { + opacity: 1; + transform: scale(1); + } +} + diff --git a/ui/src/ui/chat/grouped-render.ts b/ui/src/ui/chat/grouped-render.ts new file mode 100644 index 000000000..146d5d8e6 --- /dev/null +++ b/ui/src/ui/chat/grouped-render.ts @@ -0,0 +1,166 @@ +import { html, nothing } from "lit"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; + +import { toSanitizedMarkdownHtml } from "../markdown"; +import type { MessageGroup } from "../types/chat-types"; +import { isToolResultMessage, normalizeRoleForGrouping } from "./message-normalizer"; +import { + extractText, + extractThinking, + formatReasoningMarkdown, +} from "./message-extract"; +import { extractToolCards, renderToolCardSidebar } from "./tool-cards"; + +export function renderReadingIndicatorGroup() { + return html` +