diff --git a/desktop/src/components/chat/CodeViewer.tsx b/desktop/src/components/chat/CodeViewer.tsx index 8ede06e3..79524ec4 100644 --- a/desktop/src/components/chat/CodeViewer.tsx +++ b/desktop/src/components/chat/CodeViewer.tsx @@ -223,9 +223,10 @@ function CodeArea({ code, language, showLineNumbers }: { code: string; language?
-
+
@@ -346,9 +346,9 @@ export function SoloCouncilPanel({
{!collapsed ? ( -
+
-
+
{rows.map((row) => ( ))} @@ -408,7 +408,7 @@ function SoloCouncilCard({ row }: { row: SoloCouncilRow }) { return (
@@ -483,15 +483,54 @@ function SoloCouncilStructuredReview({ row, artifact }: { row: SoloCouncilRow; a ) } +function renderSynthesisText(text: string) { + const lines = text.split('\n') + const elements: React.ReactNode[] = [] + let listItems: string[] = [] + let listKey = 0 + + const flushList = () => { + if (listItems.length > 0) { + elements.push( +
    + {listItems.map((item, i) =>
  • {item}
  • )} +
, + ) + listItems = [] + } + } + + for (const line of lines) { + const trimmed = line.trim() + if (trimmed.startsWith('## ')) { + flushList() + elements.push(

{trimmed.slice(3)}

) + } else if (trimmed.startsWith('# ')) { + flushList() + elements.push(

{trimmed.slice(2)}

) + } else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) { + listItems.push(trimmed.slice(2)) + } else if (trimmed === '') { + flushList() + } else { + flushList() + elements.push(

{trimmed}

) + } + } + flushList() + return elements +} + function SoloCouncilSynthesis({ text }: { text: string }) { const t = useTranslation() const [expanded, setExpanded] = useState(false) const canExpand = text.length > SOLO_COUNCIL_SYNTHESIS_COLLAPSE_THRESHOLD + const renderedContent = useMemo(() => renderSynthesisText(text), [text]) const outputClassName = canExpand ? expanded - ? 'mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-[var(--radius-sm)] border border-[var(--color-border)]/60 bg-[var(--color-surface-container-lowest)]/50 p-2 text-[11px] leading-relaxed text-[var(--color-text-secondary)]' - : 'mt-2 line-clamp-5 whitespace-pre-wrap break-words text-[11px] leading-relaxed text-[var(--color-text-secondary)]' - : 'mt-2 whitespace-pre-wrap break-words text-[11px] leading-relaxed text-[var(--color-text-secondary)]' + ? 'mt-2 max-h-80 overflow-auto rounded-[var(--radius-sm)] border border-[var(--color-border)]/60 bg-[var(--color-surface-container-lowest)]/50 p-2 text-[11px] leading-relaxed text-[var(--color-text-secondary)]' + : 'mt-2 line-clamp-[8] text-[11px] leading-relaxed text-[var(--color-text-secondary)]' + : 'mt-2 text-[11px] leading-relaxed text-[var(--color-text-secondary)]' return (
@@ -504,7 +543,7 @@ function SoloCouncilSynthesis({ text }: { text: string }) { {t('soloCouncil.synthesis.approvalHint')}
- {text} + {renderedContent}
{canExpand ? (
) } - -const thinkingStyles = ` -@keyframes thinking-cursor-blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0; } -} -@keyframes thinking-dots { - 0%, 20% { content: ''; } - 40% { content: '.'; } - 60% { content: '..'; } - 80%, 100% { content: '...'; } -} -.thinking-cursor { - display: inline-block; - width: 2px; - height: 1em; - background: var(--color-text-tertiary); - vertical-align: middle; - margin-left: 1px; - animation: thinking-cursor-blink 1s step-end infinite; -} -.thinking-dots::after { - content: ''; - animation: thinking-dots 1.4s steps(1, end) infinite; -} -.thinking-markdown > :first-child, -.thinking-markdown > :first-child > :first-child { - margin-top: 0; -} -.thinking-markdown > :last-child, -.thinking-markdown > :last-child > :last-child { - margin-bottom: 0; -} -` diff --git a/desktop/src/components/chat/ToolCallBlock.tsx b/desktop/src/components/chat/ToolCallBlock.tsx index 9f17da6d..e9f24a26 100644 --- a/desktop/src/components/chat/ToolCallBlock.tsx +++ b/desktop/src/components/chat/ToolCallBlock.tsx @@ -115,7 +115,7 @@ export const ToolCallBlock = memo(function ToolCallBlock({ toolName, input, resu {expandable && expanded && ( -
+
{preview} {details}
diff --git a/desktop/src/theme/globals.css b/desktop/src/theme/globals.css index 853f0f69..0b895c9a 100644 --- a/desktop/src/theme/globals.css +++ b/desktop/src/theme/globals.css @@ -1247,11 +1247,55 @@ button, input, textarea, select, a, [role="button"] { color: inherit; } +/* Fade-in animation for expand/appear transitions */ +@keyframes fade-in { + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } +} +.fade-in { + animation: fade-in 200ms ease-out; +} + +/* Thinking block animations */ +@keyframes thinking-cursor-blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} +@keyframes thinking-dots { + 0%, 20% { content: ''; } + 40% { content: '.'; } + 60% { content: '..'; } + 80%, 100% { content: '...'; } +} +.thinking-cursor { + display: inline-block; + width: 2px; + height: 1em; + background: var(--color-text-tertiary); + vertical-align: middle; + margin-left: 1px; + animation: thinking-cursor-blink 1s step-end infinite; +} +.thinking-dots::after { + content: ''; + animation: thinking-dots 1.4s steps(1, end) infinite; +} +.thinking-markdown > :first-child, +.thinking-markdown > :first-child > :first-child { + margin-top: 0; +} +.thinking-markdown > :last-child, +.thinking-markdown > :last-child > :last-child { + margin-bottom: 0; +} + /* Scrollbar */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--color-outline); border-radius: 9999px; opacity: 0.3; } ::-webkit-scrollbar-thumb:hover { opacity: 0.6; } +.dark ::-webkit-scrollbar-thumb { background: var(--color-on-surface); opacity: 0.15; } +.dark ::-webkit-scrollbar-thumb:hover { opacity: 0.35; } .sidebar-scroll-area { padding-bottom: 72px;