zlwu 1 سال پیش
والد
کامیت
b98973b4a4
25فایلهای تغییر یافته به همراه436 افزوده شده و 1048 حذف شده
  1. 160 0
      .gitignore
  2. 0 0
      frontend/node_modules/.cache/.eslintcache
  3. BIN
      frontend/node_modules/.cache/default-development/0.pack
  4. BIN
      frontend/node_modules/.cache/default-development/1.pack
  5. BIN
      frontend/node_modules/.cache/default-development/12.pack
  6. BIN
      frontend/node_modules/.cache/default-development/13.pack
  7. BIN
      frontend/node_modules/.cache/default-development/14.pack
  8. BIN
      frontend/node_modules/.cache/default-development/15.pack
  9. BIN
      frontend/node_modules/.cache/default-development/16.pack
  10. BIN
      frontend/node_modules/.cache/default-development/17.pack
  11. BIN
      frontend/node_modules/.cache/default-development/2.pack
  12. BIN
      frontend/node_modules/.cache/default-development/4.pack
  13. BIN
      frontend/node_modules/.cache/default-development/5.pack
  14. BIN
      frontend/node_modules/.cache/default-development/6.pack
  15. BIN
      frontend/node_modules/.cache/default-development/7.pack
  16. BIN
      frontend/node_modules/.cache/default-development/8.pack
  17. BIN
      frontend/node_modules/.cache/default-development/index.pack
  18. BIN
      frontend/node_modules/.cache/default-development/index.pack.old
  19. 0 0
      frontend/node_modules/.cache/tsconfig.tsbuildinfo
  20. 215 79
      frontend/src/components/RecordingPlayback.tsx
  21. 35 8
      frontend/src/store/slices/audioSlice.ts
  22. 6 1
      logs/backend.log
  23. 1 1
      logs/backend.pid
  24. 18 958
      logs/frontend.log
  25. 1 1
      logs/frontend.pid

+ 160 - 0
.gitignore

@@ -0,0 +1,160 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Virtual environments
+venv/
+env/
+ENV/
+env.bak/
+venv.bak/
+
+# Node.js
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Dependency directories
+jspm_packages/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# React build output
+build/
+dist/
+
+# Logs
+logs/
+*.log
+*.pid
+
+# IDE and Editors
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# System Files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Docker
+.dockerignore
+
+# History and backup files
+.history/
+*.bak
+*.backup
+*.tmp
+
+# Audio and media files (temporary)
+*.wav
+*.mp3
+*.pcm
+input.pcm
+output.pcm
+
+# Playwright
+.playwright-mcp/
+
+# Temporary files
+*.temp
+*.tmp
+
+# OS generated files
+Desktop.ini
+$RECYCLE.BIN/
+
+# Archives
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+*.tar
+*.zip
+
+# Certificate files
+*.pem
+*.key
+*.crt
+
+# Configuration files with sensitive data
+config.local.*
+*-local.json 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
frontend/node_modules/.cache/.eslintcache


BIN
frontend/node_modules/.cache/default-development/0.pack


BIN
frontend/node_modules/.cache/default-development/1.pack


BIN
frontend/node_modules/.cache/default-development/12.pack


BIN
frontend/node_modules/.cache/default-development/13.pack


BIN
frontend/node_modules/.cache/default-development/14.pack


BIN
frontend/node_modules/.cache/default-development/15.pack


BIN
frontend/node_modules/.cache/default-development/16.pack


BIN
frontend/node_modules/.cache/default-development/17.pack


BIN
frontend/node_modules/.cache/default-development/2.pack


BIN
frontend/node_modules/.cache/default-development/4.pack


BIN
frontend/node_modules/.cache/default-development/5.pack


BIN
frontend/node_modules/.cache/default-development/6.pack


BIN
frontend/node_modules/.cache/default-development/7.pack


BIN
frontend/node_modules/.cache/default-development/8.pack


BIN
frontend/node_modules/.cache/default-development/index.pack


BIN
frontend/node_modules/.cache/default-development/index.pack.old


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
frontend/node_modules/.cache/tsconfig.tsbuildinfo


+ 215 - 79
frontend/src/components/RecordingPlayback.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useRef, useEffect, useMemo } from 'react';
+import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
 import styled from 'styled-components';
 import { Button, Progress, Typography } from 'antd';
 import { PlayCircleOutlined, PauseCircleOutlined, DeleteOutlined } from '@ant-design/icons';
@@ -38,6 +38,17 @@ const TimeDisplay = styled.div`
   color: rgba(255, 255, 255, 0.8);
 `;
 
+const ErrorMessage = styled.div`
+  background: rgba(255, 107, 107, 0.2);
+  border: 1px solid rgba(255, 107, 107, 0.5);
+  border-radius: 8px;
+  padding: 8px 12px;
+  margin-top: 8px;
+  font-size: 12px;
+  color: #ff6b6b;
+  line-height: 1.3;
+`;
+
 const RecordingPlayback: React.FC<RecordingPlaybackProps> = ({ audioBlob, onClear }) => {
   const [isPlaying, setIsPlaying] = useState(false);
   const [currentTime, setCurrentTime] = useState(0);
@@ -46,103 +57,204 @@ const RecordingPlayback: React.FC<RecordingPlaybackProps> = ({ audioBlob, onClea
   const audioRef = useRef<HTMLAudioElement | null>(null);
   const intervalRef = useRef<number | null>(null);
   const audioUrlRef = useRef<string | null>(null);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
 
-  // 创建和管理音频URL
-  const audioUrl = useMemo(() => {
-    // 清理之前的URL
-    if (audioUrlRef.current) {
-      URL.revokeObjectURL(audioUrlRef.current);
-      audioUrlRef.current = null;
+  // 清理音频资源
+  const cleanupAudio = useCallback(() => {
+    if (audioRef.current) {
+      // 移除所有事件監聽器
+      audioRef.current.removeEventListener('loadedmetadata', () => {});
+      audioRef.current.removeEventListener('canplay', () => {});
+      audioRef.current.removeEventListener('ended', () => {});
+      audioRef.current.removeEventListener('error', () => {});
+      
+      // 暫停並重置音頻,但不要設置為空字符串
+      audioRef.current.pause();
+      audioRef.current.currentTime = 0;
+      audioRef.current = null;
     }
     
-    // 创建新的URL
-    if (audioBlob) {
-      console.log('[RecordingPlayback] 创建音频URL, blob大小:', audioBlob.size, 'bytes, 类型:', audioBlob.type);
-      const url = URL.createObjectURL(audioBlob);
-      audioUrlRef.current = url;
-      return url;
+    if (intervalRef.current) {
+      clearInterval(intervalRef.current);
+      intervalRef.current = null;
     }
-    return null;
-  }, [audioBlob]);
+    
+    setIsPlaying(false);
+    setCurrentTime(0);
+    setProgress(0);
+  }, []);
 
-  // 清理URL当组件卸载时
+  // 初始化音频元素,使用ref确保稳定性
   useEffect(() => {
-    return () => {
+    if (!audioBlob) {
+      cleanupAudio();
       if (audioUrlRef.current) {
         URL.revokeObjectURL(audioUrlRef.current);
         audioUrlRef.current = null;
       }
+      return;
+    }
+
+    // 清理之前的資源(但保留 URL 直到新的創建成功)
+    const oldUrl = audioUrlRef.current;
+    cleanupAudio();
+
+    console.log('[RecordingPlayback] 初始化播放器,blob大小:', audioBlob.size, 'bytes, 类型:', audioBlob.type);
+
+    // 創建新的 Blob URL
+    const audioUrl = URL.createObjectURL(audioBlob);
+    audioUrlRef.current = audioUrl;
+
+    // 釋放舊的 URL
+    if (oldUrl) {
+      URL.revokeObjectURL(oldUrl);
+    }
+
+    // 創建新的音頻元素
+    const audio = new Audio();
+    audioRef.current = audio;
+
+    // 設置音頻屬性
+    audio.preload = 'metadata';
+    audio.crossOrigin = 'anonymous'; // 添加這個可能有助於避免一些播放問題
+    
+    console.log('[RecordingPlayback] 音頻元素已創建,準備設置URL:', audioUrl);
+
+    // 事件處理函數
+    const handleLoadedMetadata = () => {
+      console.log('[RecordingPlayback] 音頻元數據加載完成,時長:', audio.duration);
+      if (audio.duration && audio.duration !== Infinity) {
+        setDuration(audio.duration);
+        setErrorMessage(null);
+      } else {
+        console.warn('[RecordingPlayback] 音頻時長異常:', audio.duration);
+      }
     };
-  }, []);
 
-  // 初始化音频元素
-  useEffect(() => {
-    if (audioUrl && audioBlob) {
-      // 清理之前的音频元素
-      if (audioRef.current) {
-        audioRef.current.pause();
-        audioRef.current.src = '';
-        audioRef.current = null;
+    const handleCanPlay = () => {
+      console.log('[RecordingPlayback] 音頻可以播放,readyState:', audio.readyState);
+      setErrorMessage(null);
+    };
+
+    const handleEnded = () => {
+      console.log('[RecordingPlayback] 音頻播放結束');
+      setIsPlaying(false);
+      setCurrentTime(0);
+      setProgress(0);
+      if (intervalRef.current) {
+        clearInterval(intervalRef.current);
+        intervalRef.current = null;
       }
+    };
 
-      console.log('[RecordingPlayback] 初始化音频元素,URL:', audioUrl);
-      audioRef.current = new Audio();
-      const audio = audioRef.current;
-      
-      // 设置音频源
-      audio.src = audioUrl;
-      audio.preload = 'metadata';
-      
-      audio.addEventListener('loadedmetadata', () => {
-        console.log('[RecordingPlayback] 音频元数据加载完成,时长:', audio.duration);
-        setDuration(audio.duration);
-      });
-      
-      audio.addEventListener('canplay', () => {
-        console.log('[RecordingPlayback] 音频可以播放');
-      });
+    const handleError = (e: Event) => {
+      console.error('[RecordingPlayback] 音頻播放錯誤:', e);
+      const errorDetails = {
+        error: audio.error,
+        networkState: audio.networkState,
+        readyState: audio.readyState,
+        src: audio.src,
+        audioType: audioBlob.type,
+        blobSize: audioBlob.size
+      };
+      console.error('[RecordingPlayback] 錯誤詳情:', errorDetails);
       
-      audio.addEventListener('ended', () => {
-        console.log('[RecordingPlayback] 音频播放结束');
-        setIsPlaying(false);
-        setCurrentTime(0);
-        setProgress(0);
-        if (intervalRef.current) {
-          clearInterval(intervalRef.current);
-          intervalRef.current = null;
+      let errorMsg = '音頻播放失敗';
+      if (audio.error) {
+        switch (audio.error.code) {
+          case MediaError.MEDIA_ERR_ABORTED:
+            errorMsg = '音頻播放被中止';
+            break;
+          case MediaError.MEDIA_ERR_NETWORK:
+            errorMsg = '網路錯誤導致音頻載入失敗';
+            break;
+          case MediaError.MEDIA_ERR_DECODE:
+            errorMsg = '音頻解碼失敗,請嘗試重新錄音';
+            break;
+          case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
+            errorMsg = `音頻格式 ${audioBlob.type} 不被支援`;
+            break;
+          default:
+            errorMsg = `音頻播放錯誤(代碼:${audio.error.code})`;
         }
-      });
-
-      audio.addEventListener('error', (e) => {
-        console.error('[RecordingPlayback] 音频播放错误:', e);
-        console.error('[RecordingPlayback] 音频错误详情:', {
-          error: audio.error,
-          networkState: audio.networkState,
-          readyState: audio.readyState,
-          src: audio.src
-        });
-        setIsPlaying(false);
-      });
+      }
+      
+      setErrorMessage(errorMsg);
+      setIsPlaying(false);
+    };
 
-      // 尝试加载音频
-      audio.load();
+    const handleLoadStart = () => {
+      console.log('[RecordingPlayback] 開始載入音頻');
+    };
 
-      return () => {
-        if (audio) {
-          audio.pause();
-          audio.src = '';
-        }
-        if (intervalRef.current) {
-          clearInterval(intervalRef.current);
-          intervalRef.current = null;
-        }
-      };
+    const handleProgress = () => {
+      console.log('[RecordingPlayback] 音頻載入進度更新');
+    };
+
+    // 綁定事件 - 在設置 src 之前
+    audio.addEventListener('loadstart', handleLoadStart);
+    audio.addEventListener('progress', handleProgress);
+    audio.addEventListener('loadedmetadata', handleLoadedMetadata);
+    audio.addEventListener('canplay', handleCanPlay);
+    audio.addEventListener('ended', handleEnded);
+    audio.addEventListener('error', handleError);
+
+    // 最後設置音頻源並開始加載
+    try {
+      audio.src = audioUrl;
+      console.log('[RecordingPlayback] 音頻src已設置,開始load()');
+      audio.load();
+    } catch (error) {
+      console.error('[RecordingPlayback] 設置音頻源時發生錯誤:', error);
+      setErrorMessage('音頻初始化失敗');
     }
-  }, [audioUrl, audioBlob]);
 
-  // 播放/暂停
+    // 清理函數
+    return () => {
+      // 只清理音頻元素,不清理 URL(讓組件卸載時清理)
+      if (audioRef.current) {
+        audioRef.current.pause();
+        audioRef.current.removeEventListener('loadstart', handleLoadStart);
+        audioRef.current.removeEventListener('progress', handleProgress);
+        audioRef.current.removeEventListener('loadedmetadata', handleLoadedMetadata);
+        audioRef.current.removeEventListener('canplay', handleCanPlay);
+        audioRef.current.removeEventListener('ended', handleEnded);
+        audioRef.current.removeEventListener('error', handleError);
+      }
+    };
+  }, [audioBlob, cleanupAudio]);
+
+  // 組件卸載時清理
+  useEffect(() => {
+    return () => {
+      cleanupAudio();
+      if (audioUrlRef.current) {
+        URL.revokeObjectURL(audioUrlRef.current);
+        audioUrlRef.current = null;
+      }
+    };
+  }, [cleanupAudio]);
+
+  // 播放/暂停,参考test_recording.html的简单实现
   const togglePlayback = async () => {
-    if (!audioRef.current) return;
+    if (!audioRef.current) {
+      console.warn('[RecordingPlayback] 音頻元素未初始化');
+      setErrorMessage('音頻元素未初始化');
+      return;
+    }
+
+    // 檢查音頻狀態
+    if (audioRef.current.error) {
+      console.error('[RecordingPlayback] 音頻元素有錯誤,無法播放:', audioRef.current.error);
+      setErrorMessage('音頻有錯誤,無法播放');
+      return;
+    }
+
+    if (!audioRef.current.src || audioRef.current.src === window.location.href) {
+      console.error('[RecordingPlayback] 音頻源無效:', audioRef.current.src);
+      setErrorMessage('音頻源無效');
+      return;
+    }
 
     try {
       if (isPlaying) {
@@ -153,10 +265,22 @@ const RecordingPlayback: React.FC<RecordingPlaybackProps> = ({ audioBlob, onClea
           intervalRef.current = null;
         }
       } else {
+        console.log('[RecordingPlayback] 開始播放音頻,readyState:', audioRef.current.readyState);
+        console.log('[RecordingPlayback] 音頻src:', audioRef.current.src);
+        
+        // 確保音頻已經載入
+        if (audioRef.current.readyState < 2) { // HAVE_CURRENT_DATA
+          console.log('[RecordingPlayback] 音頻尚未準備好,等待載入...');
+          setErrorMessage('音頻正在載入中,請稍候...');
+          return;
+        }
+        
+        // 參考test_recording.html的直接播放方式
         await audioRef.current.play();
         setIsPlaying(true);
+        setErrorMessage(null); // 清除錯誤信息
         
-        // 开始更新进度
+        // 開始更新進度
         intervalRef.current = window.setInterval(() => {
           if (audioRef.current) {
             const current = audioRef.current.currentTime;
@@ -168,6 +292,7 @@ const RecordingPlayback: React.FC<RecordingPlaybackProps> = ({ audioBlob, onClea
       }
     } catch (error) {
       console.error('播放失败:', error);
+      setErrorMessage(`播放失敗: ${error instanceof Error ? error.message : String(error)}`);
       setIsPlaying(false);
     }
   };
@@ -199,7 +324,7 @@ const RecordingPlayback: React.FC<RecordingPlaybackProps> = ({ audioBlob, onClea
         
         <div style={{ flex: 1 }}>
           <Text style={{ color: 'white', fontSize: '14px', fontWeight: 500 }}>
-            录音测试播放
+            录音播放
           </Text>
           <Progress
             percent={progress}
@@ -230,7 +355,18 @@ const RecordingPlayback: React.FC<RecordingPlaybackProps> = ({ audioBlob, onClea
         <Text style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '12px' }}>
           文件大小: {(audioBlob.size / 1024).toFixed(1)} KB
         </Text>
+        {audioBlob.type && (
+          <Text style={{ color: 'rgba(255, 255, 255, 0.7)', fontSize: '12px' }}>
+            格式: {audioBlob.type}
+          </Text>
+        )}
       </PlaybackInfo>
+      
+      {errorMessage && (
+        <ErrorMessage>
+          {errorMessage}
+        </ErrorMessage>
+      )}
     </PlaybackContainer>
   );
 };

+ 35 - 8
frontend/src/store/slices/audioSlice.ts

@@ -3,23 +3,50 @@ import { AudioVisualizationData, RecordingConfig, PlaybackConfig } from '../../t
 
 // 检测浏览器支持的音频格式
 function getSupportedMimeType(): string {
+  // 參考 test_recording.html 的成功實現,優先選擇 audio/webm;codecs=opus
   const types = [
-    'audio/webm;codecs=opus',
-    'audio/webm',
-    'audio/mp4',
-    'audio/wav',
-    'audio/ogg;codecs=opus'
+    'audio/webm;codecs=opus',  // 現代瀏覽器首選,錄製和播放都支持很好
+    'audio/webm',              // 備用 webm 格式
+    'audio/ogg;codecs=opus',   // Firefox 等瀏覽器支持
+    'audio/mp4',               // Safari 可能需要
+    'audio/wav'                // 最後備用格式
   ];
 
+  // 創建一個臨時的 audio 元素來測試播放支持
+  const audio = document.createElement('audio');
+
+  for (const type of types) {
+    // 檢查 MediaRecorder 是否支持錄製
+    const recordingSupported = MediaRecorder.isTypeSupported(type);
+    
+    // 檢查 audio 元素是否支持播放
+    const playbackSupported = audio.canPlayType(type) !== '';
+    
+    console.log(`[AudioSlice] 檢查格式 ${type}:`, {
+      錄製支持: recordingSupported,
+      播放支持: playbackSupported
+    });
+    
+    if (recordingSupported && playbackSupported) {
+      console.log('[AudioSlice] 使用支持的音頻格式(錄製+播放):', type);
+      return type;
+    } else if (recordingSupported) {
+      console.log('[AudioSlice] 格式僅支持錄製:', type);
+    } else if (playbackSupported) {
+      console.log('[AudioSlice] 格式僅支持播放:', type);
+    }
+  }
+
+  // 如果沒有找到完全兼容的格式,優先選擇 webm 格式(參考 test_recording.html)
   for (const type of types) {
     if (MediaRecorder.isTypeSupported(type)) {
-      console.log('[AudioSlice] 使用支持的音频格式:', type);
+      console.warn('[AudioSlice] 使用錄製兼容的格式(播放需要驗證):', type);
       return type;
     }
   }
 
-  console.warn('[AudioSlice] 未找到支持的音频格式,使用默认格式');
-  return 'audio/webm'; // 默认回退
+  console.warn('[AudioSlice] 未找到支持的音頻格式,使用默認 webm 格式');
+  return 'audio/webm;codecs=opus'; // 改為使用 webm 格式作為默認值
 }
 
 interface AudioState {

+ 6 - 1
logs/backend.log

@@ -1 +1,6 @@
-{"error": "name 'log_error' is not defined", "error_type": "NameError", "session_id": "", "context": {"connection_id": "conn_1758520478_4615442192", "session_id": "sess_1758520478_5f867e93"}, "event": "Error occurred", "logger": "src.core.logger", "level": "error", "timestamp": "2025-09-22T05:55:50.893084Z", "exception": "Traceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 375, in process_audio\n    log_ai_request(session_id, \"transcription\", len(audio_data))\nNameError: name 'log_ai_request' is not defined\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/websocket_server.py\", line 405, in _process_audio_async\n    response_audio = await ai_service.process_audio(\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 438, in process_audio\n    log_error(e, {\nNameError: name 'log_error' is not defined"}
+{"error": "name 'log_error' is not defined", "error_type": "NameError", "session_id": "", "context": {"connection_id": "conn_1758522727_4455774000", "session_id": "sess_1758522727_83d3ea7c"}, "event": "Error occurred", "logger": "src.core.logger", "level": "error", "timestamp": "2025-09-22T06:32:20.502701Z", "exception": "Traceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 375, in process_audio\n    log_ai_request(session_id, \"transcription\", len(audio_data))\nNameError: name 'log_ai_request' is not defined\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/websocket_server.py\", line 405, in _process_audio_async\n    response_audio = await ai_service.process_audio(\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 438, in process_audio\n    log_error(e, {\nNameError: name 'log_error' is not defined"}
+{"error": "name 'log_error' is not defined", "error_type": "NameError", "session_id": "", "context": {"connection_id": "conn_1758522727_4455774000", "session_id": "sess_1758522727_83d3ea7c"}, "event": "Error occurred", "logger": "src.core.logger", "level": "error", "timestamp": "2025-09-22T06:32:33.010568Z", "exception": "Traceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 375, in process_audio\n    log_ai_request(session_id, \"transcription\", len(audio_data))\nNameError: name 'log_ai_request' is not defined\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/websocket_server.py\", line 405, in _process_audio_async\n    response_audio = await ai_service.process_audio(\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 438, in process_audio\n    log_error(e, {\nNameError: name 'log_error' is not defined"}
+{"error": "name 'log_error' is not defined", "error_type": "NameError", "session_id": "", "context": {"connection_id": "conn_1758522727_4455774000", "session_id": "sess_1758522727_83d3ea7c"}, "event": "Error occurred", "logger": "src.core.logger", "level": "error", "timestamp": "2025-09-22T06:40:17.238118Z", "exception": "Traceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 375, in process_audio\n    log_ai_request(session_id, \"transcription\", len(audio_data))\nNameError: name 'log_ai_request' is not defined\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/websocket_server.py\", line 405, in _process_audio_async\n    response_audio = await ai_service.process_audio(\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 438, in process_audio\n    log_error(e, {\nNameError: name 'log_error' is not defined"}
+{"error": "name 'log_error' is not defined", "error_type": "NameError", "session_id": "", "context": {"connection_id": "conn_1758522727_4455774000", "session_id": "sess_1758522727_83d3ea7c"}, "event": "Error occurred", "logger": "src.core.logger", "level": "error", "timestamp": "2025-09-22T06:40:34.492022Z", "exception": "Traceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 375, in process_audio\n    log_ai_request(session_id, \"transcription\", len(audio_data))\nNameError: name 'log_ai_request' is not defined\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/websocket_server.py\", line 405, in _process_audio_async\n    response_audio = await ai_service.process_audio(\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 438, in process_audio\n    log_error(e, {\nNameError: name 'log_error' is not defined"}
+{"error": "name 'log_error' is not defined", "error_type": "NameError", "session_id": "", "context": {"connection_id": "conn_1758523420_4455934800", "session_id": "sess_1758523420_7a36fdf7"}, "event": "Error occurred", "logger": "src.core.logger", "level": "error", "timestamp": "2025-09-22T06:43:53.609651Z", "exception": "Traceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 375, in process_audio\n    log_ai_request(session_id, \"transcription\", len(audio_data))\nNameError: name 'log_ai_request' is not defined\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/websocket_server.py\", line 405, in _process_audio_async\n    response_audio = await ai_service.process_audio(\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 438, in process_audio\n    log_error(e, {\nNameError: name 'log_error' is not defined"}
+{"error": "name 'log_error' is not defined", "error_type": "NameError", "session_id": "", "context": {"connection_id": "conn_1758523420_4455934800", "session_id": "sess_1758523420_7a36fdf7"}, "event": "Error occurred", "logger": "src.core.logger", "level": "error", "timestamp": "2025-09-22T06:44:10.524971Z", "exception": "Traceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 375, in process_audio\n    log_ai_request(session_id, \"transcription\", len(audio_data))\nNameError: name 'log_ai_request' is not defined\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/websocket_server.py\", line 405, in _process_audio_async\n    response_audio = await ai_service.process_audio(\n  File \"/Users/wuzilong/work/00code/Tolan/backend/src/services/ai_service.py\", line 438, in process_audio\n    log_error(e, {\nNameError: name 'log_error' is not defined"}

+ 1 - 1
logs/backend.pid

@@ -1 +1 @@
-35244
+41081

+ 18 - 958
logs/frontend.log

@@ -2,9 +2,9 @@
 > tolan-frontend@0.1.0 start
 > react-scripts start
 
-(node:35270) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
+(node:41108) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
 (Use `node --trace-deprecation ...` to show where the warning was created)
-(node:35270) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
+(node:41108) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
 Starting the development server...
 
 Compiled successfully!
@@ -25,17 +25,15 @@ Compiling...
 Compiled with warnings.
 
 [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 Search for the keywords to learn more about each warning.
 To ignore, add // eslint-disable-next-line to the line before.
 
 WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 webpack compiled with 1 warning
 Compiling...
@@ -43,17 +41,15 @@ No issues found.
 Compiled with warnings.
 
 [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 Search for the keywords to learn more about each warning.
 To ignore, add // eslint-disable-next-line to the line before.
 
 WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 webpack compiled with 1 warning
 No issues found.
@@ -61,966 +57,30 @@ Compiling...
 Compiled with warnings.
 
 [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 Search for the keywords to learn more about each warning.
 To ignore, add // eslint-disable-next-line to the line before.
 
 WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-ERROR in src/hooks/useAudio.ts:140:30
-TS2339: Property 'isCurrentlyRecording' does not exist on type 'AudioRecorder'.
-    138 |     
-    139 |     // 检查录音器的实际状态,而不仅仅依赖Redux状态
-  > 140 |     if (!recorderRef.current.isCurrentlyRecording()) {
-        |                              ^^^^^^^^^^^^^^^^^^^^
-    141 |       console.warn('[useAudio] 尝试停止录音但录音器当前没有在录音');
-    142 |       return null;
-    143 |     }
-
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-Files successfully emitted, waiting for typecheck results...
-Issues checking in progress...
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-Files successfully emitted, waiting for typecheck results...
-Issues checking in progress...
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 196:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 256:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-Files successfully emitted, waiting for typecheck results...
-Issues checking in progress...
-No issues found.
-Compiling...
-Failed to compile.
-
-[eslint] 
-src/hooks/useAudio.ts
-Syntax error: 'catch' or 'finally' expected (173:3)
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-ERROR in ./src/hooks/useAudio.ts
-Module build failed (from ./node_modules/babel-loader/lib/index.js):
-SyntaxError: /Users/wuzilong/work/00code/Tolan/frontend/src/hooks/useAudio.ts: Missing catch or finally clause. (145:4)
-
-  143 |     }
-  144 |
-> 145 |     try {
-      |     ^
-  146 |       console.log('[useAudio] 准备停止录音器...');
-  147 |       
-  148 |       // 停止计时
-    at constructor (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:367:19)
-    at TypeScriptParserMixin.raise (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:6630:19)
-    at TypeScriptParserMixin.parseTryStatement (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13233:12)
-    at TypeScriptParserMixin.parseStatementContent (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12813:21)
-    at TypeScriptParserMixin.parseStatementContent (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9532:18)
-    at TypeScriptParserMixin.parseStatementLike (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12776:17)
-    at TypeScriptParserMixin.parseStatementListItem (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12756:17)
-    at TypeScriptParserMixin.parseBlockOrModuleBlockBody (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13325:61)
-    at TypeScriptParserMixin.parseBlockBody (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13318:10)
-    at TypeScriptParserMixin.parseBlock (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13306:10)
-    at TypeScriptParserMixin.parseFunctionBody (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12110:24)
-    at TypeScriptParserMixin.parseArrowExpression (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12085:10)
-    at TypeScriptParserMixin.parseAsyncArrowFromCallExpression (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11268:10)
-    at TypeScriptParserMixin.parseAsyncArrowFromCallExpression (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9786:18)
-    at TypeScriptParserMixin.parseCoverCallAndAsyncArrowHead (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11198:27)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11124:19)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9358:18)
-    at TypeScriptParserMixin.parseSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11098:19)
-    at TypeScriptParserMixin.parseExprSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11089:17)
-    at TypeScriptParserMixin.parseUpdate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11070:21)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11050:23)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9857:18)
-    at TypeScriptParserMixin.parseMaybeUnaryOrPrivate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10903:61)
-    at TypeScriptParserMixin.parseExprOps (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10908:23)
-    at TypeScriptParserMixin.parseMaybeConditional (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10885:23)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10835:21)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9806:20)
-    at /Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:39
-    at TypeScriptParserMixin.allowInAnd (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12436:12)
-    at TypeScriptParserMixin.parseMaybeAssignAllowIn (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:17)
-    at TypeScriptParserMixin.parseMaybeAssignAllowInOrVoidPattern (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12503:17)
-    at TypeScriptParserMixin.parseExprListItem (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12185:18)
-    at TypeScriptParserMixin.parseCallExpressionArguments (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11256:22)
-    at TypeScriptParserMixin.parseCoverCallAndAsyncArrowHead (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11190:29)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11124:19)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9358:18)
-    at TypeScriptParserMixin.parseSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11098:19)
-    at TypeScriptParserMixin.parseExprSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11089:17)
-    at TypeScriptParserMixin.parseUpdate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11070:21)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11050:23)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9857:18)
-    at TypeScriptParserMixin.parseMaybeUnaryOrPrivate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10903:61)
-    at TypeScriptParserMixin.parseExprOps (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10908:23)
-    at TypeScriptParserMixin.parseMaybeConditional (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10885:23)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10835:21)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9806:20)
-    at /Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:39
-    at TypeScriptParserMixin.allowInAnd (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12431:16)
-    at TypeScriptParserMixin.parseMaybeAssignAllowIn (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:17)
-    at TypeScriptParserMixin.parseVar (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13393:91)
-
-ERROR in [eslint] 
-src/hooks/useAudio.ts
-  Line 173:3:  Parsing error: 'catch' or 'finally' expected
-
-webpack compiled with 2 errors and 1 warning
-Files successfully emitted, waiting for typecheck results...
-Issues checking in progress...
-ERROR in src/hooks/useAudio.ts:131:25
-TS2554: Expected 2 arguments, but got 1.
-    129 |
-    130 |   // 停止录音
-  > 131 |   const stopRecording = useCallback(async (): Promise<Blob | null> => {
-        |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 132 |     console.log('[useAudio] stopRecording 开始执行,当前状态:', {isRecording, hasRecorder: !!recorderRef.current});
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 133 |     
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 134 |     if (!recorderRef.current) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 135 |       console.warn('[useAudio] 尝试停止录音但录音器未初始化');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 136 |       return null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 137 |     }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 138 |     
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 139 |     // 检查录音器的实际状态,而不仅仅依赖Redux状态
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 140 |     if (!recorderRef.current.isCurrentlyRecording()) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 141 |       console.warn('[useAudio] 尝试停止录音但录音器当前没有在录音');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 142 |       return null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 143 |     }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 144 |
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 145 |     try {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 146 |       console.log('[useAudio] 准备停止录音器...');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 147 |       
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 148 |       // 停止计时
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 149 |       if (recordingTimerRef.current) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 150 |         clearInterval(recordingTimerRef.current);
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 151 |         recordingTimerRef.current = null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 152 |         console.log('[useAudio] 录音计时器已停止');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 153 |       }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 154 |
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 155 |       dispatch(setRecordingStatus('processing'));
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 156 |       console.log('[useAudio] 调用录音器的stopRecording方法...');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 157 |       
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 158 |       try {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 159 |         const audioBlob = await recorderRef.current.stopRecording();
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 160 |       console.log('[useAudio] 录音器stopRecording返回结果:', audioBlob ? `Blob(${audioBlob.size} bytes)` : 'null');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 161 |       
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 162 |       dispatch(resetRecording());
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 163 |       console.log('[useAudio] stopRecording 执行完成,返回音频数据');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 164 |       
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 165 |       return audioBlob;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 166 |
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 167 |     } catch (error) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 168 |       console.error('[useAudio] 录音停止失败:', error);
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 169 |       dispatch(setError(error instanceof Error ? error.message : '录音停止失败'));
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 170 |       dispatch(resetRecording());
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 171 |       return null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 172 |     }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 173 |   }, [isRecording, dispatch]);
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-    174 |
-    175 |   // 取消录音
-    176 |   const cancelRecording = useCallback(() => {
-
-ERROR in src/hooks/useAudio.ts:173:4
-TS1472: 'catch' or 'finally' expected.
-    171 |       return null;
-    172 |     }
-  > 173 |   }, [isRecording, dispatch]);
-        |    ^
-    174 |
-    175 |   // 取消录音
-    176 |   const cancelRecording = useCallback(() => {
-
-ERROR in src/hooks/useAudio.ts:173:29
-TS1005: ';' expected.
-    171 |       return null;
-    172 |     }
-  > 173 |   }, [isRecording, dispatch]);
-        |                             ^
-    174 |
-    175 |   // 取消录音
-    176 |   const cancelRecording = useCallback(() => {
-
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 211:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 271:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 211:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 271:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 211:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 271:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 211:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 271:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Failed to compile.
-
-[eslint] 
-src/hooks/useAudio.ts
-Syntax error: 'catch' or 'finally' expected (180:3)
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-ERROR in ./src/hooks/useAudio.ts
-Module build failed (from ./node_modules/babel-loader/lib/index.js):
-SyntaxError: /Users/wuzilong/work/00code/Tolan/frontend/src/hooks/useAudio.ts: Missing catch or finally clause. (145:4)
-
-  143 |     }
-  144 |
-> 145 |     try {
-      |     ^
-  146 |       console.log('[useAudio] 准备停止录音器...');
-  147 |       
-  148 |       // 停止计时
-    at constructor (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:367:19)
-    at TypeScriptParserMixin.raise (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:6630:19)
-    at TypeScriptParserMixin.parseTryStatement (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13233:12)
-    at TypeScriptParserMixin.parseStatementContent (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12813:21)
-    at TypeScriptParserMixin.parseStatementContent (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9532:18)
-    at TypeScriptParserMixin.parseStatementLike (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12776:17)
-    at TypeScriptParserMixin.parseStatementListItem (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12756:17)
-    at TypeScriptParserMixin.parseBlockOrModuleBlockBody (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13325:61)
-    at TypeScriptParserMixin.parseBlockBody (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13318:10)
-    at TypeScriptParserMixin.parseBlock (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13306:10)
-    at TypeScriptParserMixin.parseFunctionBody (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12110:24)
-    at TypeScriptParserMixin.parseArrowExpression (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12085:10)
-    at TypeScriptParserMixin.parseAsyncArrowFromCallExpression (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11268:10)
-    at TypeScriptParserMixin.parseAsyncArrowFromCallExpression (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9786:18)
-    at TypeScriptParserMixin.parseCoverCallAndAsyncArrowHead (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11198:27)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11124:19)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9358:18)
-    at TypeScriptParserMixin.parseSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11098:19)
-    at TypeScriptParserMixin.parseExprSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11089:17)
-    at TypeScriptParserMixin.parseUpdate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11070:21)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11050:23)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9857:18)
-    at TypeScriptParserMixin.parseMaybeUnaryOrPrivate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10903:61)
-    at TypeScriptParserMixin.parseExprOps (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10908:23)
-    at TypeScriptParserMixin.parseMaybeConditional (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10885:23)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10835:21)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9806:20)
-    at /Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:39
-    at TypeScriptParserMixin.allowInAnd (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12436:12)
-    at TypeScriptParserMixin.parseMaybeAssignAllowIn (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:17)
-    at TypeScriptParserMixin.parseMaybeAssignAllowInOrVoidPattern (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12503:17)
-    at TypeScriptParserMixin.parseExprListItem (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12185:18)
-    at TypeScriptParserMixin.parseCallExpressionArguments (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11256:22)
-    at TypeScriptParserMixin.parseCoverCallAndAsyncArrowHead (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11190:29)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11124:19)
-    at TypeScriptParserMixin.parseSubscript (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9358:18)
-    at TypeScriptParserMixin.parseSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11098:19)
-    at TypeScriptParserMixin.parseExprSubscripts (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11089:17)
-    at TypeScriptParserMixin.parseUpdate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11070:21)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:11050:23)
-    at TypeScriptParserMixin.parseMaybeUnary (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9857:18)
-    at TypeScriptParserMixin.parseMaybeUnaryOrPrivate (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10903:61)
-    at TypeScriptParserMixin.parseExprOps (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10908:23)
-    at TypeScriptParserMixin.parseMaybeConditional (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10885:23)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10835:21)
-    at TypeScriptParserMixin.parseMaybeAssign (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:9806:20)
-    at /Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:39
-    at TypeScriptParserMixin.allowInAnd (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:12431:16)
-    at TypeScriptParserMixin.parseMaybeAssignAllowIn (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:10804:17)
-    at TypeScriptParserMixin.parseVar (/Users/wuzilong/work/00code/Tolan/frontend/node_modules/@babel/parser/lib/index.js:13393:91)
-
-ERROR in [eslint] 
-src/hooks/useAudio.ts
-  Line 180:3:  Parsing error: 'catch' or 'finally' expected
-
-webpack compiled with 2 errors and 1 warning
-ERROR in src/hooks/useAudio.ts:131:25
-TS2554: Expected 2 arguments, but got 1.
-    129 |
-    130 |   // 停止录音
-  > 131 |   const stopRecording = useCallback(async (): Promise<Blob | null> => {
-        |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 132 |     console.log('[useAudio] stopRecording 开始执行,当前状态:', {isRecording, hasRecorder: !!recorderRef.current});
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 133 |     
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 134 |     if (!recorderRef.current) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 135 |       console.warn('[useAudio] 尝试停止录音但录音器未初始化');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 136 |       return null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 137 |     }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 138 |     
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 139 |     // 检查录音器的实际状态,而不仅仅依赖Redux状态
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 140 |     if (!recorderRef.current.isCurrentlyRecording()) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 141 |       console.warn('[useAudio] 尝试停止录音但录音器当前没有在录音');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 142 |       return null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 143 |     }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 144 |
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 145 |     try {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 146 |       console.log('[useAudio] 准备停止录音器...');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 147 |       
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 148 |       // 停止计时
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 149 |       if (recordingTimerRef.current) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 150 |         clearInterval(recordingTimerRef.current);
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 151 |         recordingTimerRef.current = null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 152 |         console.log('[useAudio] 录音计时器已停止');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 153 |       }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 154 |
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 155 |       dispatch(setRecordingStatus('processing'));
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 156 |       console.log('[useAudio] 调用录音器的stopRecording方法...');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 157 |       
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 158 |       try {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 159 |         const audioBlob = await recorderRef.current.stopRecording();
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 160 |         console.log('[useAudio] 录音器stopRecording返回结果:', audioBlob ? `Blob(${audioBlob.size} bytes)` : 'null');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 161 |         
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 162 |         dispatch(resetRecording());
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 163 |         console.log('[useAudio] stopRecording 执行完成,返回音频数据');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 164 |         
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 165 |         return audioBlob;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 166 |       } catch (stopError) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 167 |         console.error('[useAudio] 正常停止录音失败,尝试强制停止:', stopError);
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 168 |         
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 169 |         // 备用方案:强制停止录音器
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 170 |         try {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 171 |           recorderRef.current.forceStop();
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 172 |           console.log('[useAudio] 强制停止成功');
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 173 |         } catch (forceError) {
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 174 |           console.error('[useAudio] 强制停止也失败:', forceError);
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 175 |         }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 176 |         
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 177 |                  dispatch(resetRecording());
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 178 |          return null;
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 179 |        }
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  > 180 |   }, [isRecording, dispatch]);
-        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-    181 |
-    182 |   // 取消录音
-    183 |   const cancelRecording = useCallback(() => {
-
-ERROR in src/hooks/useAudio.ts:180:4
-TS1472: 'catch' or 'finally' expected.
-    178 |          return null;
-    179 |        }
-  > 180 |   }, [isRecording, dispatch]);
-        |    ^
-    181 |
-    182 |   // 取消录音
-    183 |   const cancelRecording = useCallback(() => {
-
-ERROR in src/hooks/useAudio.ts:180:29
-TS1005: ';' expected.
-    178 |          return null;
-    179 |        }
-  > 180 |   }, [isRecording, dispatch]);
-        |                             ^
-    181 |
-    182 |   // 取消录音
-    183 |   const cancelRecording = useCallback(() => {
-
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-ERROR in src/components/RecordingPlayback.tsx:51:20
-TS2304: Cannot find name 'useMemo'.
-    49 |
-    50 |   // 创建和管理音频URL
-  > 51 |   const audioUrl = useMemo(() => {
-       |                    ^^^^^^^
-    52 |     // 清理之前的URL
-    53 |     if (audioUrlRef.current) {
-    54 |       URL.revokeObjectURL(audioUrlRef.current);
-
 Compiling...
 Compiled with warnings.
 
 [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 Search for the keywords to learn more about each warning.
 To ignore, add // eslint-disable-next-line to the line before.
 
 WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-Files successfully emitted, waiting for typecheck results...
-Issues checking in progress...
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-src/store/slices/audioSlice.ts
-  Line 5:10:  'getSupportedMimeType' is defined but never used  @typescript-eslint/no-unused-vars
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-src/store/slices/audioSlice.ts
-  Line 5:10:  'getSupportedMimeType' is defined but never used  @typescript-eslint/no-unused-vars
-
-webpack compiled with 1 warning
-Compiling...
-Files successfully emitted, waiting for typecheck results...
-Issues checking in progress...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-src/store/slices/audioSlice.ts
-  Line 5:10:  'getSupportedMimeType' is defined but never used  @typescript-eslint/no-unused-vars
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-src/store/slices/audioSlice.ts
-  Line 5:10:  'getSupportedMimeType' is defined but never used  @typescript-eslint/no-unused-vars
-
-webpack compiled with 1 warning
-Files successfully emitted, waiting for typecheck results...
-Issues checking in progress...
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-webpack compiled with 1 warning
-No issues found.
-Compiling...
-Compiled with warnings.
-
-[eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-
-Search for the keywords to learn more about each warning.
-To ignore, add // eslint-disable-next-line to the line before.
-
-WARNING in [eslint] 
-src/components/VoiceRecorder.tsx
-  Line 345:9:  'handleClick' is assigned a value but never used       @typescript-eslint/no-unused-vars
-  Line 376:9:  'handleTouchStart' is assigned a value but never used  @typescript-eslint/no-unused-vars
-
-src/hooks/useAudio.ts
-  Line 128:6:  React Hook useCallback has a missing dependency: 'stopRecording'. Either include it or remove the dependency array     react-hooks/exhaustive-deps
-  Line 203:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
-  Line 263:6:  React Hook useCallback has an unnecessary dependency: 'isRecording'. Either exclude it or remove the dependency array  react-hooks/exhaustive-deps
+src/components/RecordingPlayback.tsx
+  Line 1:46:  'useMemo' is defined but never used  @typescript-eslint/no-unused-vars
 
 webpack compiled with 1 warning
 No issues found.

+ 1 - 1
logs/frontend.pid

@@ -1 +1 @@
-35247
+41085

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است