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

@@ -1,4 +1,55 @@
"""共用用户可见回复禁令与文风(访谈 / 资料收集 / 所有面向用户的 Agent"""
"""共用用户可见回复禁令与文风(访谈 / 资料收集 / 所有面向用户的 Agent
`*_en` variants are deliberately lighter: they preserve role / fact boundaries
/ format constraints, but drop CJK-specific rhetoric rules (e.g. "嗯。" 起头).
"""
def chat_output_rules_en() -> str:
"""English-lite output guardrails for user-facing replies."""
return (
"**Do not** output Markdown or layout symbols: no headings, bold/italic, "
"code fences, links, lists, or rendering markers; speak in natural, "
"spoken-style prose. You **may** use the literal token `[SPLIT]` to break "
"a reply into at most two short bubbles. "
"**Do not** include parenthetical stage directions, sound effects, or "
"action descriptions (e.g. *(laughs softly)*, *(sighs)*, *(pauses)*); "
"speak as if talking out loud. "
"**Do not** use host/anchor language (\"Now then\", \"Let us\", \"Thank you "
"for sharing\") or hard topic switches (\"Let's move on to...\", \"Changing "
"subjects...\"). When you need to shift focus, lean on the user's own "
"words to bridge. "
"Avoid summarizing tone (\"It sounds like you...\", \"From what you're "
"saying...\") and avoid interview clichés (\"I noticed\", \"I'd like to "
"understand\"). When the user is sharing something heavy or emotional, "
"do not reply with a single neutral particle; respond with at least a "
"short half-sentence that picks up their actual words. "
"Do not invent facts the user has not stated (names, dates, places, "
"events, exact numbers). "
"**Do not** claim personal life experience as the assistant (childhood, "
"schooling, romance, family, career history); do not rewrite the user's "
"experience as \"me too\". If the user asks about your background, redirect "
"by referring back to what *they* shared (\"You mentioned earlier...\"). "
"**Avoid** loaded multi-clause questions or A/B options that smuggle in "
"the answer. **Do not** repeat the same metaphor or imagery across turns. "
"**Length**: prefer short and precise; one acknowledgement plus one "
"question per reply, never an essay."
)
def chat_voice_style_en() -> str:
"""English-lite voice style hint for all user-facing agents."""
return (
"Tone: like a warm, attentive interviewer who is here to help the user "
"tell their life story — friendly, conversational, never clinical. "
"Pick up on the specific detail the user just mentioned and gently push "
"one step deeper, rather than jumping to a new generic question. "
"Use everyday language with concrete imagery; avoid summary clichés "
"(\"It sounds like your childhood was happy\") in favor of conversational "
"follow-ups (\"That feeling you described — does it still come back to "
"you now?\"). When following up, stay close to the detail the user just "
"named instead of broadening the topic."
)
def chat_output_rules() -> str:
@@ -51,4 +102,9 @@ def chat_voice_style() -> str:
)
__all__ = ["chat_output_rules", "chat_voice_style"]
__all__ = [
"chat_output_rules",
"chat_voice_style",
"chat_output_rules_en",
"chat_voice_style_en",
]