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:
@@ -158,6 +158,11 @@ async def websocket_endpoint(
|
||||
history = await conversation_service.ensure_redis_history_from_db(
|
||||
conversation_id
|
||||
)
|
||||
user_language = (
|
||||
"en"
|
||||
if str(getattr(user, "language_preference", "zh") or "zh").lower() == "en"
|
||||
else "zh"
|
||||
)
|
||||
if not history:
|
||||
missing_profile = get_missing_profile_fields(user)
|
||||
if missing_profile:
|
||||
@@ -166,6 +171,7 @@ async def websocket_endpoint(
|
||||
conversation_id=conversation_id,
|
||||
missing_fields=missing_profile,
|
||||
nickname=user.nickname or "",
|
||||
language=user_language,
|
||||
)
|
||||
ai_msg_id = await ConversationHistoryStore(
|
||||
db
|
||||
@@ -201,6 +207,7 @@ async def websocket_endpoint(
|
||||
birth_place=user.birth_place,
|
||||
grew_up_place=user.grew_up_place,
|
||||
occupation=user.occupation,
|
||||
language=user_language,
|
||||
)
|
||||
era_place = (user.grew_up_place or user.birth_place or "") or ""
|
||||
opening_messages = (
|
||||
@@ -214,6 +221,7 @@ async def websocket_endpoint(
|
||||
occupation=user.occupation or "",
|
||||
profile_birth_year=user.birth_year,
|
||||
profile_era_place=era_place,
|
||||
language=user_language,
|
||||
)
|
||||
)
|
||||
ai_msg_id = await ConversationHistoryStore(
|
||||
@@ -281,6 +289,13 @@ async def websocket_endpoint(
|
||||
data = message.get("data") or {}
|
||||
text_message = data.get("text", "")
|
||||
tts_this_turn = bool(data.get("tts_this_turn"))
|
||||
# 长期保留:TTS 决策入口可见性(INFO 级别即可定位 FE 是否带 tts_this_turn)
|
||||
logger.info(
|
||||
"ws.user_message tts_this_turn={} conversation_id={} text_len={}",
|
||||
tts_this_turn,
|
||||
conversation_id,
|
||||
len(text_message or ""),
|
||||
)
|
||||
|
||||
if text_message:
|
||||
can_send, quota_msg = await check_ws_quota(
|
||||
@@ -381,6 +396,17 @@ async def websocket_endpoint(
|
||||
voice_session_id = str(resolved_vs).strip()
|
||||
is_last = bool(data.get("is_last", False))
|
||||
audio_duration = int(data.get("duration", 0) or 0)
|
||||
tts_this_turn_segment = bool(data.get("tts_this_turn"))
|
||||
# 长期保留:分段语音轮的 TTS 决策入口可见性
|
||||
logger.info(
|
||||
"ws.audio_segment tts_this_turn={} is_last={} "
|
||||
"conversation_id={} voice_session_id={} segment_index_raw={}",
|
||||
tts_this_turn_segment,
|
||||
is_last,
|
||||
conversation_id,
|
||||
voice_session_id,
|
||||
segment_index_raw,
|
||||
)
|
||||
|
||||
if not audio_base64:
|
||||
await manager.send_message(
|
||||
@@ -488,7 +514,7 @@ async def websocket_endpoint(
|
||||
audio_base64=audio_base64,
|
||||
audio_duration=audio_duration,
|
||||
is_last=is_last,
|
||||
tts_this_turn=bool(data.get("tts_this_turn")),
|
||||
tts_this_turn=tts_this_turn_segment,
|
||||
)
|
||||
)
|
||||
register_segment_task(conversation_id, voice_session_id, task)
|
||||
@@ -498,6 +524,13 @@ async def websocket_endpoint(
|
||||
audio_base64 = data.get("audio_base64", "")
|
||||
audio_duration = data.get("duration", 0)
|
||||
tts_this_turn = bool(data.get("tts_this_turn"))
|
||||
# 长期保留:单次整段音频路径的 TTS 决策入口可见性
|
||||
logger.info(
|
||||
"ws.audio_message tts_this_turn={} conversation_id={} duration_s={}",
|
||||
tts_this_turn,
|
||||
conversation_id,
|
||||
audio_duration,
|
||||
)
|
||||
|
||||
if audio_base64:
|
||||
can_send, quota_msg = await check_ws_quota(
|
||||
@@ -659,6 +692,12 @@ async def websocket_endpoint(
|
||||
"assistantMessageId"
|
||||
)
|
||||
if not aid or not str(aid).strip():
|
||||
logger.warning(
|
||||
"ws.TTS_REQUEST 缺少 assistant_message_id "
|
||||
"conversation_id={} user_id={}",
|
||||
conversation_id,
|
||||
user_id,
|
||||
)
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
{
|
||||
@@ -680,6 +719,15 @@ async def websocket_endpoint(
|
||||
st_val = None
|
||||
else:
|
||||
st_val = str(st).strip() or None
|
||||
logger.info(
|
||||
"ws.TTS_REQUEST received conversation_id={} user_id={} "
|
||||
"assistant_message_id={} segment_index={} segment_text_len={}",
|
||||
conversation_id,
|
||||
user_id,
|
||||
str(aid).strip(),
|
||||
seg_idx,
|
||||
len(st_val or ""),
|
||||
)
|
||||
ok, err_msg = await handle_tts_request_on_demand(
|
||||
conversation_id=conversation_id,
|
||||
user_id=user_id,
|
||||
@@ -688,6 +736,15 @@ async def websocket_endpoint(
|
||||
segment_text=st_val,
|
||||
db=db,
|
||||
)
|
||||
logger.info(
|
||||
"ws.TTS_REQUEST handled conversation_id={} assistant_message_id={} "
|
||||
"segment_index={} ok={} err_msg={}",
|
||||
conversation_id,
|
||||
str(aid).strip(),
|
||||
seg_idx,
|
||||
ok,
|
||||
err_msg,
|
||||
)
|
||||
if not ok:
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
|
||||
Reference in New Issue
Block a user