Skip to content

Develop interview #6472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

a951273629
Copy link

@a951273629 a951273629 commented Apr 30, 2025

💻 变更类型 | Change Type

🔀 变更说明 | Description of Change

Summary by CodeRabbit

  • New Features

    • Added voice recognition and voiceprint (speaker) recognition capabilities, including a TensorFlow-based training and matching system.
    • Introduced a speech-to-text overlay with live transcription, pause/resume, and transcript submission.
    • Implemented WeChat login and user authentication, featuring a QR code login flow and user info display in the sidebar.
    • Added toast notifications for chat input validation.
    • Added a new voice action button to the chat input for easy access to voice features.
  • Enhancements

    • Sidebar now displays WeChat user information when logged in.
    • Chat overlay and actions improved for voice interactions.
    • Default UI theme set to dark mode.
    • Default language changed to Simplified Chinese.
    • Application container now always fills the viewport for a more immersive experience.
  • Bug Fixes

    • Improved chat scroll-to-bottom functionality and overlay integration.
  • Documentation

    • Removed several non-English deployment and FAQ guides to streamline documentation.
  • Chores

    • Added new dependencies for AI, speech recognition, and UI enhancements.
    • Updated TypeScript configuration for improved build reliability.
  • Style

    • Introduced new and updated styles for voice, interview overlay, WeChat components, and chat actions for a consistent and modern look.

Nan added 3 commits April 25, 2025 10:23
Merge branch 'main' into develop_interview

merge main.
Copy link

vercel bot commented Apr 30, 2025

Someone is attempting to deploy a commit to the NextChat Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Apr 30, 2025

Walkthrough

This update introduces several new features and UI enhancements, focusing on voice and WeChat authentication functionalities. It adds new React components for WeChat login, author display, and a voice recognition overlay, each with corresponding SCSS modules for styling. The chat interface is updated with a voice input action and an overlay for speech-to-text transcription. Authentication flow is improved with a new login page, authentication wrapper, and access state management. The default language is switched to Simplified Chinese, and the default theme is set to dark mode. Multiple documentation files in Spanish, Japanese, and Korean are removed. New dependencies are added for TensorFlow.js, speech recognition, toast notifications, and utility libraries.

Changes

File(s) / Path(s) Change Summary
app/components/TensorFlow.module.scss, app/components/WechatAuthor.module.scss, app/components/WechatLogin.module.scss, app/components/interview-overlay.scss Added new SCSS modules for TensorFlow voice UI, WeChat author, WeChat login, and interview overlay components, providing custom styles for layout, status indicators, animations, and responsive design.
app/components/TensorFlow.tsx Introduced a new React component for voiceprint recognition using TensorFlow.js, handling training, recognition, audio processing, and UI feedback.
app/components/WechatAuthor.tsx Added a new React component for displaying WeChat user info, managing login state, dropdown menu, and logout functionality.
app/components/WechatLogin.tsx Added a new React component simulating a WeChat QR code login flow with UI states, local storage integration, and global access store updates.
app/components/auth-wrapper.tsx Introduced an authentication wrapper component to restrict route access based on login status and redirect unauthenticated users to the login page.
app/components/chat.module.scss Added .chat-input-action-voice class for a new voice action button in the chat input actions area.
app/components/chat.tsx Added a voice action button, interview overlay integration, toast notifications, and reorganized scroll-to-bottom logic in the chat component.
app/components/home.module.scss Modified container sizing to always fill the viewport and removed borders and shadows.
app/components/home.tsx Added the login route and placeholder for interview overlay and authentication wrapper in the main routing logic.
app/components/interview-overlay.tsx Introduced a new React component for a draggable, resizable overlay providing live speech-to-text transcription and controls for pausing, stopping, and clearing the transcript.
app/components/sidebar.tsx Added WeChat author component to the sidebar and removed the mask icon button.
app/constant.ts Added Login and Interview paths to the Path enum; changed the order and provider assignments in the DEFAULT_MODELS array.
app/locales/index.ts Changed the default language and fallback locale from English to Simplified Chinese.
app/pages/login.tsx Added a new login page component that redirects authenticated users and renders the WeChat login UI.
app/store/access.ts Added wechatLoggedIn and accessToken properties to the default access state for tracking WeChat authentication.
app/store/config.ts Changed the default UI theme from automatic to dark mode.
docs/cloudflare-pages-es.md, docs/cloudflare-pages-ja.md, docs/cloudflare-pages-ko.md, docs/faq-es.md, docs/faq-ja.md, docs/faq-ko.md, docs/synchronise-chat-logs-es.md, docs/synchronise-chat-logs-ja.md, docs/synchronise-chat-logs-ko.md Deleted documentation files in Spanish, Japanese, and Korean, including deployment guides and FAQs.
package.json Updated build script for cleanup; added dependencies for TensorFlow.js, speech recognition, toast notifications, lodash, crypto-js, and related typings.
tencentcloud-speech-sdk-js Added subproject commit reference for the Tencent Cloud speech SDK.
tsconfig.json Disabled type checking for JavaScript files by setting "checkJs": false.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatUI
    participant InterviewOverlay
    participant SpeechRecognition
    participant Toast
    participant WechatLogin
    participant AccessStore

    User->>ChatUI: Clicks voice action button
    ChatUI->>InterviewOverlay: Show overlay
    InterviewOverlay->>SpeechRecognition: Start listening
    SpeechRecognition-->>InterviewOverlay: Transcript updates
    InterviewOverlay->>ChatUI: onTextUpdate (transcript)
    User->>InterviewOverlay: Clicks pause/stop/clear
    InterviewOverlay->>SpeechRecognition: Pause/stop/clear recognition
    InterviewOverlay->>ChatUI: submitMessage (final transcript)
    ChatUI->>Toast: Validate & show toast if needed

    User->>WechatLogin: Scans QR code / clicks login
    WechatLogin->>AccessStore: Update login state
    WechatLogin->>LocalStorage: Store user info
    WechatLogin->>ChatUI: Redirect to chat page
Loading

Suggested labels

enhancement

Poem

🐇
A hop, a skip, a brand new feat—
Now voice and WeChat logins greet!
Overlay floats, the chat can hear,
With speech-to-text and toast so clear.
Darkened themes and docs swept clean,
The rabbit’s code is crisp and keen.
New features bloom—let’s celebrate,
And chat away, it’s truly great!

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (30)
app/components/chat.module.scss (1)

623-623: Remove commented-out height constraint

This commented height constraint could cause layout issues if uncommented. Either implement it properly or remove it completely.

-  // height: 15vh; // 添加固定高度为视窗高度的15%
app/components/interview-overlay.scss (3)

7-7: Hardcoded color values may not respect theme changes.

The background color is hardcoded to a dark value (#1e1e1e) which may not adapt to light theme mode if the application supports theme switching.

- background-color: #1e1e1e;
+ background-color: var(--bg-color, #1e1e1e);

94-96: Transcript display has limited height which may truncate important content.

The max-height of 120px for the transcript display might be too restrictive for longer transcriptions, especially when users need to review their entire speech input.

- max-height: 120px;
+ max-height: 25vh;

159-163: Animation uses non-standard keyframe prefix.

The pulse animation might need browser-specific prefixes for better cross-browser compatibility, especially for older browsers.

Consider adding additional keyframe definitions with vendor prefixes or using a CSS autoprefixer in your build process to ensure broader browser compatibility.

app/locales/index.ts (1)

82-82: Fallback language changed to Chinese, potentially affecting error states.

The fallback language (used when the selected language is missing translations) has been changed from English to Chinese. This means users of non-Chinese, non-English languages may see Chinese text if a translation is missing.

Consider keeping English as the fallback language while making Chinese the default, as English translations are more likely to be complete and widely understood as a fallback option.

- const fallbackLang = cn;
+ const fallbackLang = en;
app/components/sidebar.tsx (2)

262-274: MaskIcon functionality has been removed but left commented out.

The MaskIcon button and its functionality have been commented out rather than properly removed. While this preserves the code for future reference, commented-out code can lead to confusion and maintenance issues.

Either fully remove the commented-out code or add a comment explaining why it's preserved and when it might be reintroduced.


228-228: Comment in Chinese without translation.

The comment "在侧边栏组件中添加WechatAuthor" is written in Chinese without an English translation, which may create barriers for developers who don't understand Chinese.

Consider adding bilingual comments or standardizing on a single language for code comments throughout the codebase.

- // 在侧边栏组件中添加WechatAuthor
+ // Add WechatAuthor component to the sidebar
app/components/home.tsx (1)

86-90: Consider removing or documenting commented code.

There are several blocks of commented code related to the interview feature and authentication wrapper. While these may be intended for future implementation, leaving uncommented code can create confusion for other developers.

If these features are planned for future releases:

  1. Consider adding TODO comments with expected implementation dates
  2. Or move this code to a feature branch until ready for integration

If these features are ready but just disabled, consider using feature flags instead of comments.

Also applies to: 202-202, 213-213

app/pages/login.tsx (1)

14-15: Consider translating comments to English for consistency.

The Chinese comment "检查是否已登录" (which means "Check if already logged in") may not align with the project's documentation standards if English is the primary language used throughout the codebase.

app/components/auth-wrapper.tsx (2)

7-8: Consider translating comments to English for consistency.

The Chinese comment "不需要登录就可以访问的路径" (which means "Paths that can be accessed without login") may not align with the project's documentation standards if English is the primary language used throughout the codebase.


16-28: Consider implementing more graceful redirect with return URL.

The current implementation redirects users to the login page but doesn't remember which page they were trying to access. Consider enhancing the redirect to include the original URL as a query parameter:

-      navigate(Path.Login);
+      navigate(`${Path.Login}?returnUrl=${encodeURIComponent(location.pathname)}`);

Then in the login page, redirect back to the original URL after successful authentication.

app/components/WechatLogin.module.scss (1)

9-18: Consider adding responsive width for login card.

The login card has a fixed width of 400px which might not be optimal for smaller mobile screens.

Consider making the login card width responsive:

.loginCard {
-  width: 400px;
+  width: 90%;
+  max-width: 400px;
  background-color: var(--white);
  border-radius: 10px;
  box-shadow: var(--card-shadow);
  padding: 30px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
app/constant.ts (3)

42-43: Translate comments to English for consistency.

The comment "添加到现有的Path枚举中" should be translated to English to maintain code consistency, as the rest of the file uses English comments.

-// 添加到现有的Path枚举中
+// Additions to the existing Path enum

Also applies to: 50-52, 57-58


679-689: Consider documenting the model provider reordering.

The reordering of model providers (prioritizing SiliconFlow over OpenAI) is a significant change that could affect the user experience. Consider adding a comment explaining the rationale for this change.

export const DEFAULT_MODELS = [
+  // SiliconFlow models placed first for preferred provider prioritization
  ...siliconflowModels.map((name) => ({
    name,
    available: true,
    sorted: seq++,
    provider: {
      id: "siliconflow",
      providerName: "SiliconFlow",
      providerType: "siliconflow",
      sorted: 1,
    },
  })),

822-832: Document OpenAI provider positioning.

The OpenAI provider has been moved to last position (sorted: 14). It would be helpful to add a comment explaining why it's now at the end of the list.

  ...openaiModels.map((name) => ({
    name,
    available: true,
    sorted: seq++, // Global sequence sort(index)
    provider: {
      id: "openai",
      providerName: "OpenAI",
      providerType: "openai",
-      sorted: 14, // 这里是固定的,确保顺序与之前内置的版本一致
+      sorted: 14, // Fixed position to ensure order consistency, now positioned last in provider list
    },
  })),
app/components/home.module.scss (1)

1-9: Consider keeping commented-out code clean.

The commented-out code for borders and shadows should either be removed entirely or properly commented with an explanation of why these styles were removed.

@mixin container {
  background-color: var(--white);
-  // 移除边框
-  // border: var(--border-in-light);
-  // 移除圆角
-  // border-radius: 20px;
-  // 移除阴影
-  // box-shadow: var(--shadow);
+  /* Border, radius and shadow removed for full-viewport design */
  color: var(--black);
  background-color: var(--white);
app/components/WechatAuthor.tsx (2)

24-34: Add null check before JSON.parse

The code attempts to parse user info from localStorage, but the try-catch could be complemented with an additional check.

    const userInfoStr = storage.getItem("wechat_user_info");
-    if (userInfoStr) {
+    if (userInfoStr && userInfoStr.trim() !== "") {
      try {
        const parsedInfo = JSON.parse(userInfoStr);
        setUserInfo(parsedInfo);
      } catch (e) {
        console.error("Failed to parse user info", e);
      }
    }

83-113: Consider adding alt text when avatar fails to load

The avatar image could benefit from a fallback mechanism.

        <img
          src={userInfo?.avatar}
          alt={userInfo?.nickname}
+         onError={(e) => {
+           e.currentTarget.onerror = null;
+           e.currentTarget.src = "path/to/default/avatar.png";
+         }}
          className={styles.avatar}
        />
app/components/WechatLogin.tsx (3)

1-12: Commented-out import should be removed or implemented

There's an unused import for QRCodeImage that's commented out. Either implement it or remove the comment.

- // import QRCodeImage from "../icons/wechat-qrcode-mock.svg"; // 假设有一个模拟的二维码SVG

42-77: Add error handling to the login simulation

The simulateLogin function lacks error handling, which could lead to a broken user experience if something goes wrong.

  const simulateLogin = () => {
+   try {
      // 模拟扫码
      setStatus(LoginStatus.SCANNED);

      // 模拟确认
      setTimeout(() => {
+       try {
          setStatus(LoginStatus.CONFIRMED);

          // 模拟登录成功
          setTimeout(() => {
+           try {
              setStatus(LoginStatus.SUCCESS);

              // 存储登录信息
              const mockUserInfo = {
                id: "wx_" + Math.floor(Math.random() * 1000000),
                nickname: "微信用户",
                avatar: "https://placekitten.com/100/100", // 模拟头像
                accessToken: "mock_token_" + Date.now(),
              };

              storage.setItem("wechat_user_info", JSON.stringify(mockUserInfo));

              // 更新访问状态
              accessStore.update((access) => {
                access.accessToken = mockUserInfo.accessToken;
                access.wechatLoggedIn = true;
              });

              // 登录成功后跳转
              setTimeout(() => {
                navigate(Path.Chat);
              }, 2000);
+           } catch (error) {
+             console.error("Error during success handling:", error);
+             handleLoginError();
+           }
          }, 1000);
+       } catch (error) {
+         console.error("Error during confirmation:", error);
+         handleLoginError();
+       }
      }, 2000);
+   } catch (error) {
+     console.error("Error initiating login:", error);
+     handleLoginError();
+   }
  };

159-170: QR Code expiration UI should match actual implementation

The component mentions a 2-minute expiration for the QR code but doesn't implement an actual timeout.

  useEffect(() => {
    // 初始加载
    const timer1 = setTimeout(() => {
      setStatus(LoginStatus.READY);
    }, 1000);
    
+   // QR Code expiration timer (2 minutes)
+   let expirationTimer: NodeJS.Timeout;
+   if (status === LoginStatus.READY) {
+     expirationTimer = setTimeout(() => {
+       if (status === LoginStatus.READY) {
+         setStatus(LoginStatus.ERROR);
+         setErrorMessage("二维码已过期,请刷新");
+       }
+     }, 120000); // 2 minutes
+   }

    return () => {
      clearTimeout(timer1);
+     if (expirationTimer) clearTimeout(expirationTimer);
    };
-  }, []);
+  }, [status]);
app/components/TensorFlow.tsx (4)

6-16: Enum values are in Chinese

The VoiceRecognitionStatus enum uses Chinese values directly. Consider using English keys with a separate translation layer for better internationalization.

// 声纹识别状态
enum VoiceRecognitionStatus {
-  IDLE = "空闲",
-  RECORDING = "录制中",
-  TRAINING = "训练中",
-  RECOGNIZING = "识别中",
-  TRAINED = "已训练",
-  MATCHED = "声纹匹配",
-  NOT_MATCHED = "声纹不匹配",
-  ERROR = "错误",
+  IDLE = "IDLE",
+  RECORDING = "RECORDING",
+  TRAINING = "TRAINING",
+  RECOGNIZING = "RECOGNIZING",
+  TRAINED = "TRAINED",
+  MATCHED = "MATCHED",
+  NOT_MATCHED = "NOT_MATCHED",
+  ERROR = "ERROR",
}

// Add a status display mapping function
const getStatusDisplay = (status: VoiceRecognitionStatus): string => {
  const statusMap: Record<VoiceRecognitionStatus, string> = {
    [VoiceRecognitionStatus.IDLE]: "空闲",
    [VoiceRecognitionStatus.RECORDING]: "录制中",
    [VoiceRecognitionStatus.TRAINING]: "训练中",
    [VoiceRecognitionStatus.RECOGNIZING]: "识别中",
    [VoiceRecognitionStatus.TRAINED]: "已训练",
    [VoiceRecognitionStatus.MATCHED]: "声纹匹配",
    [VoiceRecognitionStatus.NOT_MATCHED]: "声纹不匹配",
    [VoiceRecognitionStatus.ERROR]: "错误",
  };
  return statusMap[status];
};

304-350: Improve error handling in trainVoiceprint function

The current implementation doesn't handle TensorFlow-specific errors which might occur during training.

  // 训练声纹模型
  const trainVoiceprint = async () => {
    if (recordedChunksRef.current.length === 0 || !modelRef.current) {
      setStatus(VoiceRecognitionStatus.ERROR);
      setMessage("没有录音数据或模型未加载");
      return;
    }

    setStatus(VoiceRecognitionStatus.TRAINING);
    setMessage("正在训练声纹模型...");

    try {
      // 提取特征
      const features = await extractFeatures(recordedChunksRef.current);
      if (!features) throw new Error("特征提取失败");

+     // Wrap TensorFlow operations in try/catch for better error handling
+     let voiceprint;
+     try {
        // 使用模型提取声纹特征向量
-       const voiceprint = tf.tidy(() => {
+       voiceprint = tf.tidy(() => {
          // 前向传播获取声纹特征
          const prediction = modelRef.current!.predict(features) as tf.Tensor;
          // 归一化特征向量
          return tf.div(prediction, tf.norm(prediction));
        });
+     } catch (tfError) {
+       console.error("TensorFlow model error:", tfError);
+       throw new Error("模型处理失败");
+     }

      // 保存声纹特征
      const voiceprintData = await voiceprint.data();
      voiceprintRef.current = new Float32Array(voiceprintData);

      // 保存到localStorage
      localStorage.setItem(
        "userVoiceprint",
        JSON.stringify(Array.from(voiceprintData)),
      );

      setIsTrained(true);
      setStatus(VoiceRecognitionStatus.TRAINED);
      setMessage("声纹模型训练完成并已保存");

      // 清理
      voiceprint.dispose();
      features.dispose();
    } catch (error) {
      console.error("训练失败:", error);
      setStatus(VoiceRecognitionStatus.ERROR);
-     setMessage("声纹训练失败");
+     setMessage(`声纹训练失败: ${error instanceof Error ? error.message : '未知错误'}`);
+     
+     // Clean up any tensors that might have been created before the error
+     tf.engine().endScope();
    }
  };

394-407: Fixed threshold for voice recognition

Using a fixed threshold (0.7) for voice recognition might not be optimal. Consider making it configurable.

+     // Define threshold - consider making this configurable in component props or settings
+     const threshold = 0.7; // Default threshold
      // 判断是否为同一人 (阈值可调整)
-     const threshold = 0.7;
      const isMatch = score > threshold;

      setStatus(
        isMatch
          ? VoiceRecognitionStatus.MATCHED
          : VoiceRecognitionStatus.NOT_MATCHED,
      );
      setMessage(
        isMatch
          ? `声纹匹配成功!相似度: ${(score * 100).toFixed(2)}%`
-         : `声纹不匹配。相似度: ${(score * 100).toFixed(2)}%`,
+         : `声纹不匹配。相似度: ${(score * 100).toFixed(2)}%(阈值:${(threshold * 100).toFixed(2)}%)`,
      );

429-446: UI elements use hard-coded Chinese text

The component uses hard-coded Chinese text for UI elements. For better internationalization, consider using a localization system.

Import and use the same localization system that's used in other components (Locale from "../locales").

+ import Locale from "../locales";

  return (
    <div className={styles.voiceRecognitionContainer}>
-     <h2 className={styles.title}>声纹识别系统</h2>
+     <h2 className={styles.title}>{Locale.VoiceRecognition.Title}</h2>

      <div className={styles.statusContainer}>
        <div className={styles.statusIndicator}>
          <div
            className={`${styles.statusDot} ${styles[status.toLowerCase()]}`}
          ></div>
-         <span className={styles.statusText}>{status}</span>
+         <span className={styles.statusText}>{getStatusDisplay(status)}</span>
        </div>
        <p className={styles.message}>{message}</p>
      </div>
app/components/interview-overlay.tsx (5)

19-23: Remove commented-out countdown code

There's commented code that should be removed to keep the codebase clean.

  const [visible, setVisible] = useState(true);
- // const [countdown, setCountdown] = useState(20);
- // const countdownRef = useRef(countdown);
- // const intervalIdRef = useRef<NodeJS.Timeout | null>(null);
  // 添加暂停状态
  const [isPaused, setIsPaused] = useState(false);

143-146: Remove console.log in production code

There's a console.log that should be removed before production release.

        ),
      );
-     console.log(`mouse have moved  Width:${newWidth}vw`);
      setWidth(`${newWidth}vw`);
    }

121-155: Imperative DOM manipulation should be avoided

The drag functionality uses direct DOM manipulation which could be better handled with React patterns.

Consider using a library like react-draggable for better React integration and performance. If you need to keep the current implementation, at least optimize the event handling.


191-197: Improve error handling for browser/microphone compatibility

The error message for browser compatibility should be more helpful and guide users to compatible browsers.

        {/* 错误提示 */}
        {(!browserSupportsSpeechRecognition || !isMicrophoneAvailable) && (
          <div className="error-message">
            {!browserSupportsSpeechRecognition
-             ? "您的浏览器不支持语音识别功能,请使用Chrome浏览器"
+             ? "您的浏览器不支持语音识别功能,请使用Chrome、Edge或Firefox的最新版本"
              : "无法访问麦克风,请检查麦克风权限"}
          </div>
        )}

202-220: Button icon consistency

The stop button uses an SVG icon but other buttons use emoji. Consider using consistent icon types.

          {/* 暂停/恢复按钮 */}
          <button
            onClick={togglePause}
            className={`button pause-button ${isPaused ? "paused" : ""}`}
          >
-           <span>{isPaused ? "▶️ 恢复监听" : "⏸️ 暂停并发送"}</span>
+           <span>{isPaused ? "恢复监听" : "暂停并发送"}</span>
          </button>

          <button onClick={stopRecognition} className="button stop-button">
            <StopIcon />
            <span>结束对话</span>
          </button>

          <button onClick={resetTranscript} className="button clear-button">
-           <span>🗑️ 清空</span>
+           <span>清空</span>
          </button>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3809375 and 0db6b6b.

⛔ Files ignored due to path filters (3)
  • app/icons/wechat-qrcode-mock.svg is excluded by !**/*.svg
  • package-lock.json is excluded by !**/package-lock.json
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (31)
  • app/components/TensorFlow.module.scss (1 hunks)
  • app/components/TensorFlow.tsx (1 hunks)
  • app/components/WechatAuthor.module.scss (1 hunks)
  • app/components/WechatAuthor.tsx (1 hunks)
  • app/components/WechatLogin.module.scss (1 hunks)
  • app/components/WechatLogin.tsx (1 hunks)
  • app/components/auth-wrapper.tsx (1 hunks)
  • app/components/chat.module.scss (2 hunks)
  • app/components/chat.tsx (17 hunks)
  • app/components/home.module.scss (3 hunks)
  • app/components/home.tsx (4 hunks)
  • app/components/interview-overlay.scss (1 hunks)
  • app/components/interview-overlay.tsx (1 hunks)
  • app/components/sidebar.tsx (4 hunks)
  • app/constant.ts (3 hunks)
  • app/locales/index.ts (1 hunks)
  • app/pages/login.tsx (1 hunks)
  • app/store/access.ts (1 hunks)
  • app/store/config.ts (1 hunks)
  • docs/cloudflare-pages-es.md (0 hunks)
  • docs/cloudflare-pages-ja.md (0 hunks)
  • docs/cloudflare-pages-ko.md (0 hunks)
  • docs/faq-es.md (0 hunks)
  • docs/faq-ja.md (0 hunks)
  • docs/faq-ko.md (0 hunks)
  • docs/synchronise-chat-logs-es.md (0 hunks)
  • docs/synchronise-chat-logs-ja.md (0 hunks)
  • docs/synchronise-chat-logs-ko.md (0 hunks)
  • package.json (6 hunks)
  • tencentcloud-speech-sdk-js (1 hunks)
  • tsconfig.json (1 hunks)
💤 Files with no reviewable changes (9)
  • docs/cloudflare-pages-ko.md
  • docs/synchronise-chat-logs-es.md
  • docs/synchronise-chat-logs-ko.md
  • docs/synchronise-chat-logs-ja.md
  • docs/cloudflare-pages-es.md
  • docs/cloudflare-pages-ja.md
  • docs/faq-ko.md
  • docs/faq-es.md
  • docs/faq-ja.md
🧰 Additional context used
🧬 Code Graph Analysis (4)
app/components/sidebar.tsx (1)
app/components/WechatAuthor.tsx (1)
  • WechatAuthor (16-113)
app/components/home.tsx (1)
app/pages/login.tsx (1)
  • LoginPage (9-23)
app/pages/login.tsx (3)
app/store/access.ts (1)
  • useAccessStore (152-299)
app/utils.ts (1)
  • safeLocalStorage (377-435)
app/components/WechatLogin.tsx (1)
  • WechatLogin (23-174)
app/components/chat.tsx (2)
app/store/chat.ts (2)
  • useChatStore (232-931)
  • ChatMessage (57-66)
app/components/interview-overlay.tsx (1)
  • InterviewOverlay (14-224)
🔇 Additional comments (42)
tsconfig.json (1)

6-6:

❌ Incorrect review comment

Setting checkJs to false disables type checking for JavaScript files.

This change disables type checking for JavaScript files while retaining the ability to include them in the compilation. While this can help avoid TypeScript errors from JavaScript dependencies, it may reduce type safety for JavaScript code in the project.


🏁 Script executed:

#!/bin/bash
# Check for JavaScript files in the project that might now be untyped
echo "JavaScript files that will no longer have type checking:"
find app -name "*.js" | grep -v "node_modules"

Length of output: 169


I’ll check for JavaScript files across the entire repository to confirm whether disabling checkJs has any effect.


🏁 Script executed:

#!/bin/bash
echo "Listing all .js files outside node_modules, dist, and build directories:"
find . -type f -name '*.js' \
  -not -path './node_modules/*' \
  -not -path './dist/*' \
  -not -path './build/*'

Length of output: 364


I need to confirm your tsconfig.json settings for allowJs and your include/exclude patterns to determine if those JS files are actually being type-checked.


🏁 Script executed:

#!/bin/bash
echo "Relevant settings in tsconfig.json:"
grep -En '"(allowJs|include|exclude|files|checkJs)"' -n tsconfig.json || true

echo ""
echo "Full tsconfig.json for context:"
sed -n '1,200p' tsconfig.json

Length of output: 1090


JS files aren’t included in compilation – disabling checkJs has no effect
Your tsconfig.json explicitly includes only .ts and .tsx files (see lines 27–28), so .js files (like .prettierrc.js or those under public/) were never part of the type-checked set. Turning off checkJs therefore does not change any type-checking behavior.

Likely an incorrect or invalid review comment.

app/components/sidebar.tsx (2)

254-254: Integration of WeChat authentication at the top of the sidebar.

The WechatAuthor component is added at the very top of the sidebar, which is a good placement for authentication UI and user information.


35-35:

✅ Verification successful

Verify that all required components for WechatAuthor are properly implemented.

The addition of WechatAuthor to the sidebar UI depends on various other components and features like authentication flow and user state management.

Also applies to: 254-254


🏁 Script executed:

#!/bin/bash
# Check if all required files for WechatAuthor functionality exist
echo "Checking for related WeChat authentication files:"
find app/components -name "Wechat*"
find app/store -name "*access*"
grep -r "wechatLoggedIn" --include="*.ts" --include="*.tsx" app/

Length of output: 997


WechatAuthor dependencies verified

All required components and authentication integrations for WechatAuthor are present and correctly wired up:

  • app/components/WechatAuthor.tsx & its stylesheet
  • app/components/WechatLogin.tsx & its stylesheet
  • app/store/access.ts (wechatLoggedIn flag)
  • Usage of accessStore.wechatLoggedIn in auth-wrapper.tsx and login.tsx

No further changes needed.

package.json (2)

9-9: The build script has been updated to clean previous build artifacts.

The addition of rimraf commands to remove .next directory and next-env.d.ts file before building ensures a clean build environment, which is a good practice to avoid potential conflicts from previous builds.


29-29: New dependencies align well with the PR objectives.

The added dependencies support the new features described in the PR:

  • @tensorflow/tfjs: For voice recognition features
  • crypto-js: For encryption in the WeChat authentication flow
  • react-speech-recognition: For speech-to-text functionality
  • react-hot-toast: For toast notifications in the UI
  • tencentcloud-sdk-nodejs-cvm: For Tencent Cloud integration with WeChat

All type definitions are also appropriately included as devDependencies.

Also applies to: 34-34, 40-40, 50-50, 53-53, 62-62, 73-73, 81-81, 95-95

app/components/home.tsx (1)

33-33: Login route integration is well implemented.

The import of LoginPage and its route definition at Path.Login correctly integrates the new login functionality into the routing system.

Also applies to: 212-212

app/pages/login.tsx (2)

9-23: LoginPage component handles authentication flow appropriately.

The component correctly:

  1. Checks if the user is already logged in using both local storage and the access store
  2. Redirects authenticated users to the chat page
  3. Renders the WeChat login component for unauthenticated users

This implementation ensures users don't have to login again if they already have valid credentials.


16-20:

✅ Verification successful

Verify dependency on both localStorage and accessStore flags.

The login check requires both conditions to be true:

if (userInfoStr && accessStore.wechatLoggedIn) {
  navigate(Path.Chat);
}

Ensure this dual condition is intentional. If a user has localStorage data but the wechatLoggedIn flag is false (which could happen after a page refresh if the store isn't properly persisted), they might need to login again unnecessarily.


🏁 Script executed:

#!/bin/bash
# Check if the access store is properly persisted with the wechatLoggedIn flag
rg -A 10 "wechatLoggedIn" --glob "*.ts" --glob "*.tsx"

# Check if there's any persistence mechanism for the access store
rg "createPersistStore|persist|StoreKey.Access" --glob "*.ts"

Length of output: 5996


WeChat login flag persistence verified

The wechatLoggedIn flag is persisted via Zustand’s persist middleware in app/store/access.ts (using IndexedDB storage as configured in app/utils/store.ts), so it remains accurate after page reloads. The dual check in app/pages/login.tsx is intentional and will not force unnecessary logins.

app/components/auth-wrapper.tsx (2)

10-31: AuthWrapper implementation provides proper route protection.

The component correctly:

  1. Identifies if the current path requires authentication
  2. Checks user login status from both localStorage and the access store
  3. Redirects unauthenticated users to the login page
  4. Updates on both location changes and login state changes

This is a clean implementation of route protection that doesn't interfere with the rendering of child components.


8-8:

❓ Verification inconclusive

Verify if Path.Home should be publicly accessible.

Path.Home is included in PUBLIC_PATHS, meaning it can be accessed without authentication. Based on the PR description which emphasizes WeChat login functionality, you may want to verify if this is the intended behavior.

If home page content should be protected behind login, consider removing Path.Home from PUBLIC_PATHS.

Also applies to: 16-28


🏁 Script executed:

#!/bin/bash
# Check how Path.Home is used elsewhere in the codebase
rg "Path\.Home" --glob "*.ts" --glob "*.tsx"

# Look at how the Home path is defined
rg "enum Path" --glob "*.ts" -A 20

Length of output: 1958


I’d like to inspect how PUBLIC_PATHS is used in the auth wrapper logic to confirm whether / really bypasses authentication. Can you show the surrounding code in app/components/auth-wrapper.tsx? For example:

rg "const PUBLIC_PATHS" -A20 -B10 app/components/auth-wrapper.tsx
app/components/WechatAuthor.module.scss (6)

1-5: Well-structured container styling.

The container uses relative positioning which is appropriate for positioning the dropdown menu, with good spacing and border styling for visual separation in the sidebar.


7-11: Good use of flexbox for avatar alignment.

The avatar container uses flexbox properly for alignment and includes the cursor:pointer to indicate interactivity.


13-20: Appropriate avatar styling.

The avatar styling includes proper dimensions, border-radius for circular shape, and object-fit:cover to handle different image aspect ratios correctly.


22-25: Good text overflow handling for user information.

The userInfo section properly handles overflow with ellipsis for the nickname, ensuring long text doesn't break the layout.

Also applies to: 27-34


42-52: Well-positioned dropdown menu.

The menu has appropriate absolute positioning relative to the container with proper z-index and box-shadow. Good use of CSS variables for theming consistency.


54-64: Interactive menu item styling.

The menu items have appropriate padding, font sizing, and a smooth transition effect for hover states. The nested &:hover selector is used correctly for the hover effect.

app/components/WechatLogin.module.scss (5)

1-7: Good container layout for login screen.

The container uses flexbox to center content vertically and horizontally in the viewport, with appropriate background color.


36-44: Good QR code container implementation.

The QR code container is well structured with appropriate dimensions and positioning. The wrapper uses relative positioning which works well with the overlay. The hover functionality is implemented correctly.

Also applies to: 67-80, 82-89


91-109: Well-implemented overlay with transition.

The QR code overlay has proper positioning, background color with transparency, and smooth opacity transition for hover effects.


46-65: Good loading and status indicators.

The loading wrapper and status icons are well-styled with proper animations and color indicators for different states (loading, success, error).

Also applies to: 127-146


168-186: Well-implemented button states and animation.

The refresh button has appropriate styling for normal, hover, and disabled states with smooth transitions. The spin animation for loading is defined correctly with proper keyframes.

Also applies to: 188-195

app/components/home.module.scss (3)

11-17: Ensure consistent viewport sizing.

The change to use viewport units (vw/vh) for container sizing is appropriate for a full-screen application, but be aware this could affect the display on different devices and orientations.

Test the application on various screen sizes and orientations to ensure the layout remains consistent and usable.

Also applies to: 22-24


30-43: Good adaptation of media queries.

The tight-container media query has been properly updated to match the new viewport-based dimensions, maintaining consistency across different screen sizes.


115-122: Consistent mobile styles applied.

The container styles for mobile view have been updated to match the new viewport-based dimensions, which is good for maintaining consistency.

app/components/WechatAuthor.tsx (4)

1-8: Component dependencies look good

The necessary imports are correctly included, and the component makes use of appropriate hooks and utilities.


9-14: Interface definition is properly structured

The interface properly defines the structure for WeChat user information with all required fields.


37-48: Event listener cleanup is properly implemented

The component correctly sets up and cleans up the document event listener for detecting clicks outside the menu.


70-81: Login state check is properly implemented

The component appropriately checks the login state and renders a login prompt when needed.

app/components/WechatLogin.tsx (3)

13-21: Login status enum is well-defined

The enum provides clear states for the login process flow.


31-40: Initial loading effect is properly implemented

The effect correctly sets up and cleans up the timeout for the initial loading state.


79-85: QR Code refresh function is properly implemented

The refreshQRCode function correctly handles the state transitions.

app/components/TensorFlow.tsx (2)

1-5:

✅ Verification successful

Verify TensorFlow.js dependency

The component is importing TensorFlow.js but the dependency might not be declared in package.json. Ensure it's properly added to the project.


🏁 Script executed:

#!/bin/bash
# Check if TensorFlow.js is in package.json
grep -E "@tensorflow/tfjs" package.json || echo "TensorFlow.js dependency not found in package.json"

Length of output: 74


TensorFlow.js Dependency Confirmed

The package.json already declares

"@tensorflow/tfjs": "^4.22.0"

No further action needed.


244-251: 🛠️ Refactor suggestion

Inefficient array merging

The current implementation for merging audio data is inefficient and could cause performance issues with large arrays.

      // 合并所有音频块
-     const mergedData = new Float32Array(
-       audioData.reduce((acc, chunk) => acc + chunk.length, 0),
-     );
-     let offset = 0;
-     for (const chunk of audioData) {
-       mergedData.set(chunk, offset);
-       offset += chunk.length;
-     }
+     // Calculate total length first to avoid multiple iterations
+     const totalLength = audioData.reduce((acc, chunk) => acc + chunk.length, 0);
+     const mergedData = new Float32Array(totalLength);
+     
+     // Use a single pass with reduce to set data and track offset
+     audioData.reduce((offset, chunk) => {
+       mergedData.set(chunk, offset);
+       return offset + chunk.length;
+     }, 0);

Likely an incorrect or invalid review comment.

app/components/interview-overlay.tsx (2)

157-163: Cleanup of event listeners is properly implemented

The component correctly removes event listeners on unmount to prevent memory leaks.


1-12:

✅ Verification successful

Component dependencies look good but need verification

The component uses react-speech-recognition which needs to be verified in the package.json.


🏁 Script executed:

#!/bin/bash
# Check if react-speech-recognition is in package.json
grep -E "react-speech-recognition" package.json || echo "react-speech-recognition dependency not found in package.json"

Length of output: 138


✅ react-speech-recognition dependency confirmed

  • Verified in package.json:
    • "react-speech-recognition": "^4.0.0"
    • "@types/react-speech-recognition": "^3.9.6"

No further changes needed.

app/components/chat.tsx (7)

14-16: Avoid mixing lodash and lodash-es to keep bundle size lean

The file now imports from both lodash (debounce) and lodash-es (isEmpty). Tree-shaking only works reliably with lodash-es, so pulling in the Classic build will force the whole library (≈70 KB) into the bundle.

-import { debounce } from "lodash";
+import { debounce } from "lodash-es";

(or reuse the already-installed useDebouncedCallback).

This keeps the compiled bundle smaller and avoids duplicate code paths.
[ suggest_essential_refactor, request_verification ]


459-509: ChatActionVoice duplicates ChatAction – factor out the common code

ChatActionVoice copies almost 100 % of ChatAction’s logic, differing only in
• the root CSS class,
• the default full width (32 vs 16).

The duplication increases maintenance cost and violates DRY.
A tiny prop or wrapper suffices:

- export function ChatActionVoice( … ) {
-   // identical body …
- }
+export function ChatActionVoice(props: Props & { variant?: "voice" }) {
+  const className =
+    props.variant === "voice"
+      ? styles["chat-input-action-voice"]
+      : styles["chat-input-action"];
+
+}

Then call ChatActionVoice with variant="voice" instead of creating a fork.

Also remove the console.log in useLayoutEffect; it will spam the console on every render.
[ suggest_essential_refactor ]


524-525: Missing prop drilling check & uncoupled icon/text

ChatActions receives setShowOverlay, but showOverlay itself is never consumed inside the component (comment on 531 hints an abandoned ref). Ensure you still need the ref and remove it if unused to keep the component clean.

In addition, the voice button uses a generic “menu” icon and the label “开始”, which is not self-explanatory. Consider a microphone icon or “语音” to improve usability.
[ suggest_nitpick ]

Also applies to: 857-863


1017-1056: useScrollToBottom is re-implemented – risk of conflicting hooks

A hook with the same name already exists earlier in the project. Re-declaring it here:

  1. Inflates bundle size.
  2. Causes type / behaviour divergence over time.
  3. Makes grepping for the hook ambiguous.

Prefer importing the existing hook (or moving the single definition to a shared util) instead of redeclaring it locally.
[ suggest_essential_refactor ]


1068-1071: Remove unused showOverlayRef

showOverlayRef is created but never read after assignment, producing dead code and confusing intent.

-const showOverlayRef = useRef(showOverlay);

Safe to delete.
[ suggest_nitpick ]


1757-1775: Debounced submit recreates on every render – messages may be dropped

toastShowDebounce is rebuilt each render, resetting its internal timer.
If the overlay closes within the 500 ms debounce window, the pending submit disappears.

Wrap the debounced function in useCallback/useRef so it stays stable:

const toastShowDebounce = useRef(debounce(toastShow, 500)).current;

or switch to useDebouncedCallback, which already returns a memoised callback.
[ flag_critical_issue ]


2274-2284: Only one global <Toaster> should be mounted

Placing Toaster inside every _Chat instance risks multiple overlapping portals and duplicated toasts when the user opens more than one chat.

Move the component to a top-level layout (e.g., _app.tsx or App.tsx) and keep it singleton.

-<_Chat>
-
-  <Toaster … />
-</_Chat>
+<_Chat> … </_Chat>

Then render <Toaster /> once at the root.
[ suggest_essential_refactor ]

@@ -0,0 +1 @@
Subproject commit 34950220a00cd1fef7f4c0a17755804c9d929f87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the submodule commit reference
Ensure that the commit hash 34950220a00cd1fef7f4c0a17755804c9d929f87 exists in the tencentcloud-speech-sdk-js repository and that the .gitmodules file has been updated accordingly to point at the correct remote URL. This prevents broken submodule pointers.

Run the following script to validate the submodule reference:


🏁 Script executed:

#!/bin/bash
# Verify that the specified submodule commit exists
git submodule sync
git submodule update --init tencentcloud-speech-sdk-js
cd tencentcloud-speech-sdk-js
# Fetch the necessary refs
git fetch --depth=1
# Check for the commit object
if git cat-file -e 34950220a00cd1fef7f4c0a17755804c9d929f87^{commit}; then
  echo "✅ Submodule commit found"
else
  echo "❌ Error: Submodule commit not found"
  exit 1
fi

Length of output: 546


#!/bin/bash
# Check for .gitmodules and its contents, and submodule config
echo "=== Checking .gitmodules ==="
if [ -f .gitmodules ]; then
  echo ".gitmodules contents:"
  cat .gitmodules
else
  echo "No .gitmodules file found"
fi

echo
echo "=== Listing submodule config in .git/config ==="
git config --get-regexp 'submodule\..*' || echo "No submodule entries in .git/config"

echo
echo "=== Verifying directory presence ==="
if [ -d tencentcloud-speech-sdk-js ]; then
  echo "Directory 'tencentcloud-speech-sdk-js' exists"
else
  echo "Directory 'tencentcloud-speech-sdk-js' does not exist"
fi

Add and verify the tencentcloud-speech-sdk-js submodule entry in .gitmodules

The update failed because there’s no submodule URL for tencentcloud-speech-sdk-js, so Git can’t fetch or verify the commit hash.

• In .gitmodules, add an entry for the submodule, for example:

[submodule "tencentcloud-speech-sdk-js"]
-  # missing
+  path = tencentcloud-speech-sdk-js
+  url  = <remote-repository-url-for-tencentcloud-speech-sdk-js>

• Run git submodule sync and git submodule update --init --depth=1 tencentcloud-speech-sdk-js to pull down the code.
• Re-run the commit verification script; it should now find 34950220a00cd1fef7f4c0a17755804c9d929f87.

Please update the .gitmodules file with the correct remote URL and ensure the specified commit exists.

Committable suggestion skipped: line range outside the PR's diff.

@@ -45,7 +45,7 @@ export const DEFAULT_CONFIG = {
avatar: "1f603",
fontSize: 14,
fontFamily: "",
theme: Theme.Auto as Theme,
theme: Theme.Dark as Theme,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider user preferences when changing default theme

Changing the default theme from Theme.Auto to Theme.Dark removes the automatic theme detection based on user system preferences. While this aligns with your new dark-themed UI components, this might affect users who prefer light themes.

Consider either:

  1. Keeping auto-detection as default (Theme.Auto)
  2. Adding a first-launch preference selector
  3. Ensuring theme toggle is prominently accessible in the UI

🏁 Script executed:

#!/bin/bash
# Verify if there's an easily accessible theme toggle in the UI
echo "Checking for theme toggle UI component..."
grep -r "theme" --include="*.tsx" app/components/ | grep -i "toggle\|switch\|select"

Length of output: 220


🏁 Script executed:

#!/bin/bash
# Show context around the theme switch comment to confirm if there's a UI toggle
grep -R -n -C5 "switch themes" app/components/chat.tsx

Length of output: 484


🏁 Script executed:

#!/bin/bash
# Check for invocation of nextTheme to confirm a UI toggle exists
grep -R "nextTheme" -n app/components
grep -R "onClick.*nextTheme" -n app/components

Length of output: 411


Theme toggle exists—revisit default-theme choice

We’ve confirmed there’s a theme-switch control in the chat UI (app/components/chat.tsx:651), so suggestion #3 is already addressed. However, switching the default in config.ts (line 48) from Theme.Auto to Theme.Dark still disables automatic system‐preference detection on first launch. You may want to:

• Revert the default back to Theme.Auto in app/store/config.ts (line 48)
• Or introduce a first-launch theme selector before applying Theme.Dark

app/store/config.ts

-  theme: Theme.Dark as Theme,
+  theme: Theme.Auto as Theme,  // restore auto-detection or prompt on first launch

Comment on lines +145 to +147
// 添加微信登录状态
wechatLoggedIn: false,
accessToken: "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement secure token handling for WeChat authentication

The new WeChat authentication properties are a good addition, but there's no apparent token validation, expiration handling, or secure storage strategy. Authentication tokens should be properly managed for security.

Consider implementing:

  1. Token validation
  2. Expiration handling/refresh logic
  3. Secure storage methods
+  // 添加微信登录状态
+  wechatLoggedIn: false,
+  accessToken: "",
+  // Add token expiration tracking
+  tokenExpiration: 0,

Also, add methods to validate token status:

isTokenValid() {
  const { accessToken, tokenExpiration } = get();
  return accessToken && tokenExpiration > Date.now();
},

logoutIfTokenExpired() {
  if (!this.isTokenValid()) {
    set(() => ({ 
      wechatLoggedIn: false, 
      accessToken: "",
      tokenExpiration: 0 
    }));
    return true;
  }
  return false;
}

Comment on lines +102 to +159
.chat-input-action-voice {
// display: inline-flex;
// border-radius: 20px;
// font-size: 12px;
// background-color: var(--white);
// color: var(--black);
// border: var(--border-in-light);
// padding: 4px 10px;
// animation: slide-in ease 0.3s;
// box-shadow: var(--card-shadow);
// transition: width ease 0.3s;
// align-items: center;
// height: 16px;
// width: var(--icon-width);
// overflow: hidden;

display: inline-flex;
border-radius: 20px;
font-size: 12px;
background-color: var(--white);
color: var(--black);
border: var(--border-in-light);
padding: 4px 10px;
box-shadow: var(--card-shadow);
align-items: center;
height: 16px;
width: var(--full-width); /* 使用全宽度 */
// .text {
// white-space: nowrap;
// padding-left: 5px;
// opacity: 0;
// transform: translateX(-5px);
// transition: all ease 0.3s;
// pointer-events: none;
// }

// &:hover {
// --delay: 0.5s;
// width: var(--full-width);
// transition-delay: var(--delay);


// }
.text {
white-space: nowrap;
padding-left: 5px;
opacity: 1; /* 确保文本始终可见 */
transform: translateX(0); /* 移除初始偏移 */
transition: none; /* 移除过渡效果 */
}

.text,
.icon {
display: flex;
align-items: center;
justify-content: center;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor duplicated styling code for better maintainability

The .chat-input-action-voice class largely duplicates .chat-input-action with minor differences. This creates maintenance issues if base styles need to change.

Use CSS inheritance by creating shared base styles and only override the specific differences:

-  .chat-input-action-voice {
-    // display: inline-flex;
-    // border-radius: 20px;
-    // font-size: 12px;
-    // background-color: var(--white);
-    // color: var(--black);
-    // border: var(--border-in-light);
-    // padding: 4px 10px;
-    // animation: slide-in ease 0.3s;
-    // box-shadow: var(--card-shadow);
-    // transition: width ease 0.3s;
-    // align-items: center;
-    // height: 16px;
-    // width: var(--icon-width);
-    // overflow: hidden;
-
-    display: inline-flex;
-    border-radius: 20px;
-    font-size: 12px;
-    background-color: var(--white);
-    color: var(--black);
-    border: var(--border-in-light);
-    padding: 4px 10px;
-    box-shadow: var(--card-shadow);
-    align-items: center;
-    height: 16px;
-    width: var(--full-width); /* 使用全宽度 */
-    // .text {
-    //   white-space: nowrap;
-    //   padding-left: 5px;
-    //   opacity: 0;
-    //   transform: translateX(-5px);
-    //   transition: all ease 0.3s;
-    //   pointer-events: none;
-    // }
-
-    // &:hover {
-    //   --delay: 0.5s;
-    //   width: var(--full-width);
-    //   transition-delay: var(--delay);
-
-
-    // }
-    .text {
-      white-space: nowrap;
-      padding-left: 5px;
-      opacity: 1; /* 确保文本始终可见 */
-      transform: translateX(0); /* 移除初始偏移 */
-      transition: none; /* 移除过渡效果 */
-    }
-
-    .text,
-    .icon {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-  }
+  .chat-input-action-voice {
+    @extend .chat-input-action;
+    animation: none;
+    transition: none;
+    width: var(--full-width);
+    
+    .text {
+      opacity: 1;
+      transform: translateX(0);
+      transition: none;
+    }
+    
+    &:hover {
+      width: var(--full-width);
+      
+      .text {
+        opacity: 1;
+        transform: translateX(0);
+      }
+    }
+  }

Also, clean up commented-out code and translate Chinese comments to English for consistency.

Comment on lines +1 to +179
}

.message {
font-size: 16px;
text-align: center;
margin: 0;
}

.visualizerContainer {
width: 100%;
height: 150px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 5px;
overflow: hidden;
}

.controlsContainer {
display: flex;
justify-content: space-between;
margin-bottom: 20px;

@media (max-width: 600px) {
flex-direction: column;
}
}

.trainingControls,
.recognitionControls {
display: flex;
flex-direction: column;
width: 48%;

@media (max-width: 600px) {
width: 100%;
margin-bottom: 20px;
}

h3 {
margin-bottom: 10px;
font-size: 18px;
}
}

.button {
padding: 10px 15px;
margin-bottom: 10px;
border: none;
border-radius: 5px;
background-color: #2196f3;
color: white;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;

&:hover:not(:disabled) {
background-color: #1976d2;
}

&:disabled {
background-color: #cccccc;
color: #666666;
cursor: not-allowed;
}
}

.resultContainer {
margin-top: 20px;
padding: 15px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 5px;
}

.scoreBar {
width: 100%;
height: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}

.scoreIndicator {
height: 100%;
background: linear-gradient(to right, #f44336, #ffeb3b, #4caf50);
border-radius: 10px;
transition: width 0.5s ease-in-out;
}

.scoreValue {
text-align: center;
font-size: 16px;
font-weight: bold;
}

@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Add RTL support for internationalization

The component doesn't account for right-to-left (RTL) languages, which might be important for internationalization.

Add RTL support using CSS logical properties:

.statusDot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
-  margin-right: 10px;
+  margin-inline-end: 10px; /* Supports both LTR and RTL */
}

Apply similar logical property replacements for other directional properties throughout the file.


🏁 Script executed:

#!/bin/bash
# Check if the app has any RTL support or language settings
echo "Checking for RTL support in the application..."
grep -r "dir=\"rtl\"" --include="*.tsx" --include="*.jsx" app/
grep -r "direction:" --include="*.scss" --include="*.css" app/
grep -r "rtl" --include="package.json" .

Length of output: 3816


Use CSS logical properties for RTL support in TensorFlow.module.scss

To ensure this component adapts when direction: rtl is applied elsewhere in the app (e.g., home.module.scss), replace the hard-coded margin-right with a logical property:

• In app/components/TensorFlow.module.scss

 .statusDot {
   width: 12px;
   height: 12px;
   border-radius: 50%;
-  margin-right: 10px;
+  margin-inline-end: 10px; /* supports both LTR and RTL */
 }

No other left/right offsets exist in this file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.voiceRecognitionContainer {
display: flex;
flex-direction: column;
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #1e1e1e;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
color: #ffffff;
}
.title {
text-align: center;
margin-bottom: 20px;
font-size: 24px;
}
.statusContainer {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.statusIndicator {
display: flex;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
padding: 8px 16px;
border-radius: 20px;
margin-bottom: 10px;
}
.statusDot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 10px;
&.idle {
background-color: #888888;
}
&.recording {
background-color: #ff9800;
animation: pulse 1.5s infinite;
}
&.training {
background-color: #2196f3;
animation: pulse 1.5s infinite;
}
&.recognizing {
background-color: #9c27b0;
animation: pulse 1.5s infinite;
}
&.trained {
background-color: #4caf50;
}
&.matched {
background-color: #4caf50;
}
&.not_matched {
background-color: #f44336;
}
&.error {
background-color: #f44336;
}
}
.statusText {
font-size: 14px;
}
.message {
font-size: 16px;
text-align: center;
margin: 0;
}
.visualizerContainer {
width: 100%;
height: 150px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 5px;
overflow: hidden;
}
.controlsContainer {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
@media (max-width: 600px) {
flex-direction: column;
}
}
.trainingControls,
.recognitionControls {
display: flex;
flex-direction: column;
width: 48%;
@media (max-width: 600px) {
width: 100%;
margin-bottom: 20px;
}
h3 {
margin-bottom: 10px;
font-size: 18px;
}
}
.button {
padding: 10px 15px;
margin-bottom: 10px;
border: none;
border-radius: 5px;
background-color: #2196f3;
color: white;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
&:hover:not(:disabled) {
background-color: #1976d2;
}
&:disabled {
background-color: #cccccc;
color: #666666;
cursor: not-allowed;
}
}
.resultContainer {
margin-top: 20px;
padding: 15px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 5px;
}
.scoreBar {
width: 100%;
height: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.scoreIndicator {
height: 100%;
background: linear-gradient(to right, #f44336, #ffeb3b, #4caf50);
border-radius: 10px;
transition: width 0.5s ease-in-out;
}
.scoreValue {
text-align: center;
font-size: 16px;
font-weight: bold;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
.statusDot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-inline-end: 10px; /* supports both LTR and RTL */
&.idle {
background-color: #888888;
}
&.recording {
background-color: #ff9800;
animation: pulse 1.5s infinite;
}
&.training {
background-color: #2196f3;
animation: pulse 1.5s infinite;
}
&.recognizing {
background-color: #9c27b0;
animation: pulse 1.5s infinite;
}
&.trained {
background-color: #4caf50;
}
&.matched {
background-color: #4caf50;
}
&.not_matched {
background-color: #f44336;
}
&.error {
background-color: #f44336;
}
}

💡 Verification agent

🧩 Analysis chain

Improve theming in TensorFlow component with CSS variables

The TensorFlow voice recognition styles use hardcoded color values instead of CSS variables, which can cause inconsistencies with the application's theme system.

Replace hardcoded color values with theme variables:

.voiceRecognitionContainer {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
-  background-color: #1e1e1e;
+  background-color: var(--dark-background, #1e1e1e);
  border-radius: 10px;
-  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
+  box-shadow: var(--card-shadow, 0 5px 20px rgba(0, 0, 0, 0.3));
-  color: #ffffff;
+  color: var(--white, #ffffff);
}

Similarly, update status colors and other hardcoded values to use the app's theme variables throughout the file.


🏁 Script executed:

#!/bin/bash
# Check for theme variables in the application
echo "Looking for theme variables in style files..."
grep -r "var(--" --include="*.scss" --include="*.css" app/styles/

Length of output: 8503


🏁 Script executed:

#!/bin/bash
# Display the first 50 lines of globals.scss to inspect CSS variable definitions
head -n 50 app/styles/globals.scss

Length of output: 1001


Use CSS theme variables in TensorFlow.module.scss

The app already defines theme variables in globals.scss (--white, --black, --gray, --primary, --second, --hover-color, --bar-color, --card-shadow, etc.). Replace hard-coded colors in your component with those variables:

 .voiceRecognitionContainer {
-  background-color: #1e1e1e;
-  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
-  color: #ffffff;
+  background-color: var(--white);
+  box-shadow: var(--card-shadow);
+  color: var(--black);
 }

 .statusIndicator {
-  background-color: rgba(0, 0, 0, 0.5);
+  background-color: var(--bar-color);
 }

• For status dots, extract each state’s color into globals.scss (e.g. --status-idle, --status-recording, etc.) and reference them with var(--status-idle, #888888), var(--status-recording, #ff9800), etc.
• Audit the rest of this file and swap all hardcoded values for the corresponding CSS variables to ensure consistency with the app’s light/dark themes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.voiceRecognitionContainer {
display: flex;
flex-direction: column;
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #1e1e1e;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
color: #ffffff;
}
.title {
text-align: center;
margin-bottom: 20px;
font-size: 24px;
}
.statusContainer {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.statusIndicator {
display: flex;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
padding: 8px 16px;
border-radius: 20px;
margin-bottom: 10px;
}
.statusDot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 10px;
&.idle {
background-color: #888888;
}
&.recording {
background-color: #ff9800;
animation: pulse 1.5s infinite;
}
&.training {
background-color: #2196f3;
animation: pulse 1.5s infinite;
}
&.recognizing {
background-color: #9c27b0;
animation: pulse 1.5s infinite;
}
&.trained {
background-color: #4caf50;
}
&.matched {
background-color: #4caf50;
}
&.not_matched {
background-color: #f44336;
}
&.error {
background-color: #f44336;
}
}
.statusText {
font-size: 14px;
}
.message {
font-size: 16px;
text-align: center;
margin: 0;
}
.visualizerContainer {
width: 100%;
height: 150px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 5px;
overflow: hidden;
}
.controlsContainer {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
@media (max-width: 600px) {
flex-direction: column;
}
}
.trainingControls,
.recognitionControls {
display: flex;
flex-direction: column;
width: 48%;
@media (max-width: 600px) {
width: 100%;
margin-bottom: 20px;
}
h3 {
margin-bottom: 10px;
font-size: 18px;
}
}
.button {
padding: 10px 15px;
margin-bottom: 10px;
border: none;
border-radius: 5px;
background-color: #2196f3;
color: white;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
&:hover:not(:disabled) {
background-color: #1976d2;
}
&:disabled {
background-color: #cccccc;
color: #666666;
cursor: not-allowed;
}
}
.resultContainer {
margin-top: 20px;
padding: 15px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 5px;
}
.scoreBar {
width: 100%;
height: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.scoreIndicator {
height: 100%;
background: linear-gradient(to right, #f44336, #ffeb3b, #4caf50);
border-radius: 10px;
transition: width 0.5s ease-in-out;
}
.scoreValue {
text-align: center;
font-size: 16px;
font-weight: bold;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
// app/components/TensorFlow.module.scss
.voiceRecognitionContainer {
display: flex;
flex-direction: column;
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
- background-color: #1e1e1e;
+ background-color: var(--white);
border-radius: 10px;
- box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
+ box-shadow: var(--card-shadow);
- color: #ffffff;
+ color: var(--black);
}
.statusIndicator {
display: flex;
align-items: center;
- background-color: rgba(0, 0, 0, 0.5);
+ background-color: var(--bar-color);
padding: 8px 16px;
border-radius: 20px;
margin-bottom: 10px;
}

Comment on lines +55 to +69
// 存储登录信息
const mockUserInfo = {
id: "wx_" + Math.floor(Math.random() * 1000000),
nickname: "微信用户",
avatar: "https://placekitten.com/100/100", // 模拟头像
accessToken: "mock_token_" + Date.now(),
};

storage.setItem("wechat_user_info", JSON.stringify(mockUserInfo));

// 更新访问状态
accessStore.update((access) => {
access.accessToken = mockUserInfo.accessToken;
access.wechatLoggedIn = true;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Hardcoded placeholder image in production code

Using placekitten.com for avatar images isn't appropriate for production code. Replace with a local placeholder image.

        // 存储登录信息
        const mockUserInfo = {
          id: "wx_" + Math.floor(Math.random() * 1000000),
          nickname: "微信用户",
-         avatar: "https://placekitten.com/100/100", // 模拟头像
+         avatar: "/assets/default_avatar.png", // 使用本地默认头像
          accessToken: "mock_token_" + Date.now(),
        };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 存储登录信息
const mockUserInfo = {
id: "wx_" + Math.floor(Math.random() * 1000000),
nickname: "微信用户",
avatar: "https://placekitten.com/100/100", // 模拟头像
accessToken: "mock_token_" + Date.now(),
};
storage.setItem("wechat_user_info", JSON.stringify(mockUserInfo));
// 更新访问状态
accessStore.update((access) => {
access.accessToken = mockUserInfo.accessToken;
access.wechatLoggedIn = true;
});
// 存储登录信息
const mockUserInfo = {
id: "wx_" + Math.floor(Math.random() * 1000000),
nickname: "微信用户",
avatar: "/assets/default_avatar.png", // 使用本地默认头像
accessToken: "mock_token_" + Date.now(),
};
storage.setItem("wechat_user_info", JSON.stringify(mockUserInfo));
// 更新访问状态
accessStore.update((access) => {
access.accessToken = mockUserInfo.accessToken;
access.wechatLoggedIn = true;
});

Comment on lines +111 to +116
{/* <QRCodeImage className={styles.qrcode} /> */}
<div className={styles.qrcodeOverlay}>
<p>点击模拟扫码</p>
</div>
<p className={styles.qrcodeHint}>请使用微信扫描二维码登录</p>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing actual QR code implementation

The component is using a placeholder div instead of the actual QR code svg that was commented out in the imports.

Either implement a proper QR code component or use a placeholder image:

            <div className={styles.qrcodeWrapper} onClick={simulateLogin}>
-              {/* <QRCodeImage className={styles.qrcode} /> */}
+              <div className={styles.mockQrcode}>
+                <img src="/assets/mock-qrcode.png" alt="QR Code" className={styles.qrcode} />
+              </div>
              <div className={styles.qrcodeOverlay}>
                <p>点击模拟扫码</p>
              </div>
              <p className={styles.qrcodeHint}>请使用微信扫描二维码登录</p>
            </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* <QRCodeImage className={styles.qrcode} /> */}
<div className={styles.qrcodeOverlay}>
<p>点击模拟扫码</p>
</div>
<p className={styles.qrcodeHint}>请使用微信扫描二维码登录</p>
</div>
<div className={styles.qrcodeWrapper} onClick={simulateLogin}>
<div className={styles.mockQrcode}>
<img
src="/assets/mock-qrcode.png"
alt="QR Code"
className={styles.qrcode}
/>
</div>
<div className={styles.qrcodeOverlay}>
<p>点击模拟扫码</p>
</div>
<p className={styles.qrcodeHint}>请使用微信扫描二维码登录</p>
</div>

Comment on lines +181 to +191
stopRecording();
trainVoiceprint();
}, 5000);
}
} catch (error) {
console.error("开始录音失败:", error);
setStatus(VoiceRecognitionStatus.ERROR);
setMessage("无法访问麦克风,请检查权限");
}
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential race condition in startRecording

The function doesn't handle the case where the user might rapidly start and stop recording, leading to race conditions.

  // 开始录音
  const startRecording = async (isTraining: boolean = false) => {
    try {
-     if (isRecording) return;
+     // If already recording, first stop current recording
+     if (isRecording) {
+       await stopRecording();
+     }

      // 重置录音数据
      recordedChunksRef.current = [];

      // 请求麦克风权限
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      mediaStreamRef.current = stream;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +40 to +72
const audioContextRef = useRef<AudioContext | null>(null);
const analyserRef = useRef<AnalyserNode | null>(null);
const mediaStreamRef = useRef<MediaStream | null>(null);
const recordedChunksRef = useRef<Float32Array[]>([]);
const modelRef = useRef<tf.LayersModel | null>(null);
const voiceprintRef = useRef<Float32Array | null>(null);
const animationFrameRef = useRef<number | null>(null);

// 初始化
useEffect(() => {
// 检查是否有保存的声纹模型
const savedVoiceprint = localStorage.getItem("userVoiceprint");
if (savedVoiceprint) {
try {
voiceprintRef.current = new Float32Array(JSON.parse(savedVoiceprint));
setIsTrained(true);
setStatus(VoiceRecognitionStatus.TRAINED);
setMessage("已加载保存的声纹模型");
} catch (error) {
console.error("加载保存的声纹模型失败:", error);
}
}

// 加载TensorFlow模型
loadModel();

return () => {
stopRecording();
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use TensorFlow memory management for cleanup

The component doesn't properly dispose of TensorFlow resources in the cleanup function.

TensorFlow.js can leak memory if tensors aren't properly disposed. Add proper cleanup:

  useEffect(() => {
    // 检查是否有保存的声纹模型
    const savedVoiceprint = localStorage.getItem("userVoiceprint");
    if (savedVoiceprint) {
      try {
        voiceprintRef.current = new Float32Array(JSON.parse(savedVoiceprint));
        setIsTrained(true);
        setStatus(VoiceRecognitionStatus.TRAINED);
        setMessage("已加载保存的声纹模型");
      } catch (error) {
        console.error("加载保存的声纹模型失败:", error);
      }
    }

    // 加载TensorFlow模型
    loadModel();

    return () => {
      stopRecording();
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
      }
+     // Dispose of TensorFlow model to prevent memory leaks
+     if (modelRef.current) {
+       modelRef.current.dispose();
+       modelRef.current = null;
+     }
+     // Clear any remaining tensors in memory
+     tf.disposeVariables();
+     tf.engine().endScope();
+     tf.engine().disposeVariables();
    };
  }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const audioContextRef = useRef<AudioContext | null>(null);
const analyserRef = useRef<AnalyserNode | null>(null);
const mediaStreamRef = useRef<MediaStream | null>(null);
const recordedChunksRef = useRef<Float32Array[]>([]);
const modelRef = useRef<tf.LayersModel | null>(null);
const voiceprintRef = useRef<Float32Array | null>(null);
const animationFrameRef = useRef<number | null>(null);
// 初始化
useEffect(() => {
// 检查是否有保存的声纹模型
const savedVoiceprint = localStorage.getItem("userVoiceprint");
if (savedVoiceprint) {
try {
voiceprintRef.current = new Float32Array(JSON.parse(savedVoiceprint));
setIsTrained(true);
setStatus(VoiceRecognitionStatus.TRAINED);
setMessage("已加载保存的声纹模型");
} catch (error) {
console.error("加载保存的声纹模型失败:", error);
}
}
// 加载TensorFlow模型
loadModel();
return () => {
stopRecording();
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, []);
const audioContextRef = useRef<AudioContext | null>(null);
const analyserRef = useRef<AnalyserNode | null>(null);
const mediaStreamRef = useRef<MediaStream | null>(null);
const recordedChunksRef = useRef<Float32Array[]>([]);
const modelRef = useRef<tf.LayersModel | null>(null);
const voiceprintRef = useRef<Float32Array | null>(null);
const animationFrameRef = useRef<number | null>(null);
// 初始化
useEffect(() => {
// 检查是否有保存的声纹模型
const savedVoiceprint = localStorage.getItem("userVoiceprint");
if (savedVoiceprint) {
try {
voiceprintRef.current = new Float32Array(JSON.parse(savedVoiceprint));
setIsTrained(true);
setStatus(VoiceRecognitionStatus.TRAINED);
setMessage("已加载保存的声纹模型");
} catch (error) {
console.error("加载保存的声纹模型失败:", error);
}
}
// 加载TensorFlow模型
loadModel();
return () => {
stopRecording();
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
// Dispose of TensorFlow model to prevent memory leaks
if (modelRef.current) {
modelRef.current.dispose();
modelRef.current = null;
}
// Clear any remaining tensors in memory
tf.disposeVariables();
tf.engine().endScope();
tf.engine().disposeVariables();
};
}, []);

Comment on lines +88 to +119
// 添加暂停/恢复功能
const togglePause = () => {
if (!isPaused) {
// 使用更强制的中断方式
SpeechRecognition.abortListening();
// 然后再调用正常的停止方法确保完全停止
setTimeout(() => {
SpeechRecognition.stopListening();
}, 0);

if (transcriptRef.current && transcriptRef.current.trim() !== "") {
// 使用setTimeout将提交操作放到下一个事件循环,避免阻塞UI更新
setTimeout(() => {
submitMessage(transcriptRef.current);
resetTranscript();
}, 0);
}
} else {
// 先确保停止当前可能存在的监听
SpeechRecognition.abortListening();
// 短暂延迟后重新启动监听
setTimeout(() => {
SpeechRecognition.startListening({
continuous: true,
language: "zh-CN",
});
// 重置文本
resetTranscript();
}, 0);
}
setIsPaused(!isPaused);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential race condition in togglePause

The function uses multiple setTimeout calls with 0 delay, which can still lead to race conditions.

  // 添加暂停/恢复功能
  const togglePause = () => {
    if (!isPaused) {
      // 使用更强制的中断方式
      SpeechRecognition.abortListening();
      // 然后再调用正常的停止方法确保完全停止
-     setTimeout(() => {
-       SpeechRecognition.stopListening();
-     }, 0);
+     SpeechRecognition.stopListening();

      if (transcriptRef.current && transcriptRef.current.trim() !== "") {
-       // 使用setTimeout将提交操作放到下一个事件循环,避免阻塞UI更新
-       setTimeout(() => {
-         submitMessage(transcriptRef.current);
-         resetTranscript();
-       }, 0);
+       // Use requestAnimationFrame instead for better performance and timing
+       requestAnimationFrame(() => {
+         submitMessage(transcriptRef.current);
+         resetTranscript();
+       });
      }
    } else {
      // 先确保停止当前可能存在的监听
      SpeechRecognition.abortListening();
      // 短暂延迟后重新启动监听
-     setTimeout(() => {
+     requestAnimationFrame(() => {
        SpeechRecognition.startListening({
          continuous: true,
          language: "zh-CN",
        });
        // 重置文本
        resetTranscript();
-     }, 0);
+     });
    }
    setIsPaused(!isPaused);
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 添加暂停/恢复功能
const togglePause = () => {
if (!isPaused) {
// 使用更强制的中断方式
SpeechRecognition.abortListening();
// 然后再调用正常的停止方法确保完全停止
setTimeout(() => {
SpeechRecognition.stopListening();
}, 0);
if (transcriptRef.current && transcriptRef.current.trim() !== "") {
// 使用setTimeout将提交操作放到下一个事件循环,避免阻塞UI更新
setTimeout(() => {
submitMessage(transcriptRef.current);
resetTranscript();
}, 0);
}
} else {
// 先确保停止当前可能存在的监听
SpeechRecognition.abortListening();
// 短暂延迟后重新启动监听
setTimeout(() => {
SpeechRecognition.startListening({
continuous: true,
language: "zh-CN",
});
// 重置文本
resetTranscript();
}, 0);
}
setIsPaused(!isPaused);
};
// 添加暂停/恢复功能
const togglePause = () => {
if (!isPaused) {
// 使用更强制的中断方式
SpeechRecognition.abortListening();
// 然后再调用正常的停止方法确保完全停止
SpeechRecognition.stopListening();
if (transcriptRef.current && transcriptRef.current.trim() !== "") {
// Use requestAnimationFrame instead for better performance and timing
requestAnimationFrame(() => {
submitMessage(transcriptRef.current);
resetTranscript();
});
}
} else {
// 先确保停止当前可能存在的监听
SpeechRecognition.abortListening();
// 短暂延迟后重新启动监听
requestAnimationFrame(() => {
SpeechRecognition.startListening({
continuous: true,
language: "zh-CN",
});
// 重置文本
resetTranscript();
});
}
setIsPaused(!isPaused);
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant