feat(i18n): persist language preference and thread through chat, memoir, TTS

- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
  only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
  names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
  for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-11 16:16:49 +08:00
parent 5ce29aad64
commit ccdc4e4277
64 changed files with 3233 additions and 208 deletions

View File

@@ -72,6 +72,7 @@ class MemoirOrchestrator:
update_slot: Callable[[str, str, str, List[str]], MemoirStateSchema],
llm_fast: Any | None = None,
on_phase1_chunk: Optional[Callable[[int, int], None]] = None,
language: str = "zh",
) -> PreparedMemoirBatches:
"""
遍历 segmentsExtraction → slot 更新 → Classification → 按 category 分桶。
@@ -99,6 +100,7 @@ class MemoirOrchestrator:
classify_extract_llm=classify_extract_llm,
update_slot=update_slot,
on_phase1_chunk=on_phase1_chunk,
language=language,
)
logger.info(
"event=phase1_batch_path_used segment_count={} "
@@ -132,6 +134,7 @@ class MemoirOrchestrator:
current_stage=state.current_stage or "childhood",
stage_slots=stage_slots_raw,
llm=classify_extract_llm,
language=language,
)
fb = state.current_stage or "childhood"
detected_stage = normalize_chat_stage(result.detected_stage, fb)
@@ -151,6 +154,7 @@ class MemoirOrchestrator:
fallback_stage=detected_stage,
llm=classify_extract_llm,
segment_id=segment.id,
language=language,
)
chapter_category = classify_result.category
if (not result_slots) and classify_result.llm_said_none:
@@ -190,6 +194,7 @@ class MemoirOrchestrator:
classify_extract_llm: Any,
update_slot: Callable[[str, str, str, List[str]], MemoirStateSchema],
on_phase1_chunk: Optional[Callable[[int, int], None]] = None,
language: str = "zh",
) -> PreparedMemoirBatches:
category_to_segments: Dict[str, List[Segment]] = {}
segment_skip_story_ids: Set[str] = set()
@@ -201,6 +206,7 @@ class MemoirOrchestrator:
classify_extract_llm,
chunk_size=int(settings.memoir_phase1_batch_llm_chunk_size),
on_chunk=on_phase1_chunk,
language=language,
)
for segment in segments: