import React from 'react';
import {
Avatar,
Button,
Empty,
Input,
Space,
Spin,
Timeline,
Typography,
} from 'antd';
import {
CheckOutlined,
CloseOutlined,
EditOutlined,
UserOutlined,
} from '@ant-design/icons';
import tools from '../utils/tools';
import './TranscriptTimeline.css';
const { Text } = Typography;
const TranscriptTimeline = ({
transcript = [],
loading = false,
visibleCount,
currentHighlightIndex = -1,
onJumpToTime,
onScroll,
transcriptRefs,
getSpeakerColor,
emptyDescription = '暂无对话数据',
loadingTip = '正在加载转录内容...',
showRenderHint = false,
fillHeight = false,
maxHeight = null,
editable = false,
isMeetingOwner = false,
editing = {},
}) => {
const {
inlineSpeakerEdit = null,
inlineSpeakerEditSegmentId = null,
inlineSpeakerValue = '',
setInlineSpeakerValue,
startInlineSpeakerEdit,
saveInlineSpeakerEdit,
cancelInlineSpeakerEdit,
inlineSegmentEditId = null,
inlineSegmentValue = '',
setInlineSegmentValue,
startInlineSegmentEdit,
saveInlineSegmentEdit,
cancelInlineSegmentEdit,
savingInlineEdit = false,
} = editing;
const renderCount = Math.min(visibleCount ?? transcript.length, transcript.length);
if (loading) {
return (
);
}
if (!transcript.length) {
return (
);
}
return (
{
const isActive = currentHighlightIndex === index;
const speakerColor = getSpeakerColor(item.speaker_id);
const speakerEditKey = `speaker-${item.speaker_id}-${item.segment_id}`;
return {
label: (
{tools.formatDuration(item.start_time_ms / 1000)}
),
dot: (
),
children: (
{
if (transcriptRefs?.current) {
transcriptRefs.current[index] = el;
}
}}
className={`transcript-entry${isActive ? ' is-active' : ''}`}
onClick={() => onJumpToTime?.(item.start_time_ms)}
>
}
className="transcript-entry-avatar"
style={{ backgroundColor: speakerColor }}
/>
{editable && inlineSpeakerEdit === item.speaker_id && inlineSpeakerEditSegmentId === speakerEditKey ? (
event.stopPropagation()}>
setInlineSpeakerValue?.(event.target.value)}
onPressEnter={saveInlineSpeakerEdit}
style={{ width: 180 }}
/>
}
loading={savingInlineEdit}
onClick={saveInlineSpeakerEdit}
/>
}
disabled={savingInlineEdit}
onClick={cancelInlineSpeakerEdit}
/>
) : (
{
event.stopPropagation();
startInlineSpeakerEdit?.(item.speaker_id, item.speaker_tag, item.segment_id);
} : undefined}
>
{item.speaker_tag || `发言人 ${item.speaker_id}`}
{editable && isMeetingOwner ? : null}
)}
{editable && inlineSegmentEditId === item.segment_id ? (
event.stopPropagation()}>
setInlineSegmentValue?.(event.target.value)}
onPressEnter={(event) => {
if (event.ctrlKey || event.metaKey) {
saveInlineSegmentEdit?.();
}
}}
/>
} loading={savingInlineEdit} onClick={saveInlineSegmentEdit}>
保存
} disabled={savingInlineEdit} onClick={cancelInlineSegmentEdit}>
取消
) : (
{
event.stopPropagation();
startInlineSegmentEdit?.(item);
} : undefined}
>
{item.text_content}
)}
),
};
})}
/>
{showRenderHint && renderCount < transcript.length ? (
已渲染 {renderCount} / {transcript.length} 条转录,继续向下滚动将自动加载更多
) : null}
);
};
export default TranscriptTimeline;