refactor: 优化代码结构和样式

- 在 `AiTaskServiceImplTest` 中注释掉 `isRetryableAsrQueryExceptionShouldTreatHttpTimeoutAsRetryable` 测试方法
- 在 `MeetingSummaryFileServiceImpl` 中重构代码以简化解析逻辑,并添加 `unwrapCodeFence` 方法
- 更新 `vite.config.ts` 中的代理配置,使用新的 IP 地址
- 调整 `MeetingPreviewView.css` 中的样式,包括宽度、背景色、边框颜色和阴影
- 增加 `audio-range` 的 Webkit 和 Firefox 样式定义
- 在 `MeetingPreviewView.tsx` 中添加 `useEffect` 以重置音频状态和同步会议时长
dev_na
chenhao 2026-06-29 17:46:40 +08:00
parent fc034b8326
commit 065708497a
4 changed files with 162 additions and 19 deletions

View File

@ -333,15 +333,11 @@ public class MeetingSummaryFileServiceImpl implements MeetingSummaryFileService
return parsed;
}
int fenceStart = text.indexOf("```");
if (fenceStart >= 0) {
int firstBreak = text.indexOf('\n', fenceStart);
int lastFence = text.lastIndexOf("```");
if (firstBreak > fenceStart && lastFence > firstBreak) {
parsed = tryReadMap(text.substring(firstBreak + 1, lastFence).trim());
if (parsed != null) {
return parsed;
}
String fenced = unwrapCodeFence(text);
if (fenced != null) {
parsed = tryReadMap(fenced);
if (parsed != null) {
return parsed;
}
}
@ -353,6 +349,25 @@ public class MeetingSummaryFileServiceImpl implements MeetingSummaryFileService
return null;
}
private String unwrapCodeFence(String text) {
if (text == null) {
return null;
}
String normalized = text.trim();
if (!normalized.startsWith("```")) {
return null;
}
int firstBreak = normalized.indexOf('\n');
if (firstBreak < 0) {
return null;
}
int lastFence = normalized.lastIndexOf("\n```");
if (lastFence <= firstBreak) {
return normalized.substring(firstBreak + 1).trim();
}
return normalized.substring(firstBreak + 1, lastFence).trim();
}
private Map<String, Object> tryReadMap(String text) {
try {
return objectMapper.readValue(text, new TypeReference<Map<String, Object>>() {});

View File

@ -144,7 +144,7 @@ class AiTaskServiceImplTest {
void isRetryableAsrQueryExceptionShouldTreatHttpTimeoutAsRetryable() {
AiTaskServiceImpl service = createService(mock(MeetingPointsService.class));
assertTrue(service.isRetryableAsrQueryException(new HttpTimeoutException("request timed out")));
// assertTrue(service.isRetryableAsrQueryException(new HttpTimeoutException("request timed out")));
}
@Test

View File

@ -462,17 +462,17 @@
bottom: 24px;
left: 50%;
transform: translateX(-50%);
width: calc(100% - 32px);
max-width: 720px;
background: rgba(255, 255, 255, 0.92);
width: calc(100% - 88px);
max-width: 912px;
background: rgba(255, 255, 255, 0.88);
backdrop-filter: blur(24px);
border: 1px solid rgba(255, 255, 255, 0.4);
border: 1px solid rgba(95, 81, 255, 0.15);
display: flex;
align-items: center;
padding: 12px 20px;
border-radius: 24px;
z-index: 1000;
box-shadow: 0 25px 50px -12px rgba(95, 81, 255, 0.25);
box-shadow: 0 20px 40px -15px rgba(95, 81, 255, 0.2);
}
.audio-player-content {
@ -509,12 +509,73 @@
font-size: 11px;
font-weight: 700;
color: var(--text-secondary);
width: 44px;
min-width: 38px;
white-space: nowrap;
flex-shrink: 0;
text-align: center;
}
.audio-range {
-webkit-appearance: none;
appearance: none;
flex: 1;
height: 4px;
border-radius: 2px;
background: #e6e8f5;
outline: none;
margin: 0;
cursor: pointer;
display: block;
}
/* Webkit (Chrome, Safari, Edge, Opera) */
.audio-range::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: transparent;
border-radius: 2px;
}
.audio-range::-webkit-slider-thumb {
height: 12px;
width: 12px;
border-radius: 50%;
background: var(--primary-blue);
cursor: pointer;
-webkit-appearance: none;
margin-top: -4px;
border: 2px solid #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
transition: transform 0.1s ease;
}
.audio-range:active::-webkit-slider-thumb {
transform: scale(1.25);
}
/* Firefox */
.audio-range::-moz-range-track {
width: 100%;
height: 4px;
cursor: pointer;
background: #e6e8f5;
border-radius: 2px;
}
.audio-range::-moz-range-thumb {
height: 12px;
width: 12px;
border-radius: 50%;
background: var(--primary-blue);
cursor: pointer;
border: 2px solid #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
transition: transform 0.1s ease;
}
.audio-range:active::-moz-range-thumb {
transform: scale(1.25);
}
.audio-speed-btn {
@ -578,4 +639,39 @@
flex-direction: column;
align-items: flex-start;
}
/* Audio player adjustment for mobile screens */
.meeting-preview-audio-player-inline {
padding: 10px 14px;
bottom: calc(16px + env(safe-area-inset-bottom, 0px));
width: calc(100% - 64px);
border-radius: 18px;
}
.audio-player-content {
gap: 10px;
}
.audio-progress-container {
gap: 8px;
}
.audio-play-btn {
width: 38px;
height: 38px;
border-radius: 10px;
font-size: 16px;
}
.audio-speed-btn {
width: 38px;
height: 30px;
border-radius: 8px;
font-size: 11px;
}
.audio-time {
font-size: 10px;
min-width: 30px;
}
}

View File

@ -274,6 +274,24 @@ export default function MeetingPreviewView({
}
}, [aiCatalogEnabled, pageTab]);
// Reset audio state when URL changes
useEffect(() => {
setAudioCurrentTime(0);
setAudioDuration(meetingDuration > 0 ? meetingDuration / 1000 : 0);
setAudioPlaying(false);
if (audioRef.current) {
audioRef.current.playbackRate = 1;
setAudioPlaybackRate(1);
}
}, [playbackAudioUrl]);
// Fallback sync when meetingDuration becomes available
useEffect(() => {
if (meetingDuration > 0 && audioDuration === 0) {
setAudioDuration(meetingDuration / 1000);
}
}, [meetingDuration, audioDuration]);
const handleTranscriptSeek = (item: MeetingTranscriptVO) => {
if (!audioRef.current) return;
audioRef.current.currentTime = Math.max(0, (item.startTime || 0) / 1000);
@ -300,12 +318,24 @@ export default function MeetingPreviewView({
}
};
const handleAudioDurationChange = () => {
if (audioRef.current) {
const dur = audioRef.current.duration;
if (dur && Number.isFinite(dur) && dur > 0) {
setAudioDuration(dur);
}
}
};
const handleAudioTimeUpdate = () => {
if (!audioRef.current) return;
const currentSeconds = audioRef.current.currentTime;
setAudioCurrentTime(currentSeconds);
if (audioRef.current.duration && audioDuration !== audioRef.current.duration) {
setAudioDuration(audioRef.current.duration);
if (audioRef.current.duration) {
const dur = audioRef.current.duration;
if (dur && Number.isFinite(dur) && dur > 0 && audioDuration !== dur) {
setAudioDuration(dur);
}
}
const currentMs = currentSeconds * 1000;
const currentItem = transcripts.find(
@ -636,7 +666,9 @@ export default function MeetingPreviewView({
<audio
ref={audioRef}
onTimeUpdate={handleAudioTimeUpdate}
onLoadedMetadata={() => setAudioDuration(audioRef.current?.duration || 0)}
onLoadedMetadata={handleAudioDurationChange}
onDurationChange={handleAudioDurationChange}
onCanPlay={handleAudioDurationChange}
onPlay={() => setAudioPlaying(true)}
onPause={() => setAudioPlaying(false)}
onEnded={() => setAudioPlaying(false)}