From 659c470eba542383987aad227b7f918100f131fa Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期五, 11 九月 2026 15:09:05 +0800
Subject: [PATCH] 小智终端 5种 场景模式 OK

---
 xiaozhi-esp32-server/main/xiaozhi-server/core/handle/receiveAudioHandle.py |   84 +++++++++++++++++++++++++++++++----------
 1 files changed, 63 insertions(+), 21 deletions(-)

diff --git a/xiaozhi-esp32-server/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/xiaozhi-esp32-server/main/xiaozhi-server/core/handle/receiveAudioHandle.py
index 2985600..e26fea1 100644
--- a/xiaozhi-esp32-server/main/xiaozhi-server/core/handle/receiveAudioHandle.py
+++ b/xiaozhi-esp32-server/main/xiaozhi-server/core/handle/receiveAudioHandle.py
@@ -17,16 +17,21 @@
 async def handleAudioMessage(conn: "ConnectionHandler", pcm_frame):
     # 褰撳墠鐗囨鏄惁鏈変汉璇磋瘽
     have_voice = conn.vad.is_vad(conn, pcm_frame)
-    # 濡傛灉璁惧鍒氬垰琚敜閱掞紝鐭殏蹇界暐VAD妫�娴�
-    if hasattr(conn, "just_woken_up") and conn.just_woken_up:
+    mode = getattr(conn, "interaction_mode", "CHAT")
+    # 璺熻鍚獥蹇呴』鏀堕綈 PCM銆俲ust_woken_up 鐨� 2s 涓㈠抚鍙敤浜庡璇濓紝閬垮厤璺熻鍓嶅嚑鍙ヨ惤鎴愮┖ wav銆�
+    if mode != "SHADOWING" and hasattr(conn, "just_woken_up") and conn.just_woken_up:
         have_voice = False
         # 璁剧疆涓�涓煭鏆傚欢杩熷悗鎭㈠VAD妫�娴�
         if not hasattr(conn, "vad_resume_task") or conn.vad_resume_task.done():
             conn.vad_resume_task = asyncio.create_task(resume_vad_detection(conn))
         return
-    # 鏈嶅姟绔疉EC鍔熻兘闇�瑕佸疄鏃惰Е鍙戞墦鏂�
+    # 鏈嶅姟绔疉EC锛氫粎瀵硅瘽绫绘ā寮忓厑璁告墦鏂�傚璇�/璺熻/缈昏瘧鏃跺枃鍙洖澹颁笉鑳藉綋鎴愭彃璇濄��
     if conn.client_aec and have_voice:
-        if conn.client_is_speaking and conn.client_listen_mode != "manual":
+        if (
+            conn.client_is_speaking
+            and conn.client_listen_mode != "manual"
+            and mode in ("CHAT", "BILINGUAL")
+        ):
             await handleAbortMessage(conn)
     # 璁惧闀挎椂闂寸┖闂叉娴嬶紝鐢ㄤ簬say goodbye
     await no_voice_close_connect(conn, have_voice)
@@ -44,6 +49,7 @@
     # 妫�鏌ヨ緭鍏ユ槸鍚︽槸JSON鏍煎紡锛堝寘鍚璇濅汉淇℃伅锛�
     speaker_name = None
     actual_text = text
+    conn.last_asr_language = getattr(conn, "last_asr_language", None)
 
     try:
         # FunASR 绛変細鎶婄粨鏋滃寘鎴� {"content","language","emotion"}锛涘0绾瑰満鏅繕鏈� speaker
@@ -52,6 +58,8 @@
             if isinstance(data, dict) and "content" in data:
                 speaker_name = data.get("speaker")
                 actual_content = data.get("content") or ""
+                if data.get("language"):
+                    conn.last_asr_language = data.get("language")
                 if speaker_name:
                     conn.logger.bind(tag=TAG).info(f"瑙f瀽鍒拌璇濅汉淇℃伅: {speaker_name}")
                     # 浠呭湪璇ヨ璇濅汉棣栨鍑虹幇鏃朵繚鐣� {"speaker":...} JSON锛岃妯″瀷鑷劧绉板懠涓�娆★紱
@@ -86,15 +94,28 @@
             return
 
     from core.app_runtime.runtime import (
-        is_exit_translation,
+        detect_user_language,
+        handle_shadowing_turn,
+        is_exit_to_chat,
         match_voice_switch,
         run_translation,
         start_app_task,
-        VOICE_MENU,
+        wrap_bilingual_user_text,
+        voice_menu_text,
     )
     from core.handle.intentHandler import speak_txt
 
     mode = getattr(conn, "interaction_mode", "CHAT")
+
+    # 鑿滃崟 TTS 閲岀殑銆岃窡璇�/澶嶈鏈�/缈昏瘧銆嶄笉鑳藉綋鎴愬垏 App锛涢��鍑鸿亰澶╁彛浠や粛瑕佺敓鏁�
+    if conn.client_is_speaking and getattr(conn, "awaiting_voice_app_choice", False):
+        mapped = match_voice_switch(actual_text)
+        if mapped and mapped[0] == "chat":
+            conn.awaiting_voice_app_choice = False
+            await start_app_task(conn, "chat", "conversation", {}, source="voice")
+            return
+        conn.logger.bind(tag=TAG).info("璇煶鑿滃崟鎾姤涓紝蹇界暐鍒� App")
+        return
 
     if getattr(conn, "awaiting_voice_app_choice", False):
         mapped = match_voice_switch(actual_text)
@@ -105,39 +126,50 @@
             return
         if mapped and mapped[0] == "__menu__":
             conn.awaiting_voice_app_choice = True
-            speak_txt(conn, VOICE_MENU)
+            speak_txt(conn, voice_menu_text())
             return
-        if mapped and mapped[0] != "__menu__":
-            params = {}
-            await start_app_task(conn, mapped[0], mapped[1], params, source="voice")
+        if mapped and mapped[0] == "chat":
+            await start_app_task(conn, "chat", "conversation", {}, source="voice")
             return
-        speak_txt(conn, VOICE_MENU)
-        conn.awaiting_voice_app_choice = True
+        if mapped:
+            await start_app_task(conn, mapped[0], mapped[1], {}, source="voice")
+            return
+        conn.logger.bind(tag=TAG).info("璇煶鑿滃崟鏈尮閰嶏紝淇濇寔 chat")
         return
 
     mapped = match_voice_switch(actual_text)
     if mapped and mapped[0] == "__menu__":
         conn.awaiting_voice_app_choice = True
-        speak_txt(conn, VOICE_MENU)
+        speak_txt(conn, voice_menu_text())
         return
     if mapped and mapped[0] == "__reserved__":
         speak_txt(conn, "杩欎釜杩樻病寮�鏀撅紝鍏堢户缁綋鍓嶅唴瀹瑰惂銆�")
         return
-    if mapped and mapped[0] in ("english_translation", "english_learning", "chat"):
-        if mapped[0] == "english_learning" and mapped[1] == "repeater":
-            speak_txt(conn, "澶嶈闇�瑕佹寚瀹� SD 鍗′笂鐨勬枃浠跺悕锛屾垨鐢ㄥ皬绋嬪簭寮�濮嬨��")
-            return
+    if conn.client_is_speaking and mapped and mapped[0] not in ("chat",):
+        conn.logger.bind(tag=TAG).info(f"TTS 鎾斁涓拷鐣ュ垏 App: {mapped}")
+        return
+    if mapped and mapped[0] in (
+        "english_translation",
+        "english_learning",
+        "english_conversation",
+        "chat",
+    ):
         await start_app_task(conn, mapped[0], mapped[1], {}, source="voice")
         return
 
+    if mode != "CHAT" and is_exit_to_chat(actual_text):
+        await start_app_task(conn, "chat", "conversation", {}, source="voice")
+        return
+
     if mode == "PLAY_AUDIO":
-        conn.logger.bind(tag=TAG).info("PLAY_AUDIO 妯″紡蹇界暐涓婅瀵硅瘽")
+        conn.logger.bind(tag=TAG).info("澶嶈鏈烘ā寮忓拷鐣ヤ笂琛屽璇�")
+        return
+
+    if mode == "SHADOWING":
+        await handle_shadowing_turn(conn, actual_text)
         return
 
     if mode == "TRANSLATION":
-        if is_exit_translation(actual_text):
-            await start_app_task(conn, "chat", "conversation", {}, source="voice")
-            return
         await send_stt_message(conn, actual_text)
         conn.client_abort = False
         translated = await run_translation(conn, actual_text)
@@ -149,6 +181,16 @@
         speak_txt(conn, translated)
         return
 
+    if mode == "BILINGUAL":
+        await send_stt_message(conn, actual_text)
+        conn.client_abort = False
+        user_lang = detect_user_language(
+            actual_text, getattr(conn, "last_asr_language", None)
+        )
+        wrapped = wrap_bilingual_user_text(actual_text, user_lang)
+        conn.executor.submit(conn.chat, wrapped)
+        return
+
     # manual 妯″紡涓嬩笉鎵撴柇姝e湪鎾斁鐨勫唴瀹�
     if conn.client_is_speaking and conn.client_listen_mode != "manual":
         await handleAbortMessage(conn)

--
Gitblit v1.9.1