diff --git a/frontend/src/pages/business/MeetingDetail.css b/frontend/src/pages/business/MeetingDetail.css
index 44ae5a5..b1ac2ce 100644
--- a/frontend/src/pages/business/MeetingDetail.css
+++ b/frontend/src/pages/business/MeetingDetail.css
@@ -741,6 +741,48 @@
background: #ffffff !important;
}
+.meeting-detail-page-v2 .ant-list-item.transcript-row {
+ display: block !important;
+ min-width: 0;
+}
+
+.meeting-detail-page-v2 .transcript-entry {
+ width: 100%;
+ min-width: 0;
+ display: grid !important;
+ grid-template-columns: 32px minmax(0, 1fr);
+ gap: 12px;
+ align-items: flex-start;
+}
+
+.meeting-detail-page-v2 .transcript-content-wrap,
+.meeting-detail-page-v2 .transcript-meta,
+.meeting-detail-page-v2 .transcript-bubble,
+.meeting-detail-page-v2 .transcript-bubble-editing {
+ min-width: 0;
+ max-width: 100%;
+}
+
+.meeting-detail-page-v2 .transcript-avatar {
+ align-self: start;
+ margin-top: 0 !important;
+}
+
+.meeting-detail-page-v2 .transcript-meta {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ min-height: 32px;
+ gap: 6px 8px;
+}
+
+.meeting-detail-page-v2 .transcript-speaker {
+ max-width: min(100%, 180px);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.meeting-detail-page-v2 .transcript-player.transcript-player--embedded {
display: flex;
align-items: center;
@@ -845,4 +887,13 @@
.meeting-detail-page-v2 .transcript-panel-header .transcript-stage-tabs button {
flex: 1;
}
+
+ .meeting-detail-page-v2 .transcript-entry {
+ grid-template-columns: 28px minmax(0, 1fr);
+ gap: 10px;
+ }
+
+ .meeting-detail-page-v2 .transcript-meta {
+ min-height: 28px;
+ }
}
diff --git a/frontend/src/pages/business/MeetingDetail.tsx b/frontend/src/pages/business/MeetingDetail.tsx
index a5e6523..5785f2a 100644
--- a/frontend/src/pages/business/MeetingDetail.tsx
+++ b/frontend/src/pages/business/MeetingDetail.tsx
@@ -36,7 +36,6 @@ import {
MeetingTranscriptVO,
MeetingVO,
reSummary,
- resolveAudioMimeType,
resolveMeetingPlaybackAudioUrl,
retryMeetingChapter,
retryMeetingSummary,
@@ -1738,11 +1737,27 @@ const MeetingDetail: React.FC = () => {
setSummaryVisible(true);
};
+ const handleAudioPlaybackError = useCallback(() => {
+ const currentAudioUrl = playbackAudioUrl || '';
+ if (!currentAudioUrl || audioPlaybackErrorShownRef.current === currentAudioUrl) {
+ return;
+ }
+ const normalizedUrl = currentAudioUrl.split('#')[0]?.split('?')[0]?.toLowerCase() || '';
+ const isM4a = normalizedUrl.endsWith('.m4a');
+ message.warning(
+ isM4a
+ ? '当前 m4a 文件在本机浏览器中无法直接播放。已确认文件与服务端响应基本正常,更可能是浏览器对该录音参数或容器实现的兼容性问题。建议优先使用 mp3、wav,或下载到本地播放。'
+ : '当前音频文件无法播放,请检查文件是否损坏或格式是否兼容。',
+ );
+ audioPlaybackErrorShownRef.current = currentAudioUrl;
+ setAudioPlaying(false);
+ }, [message, playbackAudioUrl]);
+
const seekTo = useCallback((timeMs: number) => {
if (!audioRef.current) return;
audioRef.current.currentTime = timeMs / 1000;
- audioRef.current.play();
- }, []);
+ audioRef.current.play().catch(() => handleAudioPlaybackError());
+ }, [handleAudioPlaybackError]);
const handleTranscriptRowPlay = useCallback((timeMs: number) => {
setLinkedTranscriptIds([]);
@@ -1948,22 +1963,6 @@ const MeetingDetail: React.FC = () => {
transcriptItemRefs.current[transcriptId] = node;
}, []);
- const handleAudioPlaybackError = useCallback(() => {
- const currentAudioUrl = playbackAudioUrl || '';
- if (!currentAudioUrl || audioPlaybackErrorShownRef.current === currentAudioUrl) {
- return;
- }
- const normalizedUrl = currentAudioUrl.split('#')[0]?.split('?')[0]?.toLowerCase() || '';
- const isM4a = normalizedUrl.endsWith('.m4a');
- message.warning(
- isM4a
- ? '当前 m4a 文件在本机浏览器中无法直接播放。已确认文件与服务端响应基本正常,更可能是浏览器对该录音参数或容器实现的兼容性问题。建议优先使用 mp3、wav,或下载到本地播放。'
- : '当前音频文件无法播放,请检查文件是否损坏或格式是否兼容。',
- );
- audioPlaybackErrorShownRef.current = currentAudioUrl;
- setAudioPlaying(false);
- }, [message, playbackAudioUrl]);
-
useEffect(() => {
const audio = audioRef.current;
if (!audio) return undefined;
@@ -2004,10 +2003,23 @@ const MeetingDetail: React.FC = () => {
};
}, [audioPlaybackRate, meeting?.status, playbackAudioUrl, handleAudioPlaybackError]);
+ useEffect(() => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ audio.pause();
+ audio.load();
+ audio.playbackRate = 1;
+ setAudioPlaybackRate(1);
+ setAudioCurrentTime(0);
+ setAudioDuration(0);
+ setAudioPlaying(false);
+ }, [playbackAudioUrl]);
+
const toggleAudioPlayback = () => {
if (!audioRef.current) return;
if (audioRef.current.paused) {
- audioRef.current.play();
+ audioRef.current.play().catch(() => handleAudioPlaybackError());
} else {
audioRef.current.pause();
}
@@ -2658,11 +2670,6 @@ const MeetingDetail: React.FC = () => {
原文}>
- {playbackAudioUrl && (
-
- )}
{emptyTranscriptFailureNotice && (
当前没有可展示的转录内容
@@ -2715,9 +2722,7 @@ const MeetingDetail: React.FC = () => {
styles={{ body: { padding: 0, height: '100%', display: 'flex', flexDirection: 'column', minHeight: 0 } }}
>
{playbackAudioUrl && (
-
+
)}
@@ -3854,10 +3859,8 @@ const MeetingDetail: React.FC = () => {
white-space: nowrap;
}
.ant-list-item.transcript-row {
- display: flex !important;
- justify-content: flex-start !important;
- align-items: flex-start !important;
- gap: 12px;
+ display: block !important;
+ min-width: 0;
padding: 12px 0 !important;
border-bottom: 0 !important;
cursor: pointer;
@@ -3877,14 +3880,13 @@ const MeetingDetail: React.FC = () => {
box-shadow: 0 12px 28px rgba(60, 112, 245, 0.12);
}
.transcript-entry {
- flex: 1;
- justify-self: start;
- text-align: left;
- display: flex;
- align-items: flex-start;
- gap: 16px;
width: 100%;
min-width: 0;
+ display: grid !important;
+ grid-template-columns: 32px minmax(0, 1fr);
+ gap: 12px;
+ align-items: flex-start;
+ text-align: left;
}
.transcript-content-wrap {
flex: 1;
@@ -3892,22 +3894,31 @@ const MeetingDetail: React.FC = () => {
flex-direction: column;
gap: 8px;
min-width: 0;
+ max-width: 100%;
}
.transcript-meta {
display: flex;
align-items: center;
- gap: 8px;
+ gap: 6px 8px;
flex-wrap: wrap;
+ min-height: 32px;
+ min-width: 0;
+ max-width: 100%;
color: #8e98b8;
font-size: 12px;
}
.transcript-avatar {
flex-shrink: 0;
+ align-self: start;
background: linear-gradient(135deg, #d9ebff, #eaf2ff) !important;
color: #4362ff !important;
- margin-top: 4px;
+ margin-top: 0 !important;
}
.transcript-speaker {
+ max-width: min(100%, 180px);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
color: var(--app-text-main);
font-weight: 700;
font-size: 14px;
diff --git a/frontend/src/pages/business/MeetingPreview.tsx b/frontend/src/pages/business/MeetingPreview.tsx
index be5119c..aa3149f 100644
--- a/frontend/src/pages/business/MeetingPreview.tsx
+++ b/frontend/src/pages/business/MeetingPreview.tsx
@@ -22,7 +22,6 @@ import ReactMarkdown from "react-markdown";
import {
getMeetingPreviewAccess,
getPublicMeetingPreview,
- resolveAudioMimeType,
resolveMeetingPlaybackAudioUrl,
type MeetingChapterVO,
type MeetingTranscriptVO,
@@ -524,6 +523,20 @@ export default function MeetingPreview() {
}
};
+ useEffect(() => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ audio.pause();
+ audio.load();
+ audio.playbackRate = 1;
+ setAudioPlaybackRate(1);
+ setAudioCurrentTime(0);
+ setAudioDuration(0);
+ setAudioPlaying(false);
+ setActiveTranscriptId(null);
+ }, [playbackAudioUrl]);
+
const handleAudioError = () => {
const currentAudioUrl = playbackAudioUrl || "";
if (!currentAudioUrl || audioPlaybackErrorShownRef.current === currentAudioUrl) {
@@ -997,6 +1010,7 @@ export default function MeetingPreview() {
>
+ />