From 64a73c41d639a2e6b06f35a69aedf14b322b82d5 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 30 Mar 2026 15:49:58 +0800 Subject: [PATCH] fix(web): chat history audio add status --- web/src/components/Chat/ChatContent.tsx | 24 ++++--- web/src/views/ApplicationConfig/Logs.tsx | 2 +- web/src/views/Conversation/index.tsx | 86 +++++++++++++++--------- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/web/src/components/Chat/ChatContent.tsx b/web/src/components/Chat/ChatContent.tsx index ddb25838..0276916f 100644 --- a/web/src/components/Chat/ChatContent.tsx +++ b/web/src/components/Chat/ChatContent.tsx @@ -37,11 +37,11 @@ const ChatContent: FC = ({ const prevDataLengthRef = useRef(data.length); const isScrolledToBottomRef = useRef(true); const audioRef = useRef(null) - const [playingIndex, setPlayingIndex] = useState(null) + const [playingIndex, setPlayingIndex] = useState(null) - const handlePlay = (index: number, audio_url: string, audio_status?: string) => { - if (audio_status !== 'completed' && !audio_status) return - if (playingIndex === index) { + const handlePlay = (audio_url: string, audio_status?: string) => { + if (audio_status !== 'completed' && typeof audio_status === 'string') return + if (playingIndex === audio_url) { audioRef.current?.pause() setPlayingIndex(null) return @@ -52,7 +52,7 @@ const ChatContent: FC = ({ const audio = new Audio(audio_url) audioRef.current = audio audio.play() - setPlayingIndex(index) + setPlayingIndex(audio_url) audio.onended = () => setPlayingIndex(null) } @@ -79,12 +79,16 @@ const ChatContent: FC = ({ } }; }, []); - + // Auto-scroll to bottom when data changes to show latest messages // When data array length remains unchanged, if data is updated and user manually scrolled up, don't auto-scroll to bottom // When data array length changes, auto-scroll to bottom // If already scrolled to bottom, will auto-scroll to bottom useEffect(() => { + if (playingIndex && !data.some(item => item.meta_data?.audio_url === playingIndex)) { + audioRef.current?.pause() + setPlayingIndex(null) + } setTimeout(() => { if (scrollContainerRef.current) { // Auto-scroll if data length changed OR user is currently at bottom @@ -204,16 +208,16 @@ const ChatContent: FC = ({ {item.meta_data?.audio_url && <> - {playingIndex !== index && item.meta_data?.audio_status === 'pending' + {playingIndex !== item.meta_data?.audio_url && item.meta_data?.audio_status === 'pending' ? - : playingIndex !== index + : playingIndex !== item.meta_data?.audio_url ? handlePlay(index, item.meta_data?.audio_url!, item.meta_data?.audio_status)} /> + })} onClick={() => handlePlay(item.meta_data?.audio_url!, item.meta_data?.audio_status)} /> :
handlePlay(index, item.meta_data?.audio_url!, item.meta_data?.audio_status)} + onClick={() => handlePlay(item.meta_data?.audio_url!, item.meta_data?.audio_status)} /> } diff --git a/web/src/views/ApplicationConfig/Logs.tsx b/web/src/views/ApplicationConfig/Logs.tsx index 49a5bbd6..cf56059c 100644 --- a/web/src/views/ApplicationConfig/Logs.tsx +++ b/web/src/views/ApplicationConfig/Logs.tsx @@ -34,7 +34,7 @@ const Statistics: FC = () => { className: 'rb:text-[#212332]' }, { - title: t('application.createTime'), + title: t('application.created_at'), dataIndex: 'created_at', key: 'created_at', render: (createdAt: string) => formatDateTime(createdAt, 'YYYY-MM-DD HH:mm:ss'), diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx index 80394317..d4d25070 100644 --- a/web/src/views/Conversation/index.tsx +++ b/web/src/views/Conversation/index.tsx @@ -64,6 +64,13 @@ const Conversation: FC = () => { const [config, setConfig] = useState>({}) const [audioStatusMap, setAudioStatusMap] = useState>({}) + useEffect(() => { + return () => { + audioPollingRef.current.forEach((timer) => clearInterval(timer)) + audioPollingRef.current.clear() + } + }, []) + useEffect(() => { const shareToken = localStorage.getItem(`shareToken_${token}`) setShareToken(shareToken) @@ -144,13 +151,29 @@ const Conversation: FC = () => { } useEffect(() => { - audioPollingRef.current.forEach((timer) => clearInterval(timer)) - audioPollingRef.current.clear() if (conversation_id) { getConversationDetail(token as string, conversation_id) .then(res => { const response = res as { messages: ChatItem[] } - setChatList(response?.messages || []) + const messages = response?.messages || [] + const historyAudioUrls = new Set(messages.map(m => m.meta_data?.audio_url).filter(Boolean)) + audioPollingRef.current.forEach((timer, key) => { + if (!historyAudioUrls.has(key)) { + clearInterval(timer) + audioPollingRef.current.delete(key) + } + }) + messages.forEach(msg => { + if (msg.role === 'assistant' && msg.meta_data?.audio_url && msg.meta_data?.audio_status === 'pending') { + startAudioPolling(msg.meta_data.audio_url, msg.meta_data.audio_url) + } + }) + setChatList(messages.map(msg => { + if (msg.role === 'assistant' && msg.meta_data?.audio_url && audioPollingRef.current.has(msg.meta_data.audio_url)) { + return { ...msg, meta_data: { ...msg.meta_data, audio_status: 'pending' } } + } + return msg + })) }) } else { if (features?.opening_statement?.statement) { @@ -228,6 +251,28 @@ const Conversation: FC = () => { })) }, [audioStatusMap, chatList.length]) + const startAudioPolling = (audioUrl: string, idToPoll: string) => { + if (audioPollingRef.current.has(idToPoll)) return + const fileId = audioUrl.split('/').pop() + if (!fileId) return + const timer = setInterval(() => { + getFileStatusById(fileId) + .then(res => { + const { status } = res as { status: string } + if (status && status !== 'pending') { + setAudioStatusMap(prev => ({ ...prev, [idToPoll]: status })) + clearInterval(audioPollingRef.current.get(idToPoll)) + audioPollingRef.current.delete(idToPoll) + } + }) + .catch(() => { + clearInterval(audioPollingRef.current.get(idToPoll)) + audioPollingRef.current.delete(idToPoll) + }) + }, 2000) + audioPollingRef.current.set(idToPoll, timer) + } + /** Send message and handle streaming response */ const handleSend = (msg?: string) => { if (!token || !shareToken) return @@ -287,35 +332,8 @@ const Conversation: FC = () => { const { file_id } = item.data as { file_id?: string } const idToPoll = file_id || audio_url || '' const fileId = audio_url.split('/').pop() - if (fileId && idToPoll && !audioPollingRef.current.has(idToPoll)) { - - const timer = setInterval(() => { - getFileStatusById(fileId) - .then(res => { - const { status } = res as { status: string } - if (status && status !== 'pending') { - setAudioStatusMap(prev => ({ - ...prev, - [idToPoll]: status - })) - clearInterval(audioPollingRef.current.get(idToPoll)) - audioPollingRef.current.delete(idToPoll) - getHistory(true) - if (currentConversationId && currentConversationId !== conversation_id) { - setConversationId(currentConversationId) - } - } - }) - .catch(() => { - clearInterval(audioPollingRef.current.get(idToPoll)) - audioPollingRef.current.delete(idToPoll) - getHistory(true) - if (currentConversationId && currentConversationId !== conversation_id) { - setConversationId(currentConversationId) - } - }) - }, 2000) - audioPollingRef.current.set(idToPoll, timer) + if (fileId && idToPoll) { + startAudioPolling(audio_url, idToPoll) } } else { getHistory(true) @@ -327,6 +345,10 @@ const Conversation: FC = () => { updateAssistantMessage(content, audio_url, undefined, citations) } setLoading(false) + getHistory(true) + if (currentConversationId && currentConversationId !== conversation_id) { + setConversationId(currentConversationId) + } break } })