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>
2026-05-11 16:16:49 +08:00
|
|
|
|
from typing import Literal, Optional
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
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>
2026-05-11 16:16:49 +08:00
|
|
|
|
LanguagePreference = Literal["zh", "en"]
|
|
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
class RegisterRequest(BaseModel):
|
|
|
|
|
|
phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)")
|
|
|
|
|
|
password: str = Field(..., min_length=6, description="密码(至少6位)")
|
|
|
|
|
|
nickname: str = Field(..., min_length=1, max_length=50, description="昵称")
|
|
|
|
|
|
email: Optional[str] = Field(None, description="邮箱(可选)")
|
|
|
|
|
|
agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策")
|
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>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: Optional[LanguagePreference] = Field(
|
|
|
|
|
|
None,
|
|
|
|
|
|
description="device language at signup; only used when creating a new user",
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
|
|
|
|
phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)")
|
|
|
|
|
|
password: str = Field(..., min_length=1, description="密码")
|
|
|
|
|
|
agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TokenResponse(BaseModel):
|
|
|
|
|
|
access_token: str
|
|
|
|
|
|
refresh_token: str
|
|
|
|
|
|
token_type: str = "bearer"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RefreshTokenRequest(BaseModel):
|
|
|
|
|
|
refresh_token: str = Field(..., description="刷新令牌")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
phone: str
|
|
|
|
|
|
email: Optional[str] = None
|
|
|
|
|
|
nickname: str
|
|
|
|
|
|
avatar_url: Optional[str] = None
|
|
|
|
|
|
subscription_type: str
|
|
|
|
|
|
created_at: str
|
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>
2026-05-11 16:16:49 +08:00
|
|
|
|
language_preference: LanguagePreference = "zh"
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SendSmsRequest(BaseModel):
|
|
|
|
|
|
phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)")
|
2026-03-19 14:36:14 +08:00
|
|
|
|
purpose: str = Field(
|
|
|
|
|
|
..., description="用途:register/login/reset_password/change_phone"
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SendSmsResponse(BaseModel):
|
|
|
|
|
|
success: bool
|
|
|
|
|
|
message: str
|
|
|
|
|
|
expires_in: Optional[int] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SmsLoginRequest(BaseModel):
|
|
|
|
|
|
phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)")
|
|
|
|
|
|
code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)")
|
|
|
|
|
|
agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策")
|
2026-03-19 14:36:14 +08:00
|
|
|
|
nickname: Optional[str] = Field(
|
|
|
|
|
|
None, max_length=50, description="昵称(注册时必填,登录时可选)"
|
|
|
|
|
|
)
|
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>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: Optional[LanguagePreference] = Field(
|
|
|
|
|
|
None,
|
|
|
|
|
|
description="device language at signup; only used when creating a new user",
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-20 11:58:32 +08:00
|
|
|
|
class MockSmsLoginRequest(BaseModel):
|
|
|
|
|
|
"""开发/评测专用:与 MOCK_SMS_LOGIN_ENABLED 联用,跳过短信校验。"""
|
|
|
|
|
|
|
|
|
|
|
|
phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)")
|
|
|
|
|
|
agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策")
|
|
|
|
|
|
nickname: Optional[str] = Field(
|
|
|
|
|
|
None, max_length=50, description="新用户昵称(可选)"
|
|
|
|
|
|
)
|
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>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: Optional[LanguagePreference] = Field(
|
|
|
|
|
|
None,
|
|
|
|
|
|
description="device language at signup; only used when creating a new user",
|
|
|
|
|
|
)
|
2026-04-20 11:58:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
class SmsRegisterRequest(BaseModel):
|
|
|
|
|
|
phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)")
|
|
|
|
|
|
code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)")
|
|
|
|
|
|
password: str = Field(..., min_length=6, description="密码(至少6位)")
|
|
|
|
|
|
nickname: str = Field(..., min_length=1, max_length=50, description="昵称")
|
|
|
|
|
|
email: Optional[str] = Field(None, description="邮箱(可选)")
|
|
|
|
|
|
agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策")
|
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>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: Optional[LanguagePreference] = Field(
|
|
|
|
|
|
None,
|
|
|
|
|
|
description="device language at signup; only used when creating a new user",
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResetPasswordRequest(BaseModel):
|
|
|
|
|
|
phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)")
|
|
|
|
|
|
code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)")
|
|
|
|
|
|
new_password: str = Field(..., min_length=6, description="新密码(至少6位)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChangePasswordRequest(BaseModel):
|
|
|
|
|
|
old_password: str = Field(..., min_length=1, description="旧密码")
|
|
|
|
|
|
new_password: str = Field(..., min_length=6, description="新密码(至少6位)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChangePhoneRequest(BaseModel):
|
2026-03-19 14:36:14 +08:00
|
|
|
|
new_phone: str = Field(
|
|
|
|
|
|
..., min_length=11, max_length=11, description="新手机号(11位)"
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UpdateNicknameRequest(BaseModel):
|
2026-03-19 14:36:14 +08:00
|
|
|
|
nickname: str = Field(
|
|
|
|
|
|
..., min_length=1, max_length=50, description="昵称(1-50个字符)"
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AvatarUploadResponse(BaseModel):
|
|
|
|
|
|
avatar_url: str
|
feat(profile): avatar presets, upload, nickname editing
- FastAPI: preset assets 01–08, GET list/static, PUT /me/avatar/preset,
safer uploaded-avatar path validation, preset_avatars + HTTP tests.
- Expo: personal-info (library + presets), profile tab avatar,
resolveApiMediaUrl, auth hooks cache sync, Web multipart helper,
partial-save messaging + profile i18n.
- Includes existing edits to conversation screen and voice use-player.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-06 13:51:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SetAvatarPresetRequest(BaseModel):
|
|
|
|
|
|
preset_id: str = Field(
|
|
|
|
|
|
...,
|
|
|
|
|
|
min_length=2,
|
|
|
|
|
|
max_length=2,
|
|
|
|
|
|
pattern=r"^\d{2}$",
|
|
|
|
|
|
description="预设编号,如 01–08",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AvatarPresetItem(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
url: str
|