|
|
@@ -45,12 +45,14 @@ export const ChatBotPage = () => {
|
|
|
updateProgressByDuration,
|
|
|
setProgress,
|
|
|
reset,
|
|
|
+ isConnected,
|
|
|
} = useAppStore();
|
|
|
|
|
|
const recorderRef = useRef<AudioRecorder | null>(null);
|
|
|
const playerRef = useRef<AudioPlayer | null>(null);
|
|
|
const wsRef = useRef<WebSocketManager | null>(null);
|
|
|
const chunkIndexRef = useRef(0);
|
|
|
+ const connectionLostRef = useRef(false);
|
|
|
const mobileAudioPreparedRef = useRef(false);
|
|
|
const sendTimeRef = useRef<number>(0);
|
|
|
// 初始化时检查 localStorage,确保第一次打开时立即显示提示
|
|
|
@@ -327,14 +329,42 @@ export const ChatBotPage = () => {
|
|
|
const ws = new WebSocketManager();
|
|
|
const wsUrl = user_id ? `${WS_URL}?user_id=${encodeURIComponent(user_id)}` : WS_URL;
|
|
|
ws.connect(wsUrl, (connected) => {
|
|
|
+ const appState = useAppStore.getState();
|
|
|
+ const wasConnected = appState.isConnected;
|
|
|
+
|
|
|
setConnected(connected);
|
|
|
+
|
|
|
if (connected) {
|
|
|
// 记录WebSocket连接建立时间
|
|
|
performanceLogger.recordWebSocketConnected();
|
|
|
- } else {
|
|
|
- setState('error');
|
|
|
- setError('WebSocket连接断开');
|
|
|
+
|
|
|
+ if (connectionLostRef.current) {
|
|
|
+ antMessage.success('语音服务已恢复,可以继续对话');
|
|
|
+ connectionLostRef.current = false;
|
|
|
+ recorderRef.current?.stop();
|
|
|
+ playerRef.current?.stop();
|
|
|
+ reset();
|
|
|
+ setError(null);
|
|
|
+ }
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ if (!wasConnected) {
|
|
|
+ // 初次连接失败,静默等待自动重连
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ connectionLostRef.current = true;
|
|
|
+
|
|
|
+ if (appState.currentRequestId) {
|
|
|
+ performanceLogger.clearRequest(appState.currentRequestId, 'websocket_disconnected');
|
|
|
+ }
|
|
|
+
|
|
|
+ recorderRef.current?.stop();
|
|
|
+ playerRef.current?.stop();
|
|
|
+
|
|
|
+ antMessage.error('语音服务连接断开,请稍后重试');
|
|
|
+ setError('语音服务连接断开,请稍后重试');
|
|
|
});
|
|
|
ws.onMessage('app', handleMessage);
|
|
|
wsRef.current = ws;
|
|
|
@@ -435,6 +465,11 @@ export const ChatBotPage = () => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (!isConnected) {
|
|
|
+ antMessage.warning('语音服务正在重连,请稍后再试');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// 如果正在播放,先停止播放
|
|
|
if (currentState === 'playing') {
|
|
|
playerRef.current?.stop();
|
|
|
@@ -502,6 +537,14 @@ export const ChatBotPage = () => {
|
|
|
if (currentState !== 'recording' || !currentRequestId) {
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ if (!isConnected) {
|
|
|
+ antMessage.error('语音服务未连接,请重新录音发送');
|
|
|
+ recorderRef.current?.stop();
|
|
|
+ performanceLogger.clearRequest(currentRequestId, 'websocket_disconnected_before_send');
|
|
|
+ reset();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
timingRef.current.recordEnd = Date.now();
|
|
|
sendTimeRef.current = Date.now();
|
|
|
@@ -686,7 +729,7 @@ export const ChatBotPage = () => {
|
|
|
}}
|
|
|
>
|
|
|
<AudioWave
|
|
|
- isAnimating={currentState === 'recording' || currentState === 'playing'}
|
|
|
+ isAnimating={currentState === 'recording'}
|
|
|
width={typeof window !== 'undefined' ? window.innerWidth : 375}
|
|
|
height={layout.waveformHeight}
|
|
|
/>
|
|
|
@@ -758,7 +801,7 @@ export const ChatBotPage = () => {
|
|
|
|
|
|
{/* 录音按钮 - 设计稿:尺寸 96×96,在进度环内居中,左右居中 */}
|
|
|
<div
|
|
|
- className="chatbot-record-button absolute cursor-pointer touch-manipulation"
|
|
|
+ className={`chatbot-record-button absolute touch-manipulation ${isConnected ? 'cursor-pointer' : 'cursor-not-allowed'}`}
|
|
|
onClick={handleStartRecord}
|
|
|
style={{
|
|
|
width: `${layout.recordButtonSize}px`,
|
|
|
@@ -769,7 +812,7 @@ export const ChatBotPage = () => {
|
|
|
left: '50%',
|
|
|
transform: 'translateX(-50%)',
|
|
|
borderRadius: '100px',
|
|
|
- opacity: 1,
|
|
|
+ opacity: isConnected ? 1 : 0.55,
|
|
|
display: 'flex',
|
|
|
alignItems: 'center',
|
|
|
justifyContent: 'center',
|
|
|
@@ -777,9 +820,11 @@ export const ChatBotPage = () => {
|
|
|
WebkitUserSelect: 'none',
|
|
|
caretColor: 'transparent',
|
|
|
outline: 'none',
|
|
|
+ filter: isConnected ? 'none' : 'grayscale(0.25)',
|
|
|
}}
|
|
|
contentEditable={false}
|
|
|
tabIndex={-1}
|
|
|
+ aria-disabled={!isConnected}
|
|
|
>
|
|
|
{/* 录音按钮背景 - PNG 图片 */}
|
|
|
<img
|
|
|
@@ -812,8 +857,24 @@ export const ChatBotPage = () => {
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
+ {!isConnected && currentState !== 'recording' && (
|
|
|
+ <div
|
|
|
+ className="absolute text-white/80 text-center"
|
|
|
+ style={{
|
|
|
+ top: `${layout.progressRingTop - 36}px`,
|
|
|
+ left: '50%',
|
|
|
+ transform: 'translateX(-50%)',
|
|
|
+ zIndex: 25,
|
|
|
+ fontSize: `${Math.max(12, layout.recordButtonSize * 0.18)}px`,
|
|
|
+ textShadow: '0 2px 6px rgba(0,0,0,0.35)',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 正在重连语音服务...
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
{/* 进度条提示 - 首次有进度时显示 */}
|
|
|
- {showProgressBarTooltip && (
|
|
|
+ {showProgressBarTooltip && isConnected && (
|
|
|
<img
|
|
|
src={tooltipsProgressBar}
|
|
|
alt="Progress Bar Tooltip"
|
|
|
@@ -829,7 +890,7 @@ export const ChatBotPage = () => {
|
|
|
)}
|
|
|
|
|
|
{/* Tap to Chat 提示 - 只显示一次 - 等比例缩放 */}
|
|
|
- {showTapToChat && (
|
|
|
+ {showTapToChat && isConnected && (
|
|
|
<img
|
|
|
src={tapToChat}
|
|
|
alt="Tap to Chat"
|