@@ -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 ? (
{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;