cc-haha/desktop/src/components/chat/ThinkingBlock.tsx
你的姓名 18b7e8e2c8 feat(desktop): polish chat UI visuals and Solo Council panel
- StreamingIndicator: replace Unicode ✦ with Material Symbols auto_awesome
- ThinkingBlock: extract inline <style> to globals.css, eliminate
  duplicate style nodes per instance
- DiffViewer: fix bottom border-radius to match outer container
- ToolCallBlock: add fade-in animation on expand
- CodeViewer: add opacity transition for Shiki highlighter load
- SoloCouncilPanel: add role-specific left border colors (planner:
  primary, reviewer: success, critic: warning)
- SoloCouncilPanel: add animate-pulse to running flow step pills
- SoloCouncilPanel: parse synthesis text headings and lists for
  structured rendering, increase collapsed preview to 8 lines
- SoloCouncilPanel: responsive grid (grid-cols-1 sm:grid-cols-3)
- SoloCouncilPanel: stronger shadow (shadow-md) for visual separation
- SoloCouncilPanel: add transition-colors on card state changes
- globals.css: add dark-mode scrollbar contrast override

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 23:29:00 +08:00

53 lines
2.1 KiB
TypeScript

import { useState, useEffect, useMemo, useRef } from 'react'
import { useTranslation } from '../../i18n'
import { MarkdownRenderer } from '../markdown/MarkdownRenderer'
export function ThinkingBlock({ content, isActive = false }: { content: string; isActive?: boolean }) {
const t = useTranslation()
const [expanded, setExpanded] = useState(true)
const contentRef = useRef<HTMLDivElement>(null)
const displayContent = useMemo(() => content.replace(/\r\n?/g, '\n').trimEnd(), [content])
const hasDisplayContent = displayContent.trim().length > 0
useEffect(() => {
if (expanded && isActive && contentRef.current) {
contentRef.current.scrollTop = contentRef.current.scrollHeight
}
}, [displayContent, expanded, isActive])
return (
<div className="mb-1">
<button
type="button"
onClick={() => setExpanded((v) => !v)}
aria-expanded={expanded}
className="flex w-full items-center gap-1.5 rounded-md px-1 py-0.5 text-left text-[12px] text-[var(--color-text-tertiary)] transition-colors hover:text-[var(--color-text-secondary)]"
>
<span className="text-[10px] text-[var(--color-outline)]">
{expanded ? '\u25BE' : '\u25B8'}
</span>
<span className="shrink-0 font-medium italic">
{isActive ? t('thinking.label') : t('thinking.labelDone')}
{isActive && <span className="thinking-dots" />}
</span>
</button>
{expanded && hasDisplayContent && (
<div
ref={contentRef}
data-thinking-content="expanded"
className="relative mt-1 max-h-[300px] overflow-y-auto rounded-lg border border-[var(--color-border)]/40 bg-[var(--color-surface-container-lowest)] p-2.5 text-[11px] text-[var(--color-text-secondary)]"
>
<MarkdownRenderer
content={displayContent}
variant="compact"
cache={!isActive}
streaming={isActive}
className="thinking-markdown text-[var(--color-text-secondary)]"
/>
{isActive && <span className="thinking-cursor" />}
</div>
)}
</div>
)
}