Add explain analysis workflow and UI

This commit is contained in:
2026-03-16 22:28:41 +08:00
parent 3a5558b576
commit 1f5ee3698e
49 changed files with 8888 additions and 1476 deletions

View File

@@ -0,0 +1,157 @@
import React from 'react';
import { formatDateTime } from '../../utils/formatters';
export default function ExplainEventsSection({
explainTimeline,
isOpen,
onToggle,
availableEventDates,
selectedEventDate,
onSelectEventDate,
eventCategoryCounts,
activeEventCategory,
onSelectEventCategory,
eventCategoryMeta,
visibleExplainEvents,
}) {
return (
<div className="section">
<div className="section-header">
<h2 className="section-title">关键事件时间线</h2>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<div style={{ fontSize: 11, color: '#666666' }}>
图上点击事件点可切换对应日期
</div>
<button
onClick={onToggle}
style={{
border: '1px solid #111111',
background: isOpen ? '#111111' : '#ffffff',
color: isOpen ? '#ffffff' : '#111111',
padding: '7px 10px',
fontFamily: 'inherit',
fontSize: 11,
fontWeight: 700,
cursor: 'pointer'
}}
>
{isOpen ? '收起关键事件' : `展开关键事件 ${explainTimeline.length}`}
</button>
</div>
</div>
{explainTimeline.length === 0 ? (
<div className="empty-state">当前还没有可以串起来看的关键事件</div>
) : !isOpen ? (
<div className="empty-state">关键事件默认收起需要时再展开查看和筛选</div>
) : (
<div style={{ display: 'grid', gap: 14 }}>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{availableEventDates.map((dateKey) => {
const isActive = dateKey === selectedEventDate;
return (
<button
key={dateKey}
onClick={() => onSelectEventDate(dateKey)}
style={{
border: '1px solid #111111',
background: isActive ? '#111111' : '#ffffff',
color: isActive ? '#ffffff' : '#111111',
padding: '7px 10px',
fontFamily: 'inherit',
fontSize: 11,
fontWeight: 700,
cursor: 'pointer'
}}
>
{dateKey}
</button>
);
})}
</div>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{Object.entries(eventCategoryMeta)
.filter(([key]) => (eventCategoryCounts[key] || 0) > 0 || key === 'all')
.map(([key, meta]) => {
const isActive = key === activeEventCategory;
return (
<button
key={key}
onClick={() => onSelectEventCategory(key)}
style={{
border: `1px solid ${meta.color}`,
background: isActive ? meta.color : '#ffffff',
color: isActive ? '#ffffff' : meta.color,
padding: '8px 10px',
fontFamily: 'inherit',
fontSize: 11,
fontWeight: 700,
cursor: 'pointer'
}}
>
{meta.label} {eventCategoryCounts[key] || 0}
</button>
);
})}
</div>
{visibleExplainEvents.length === 0 ? (
<div className="empty-state">当前日期下没有符合筛选条件的事件</div>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 16 }}>
{visibleExplainEvents.map((event) => {
const accent = event.tone === 'positive' ? '#00C853' : event.tone === 'negative' ? '#FF1744' : '#000000';
const categoryMeta = eventCategoryMeta[event.category] || eventCategoryMeta.other;
return (
<div
key={event.id}
style={{
border: '1px solid #000000',
background: '#ffffff',
padding: 14,
minHeight: 180
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, marginBottom: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<span style={{
display: 'inline-flex',
padding: '2px 6px',
border: `1px solid ${categoryMeta.color}`,
color: categoryMeta.color,
fontSize: 10,
fontWeight: 700
}}>
{categoryMeta.label}
</span>
<strong style={{ fontSize: 13 }}>{event.title}</strong>
</div>
<span style={{ fontSize: 10, color: '#666666', whiteSpace: 'nowrap' }}>
{formatDateTime(event.timestamp)}
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
<span style={{
width: 8,
height: 8,
borderRadius: '50%',
background: accent
}} />
<span style={{ fontSize: 10, color: '#666666', textTransform: 'uppercase', letterSpacing: 0.6 }}>
{event.meta}
</span>
</div>
<div style={{ fontSize: 12, lineHeight: 1.7, color: '#000000', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{event.body}
</div>
</div>
);
})}
</div>
)}
</div>
)}
</div>
);
}