save code

This commit is contained in:
xsl
2026-03-29 23:01:39 +08:00
parent 36e838caec
commit 4dcae11c1b
30 changed files with 1094 additions and 203 deletions
Binary file not shown.
Binary file not shown.
+53 -17
View File
@@ -5,6 +5,7 @@ import time
from typing import Awaitable, Callable, Optional
import numpy as np
from config import settings
from core.state_machine import Arbitrator
from services.asr import ASRService
from services.llm import LLMService
@@ -36,12 +37,10 @@ class ChatPipeline:
await self.arbitrator.on_avatar_done()
@staticmethod
def _split_sentences(text: str, max_len: int = 24) -> list[str]:
# Two-stage split:
# 1) hard sentence boundaries
# 2) soft commas / max length chunks for lower first-chunk latency
def _split_sentences(text: str, max_len: Optional[int] = None) -> list[str]:
max_len = max(16, int(max_len or settings.tts_sentence_max_len))
hard_seps = "。!?!?;\n"
soft_seps = ",、, "
soft_seps = ",、,:"
stage1 = []
buf = []
@@ -60,21 +59,43 @@ class ChatPipeline:
out = []
for seg in stage1:
cur = []
for ch in seg:
cur.append(ch)
cur_len = len(cur)
if ch in soft_seps and cur_len >= 8:
out.append("".join(cur).strip())
cur = []
elif cur_len >= max_len:
out.append("".join(cur).strip())
cur = []
if cur:
out.append("".join(cur).strip())
seg = seg.strip()
if len(seg) <= max_len:
out.append(seg)
continue
start = 0
while start < len(seg):
end = min(len(seg), start + max_len)
if end >= len(seg):
out.append(seg[start:].strip())
break
cut = end
probe = seg[start:end]
soft_cut = max(18, max_len // 2)
for idx in range(len(probe) - 1, soft_cut - 1, -1):
if probe[idx] in soft_seps:
cut = start + idx + 1
break
out.append(seg[start:cut].strip())
start = cut
return [s for s in out if s]
@staticmethod
def _append_segment_pause(audio: np.ndarray, sr: int, is_last: bool) -> np.ndarray:
if is_last or audio.size == 0:
return audio
pause_ms = max(0, int(settings.tts_segment_pause_ms))
if pause_ms <= 0:
return audio
pad = np.zeros(int(sr * pause_ms / 1000.0), dtype=np.float32)
if pad.size == 0:
return audio
return np.concatenate([audio, pad])
async def _synthesize_sentence_first(
self,
reply_text: str,
@@ -94,6 +115,7 @@ class ChatPipeline:
if should_interrupt and should_interrupt():
break
sr = seg_sr
seg_audio = self._append_segment_pause(seg_audio, seg_sr, is_last=(i == len(segments) - 1))
if i == 0:
first_chunk_ms = int((time.perf_counter() - t0) * 1000)
if on_text_segment is not None:
@@ -117,6 +139,20 @@ class ChatPipeline:
asr_t0 = time.perf_counter()
user_text = await self.asr.transcribe(audio_16k)
asr_latency_ms = int((time.perf_counter() - asr_t0) * 1000)
if not user_text.strip():
await self.arbitrator.on_avatar_done()
payload = {
"user_text": "",
"reply_text": "",
"audio_samples": 0,
"sample_rate": 24000,
"asr_latency_ms": asr_latency_ms,
"llm_latency_ms": 0,
"llm_source": "skipped",
"tts_latency_ms": 0,
"tts_first_chunk_ms": 0,
}
return payload, np.zeros(1, dtype=np.float32), 24000
if on_user_text is not None:
await on_user_text(user_text)
llm_t0 = time.perf_counter()