fix(chat): support body and content fields in world and DM chat messages; feat(audio): add sound toggle switch and mute controls
checkpoint / checkpoint (push) Successful in 6m11s

This commit is contained in:
2026-09-19 13:38:23 +00:00
parent 7b693899a7
commit 7abf37ea81
13 changed files with 558 additions and 14 deletions
+95 -2
View File
@@ -71,7 +71,7 @@ from datetime import datetime, timezone
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, computed_field, model_validator
from sqlalchemy import and_, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -97,7 +97,33 @@ _BODY_MAX_LEN = 4000
class SendDMRequest(BaseModel):
body: str = Field(..., min_length=1, max_length=_BODY_MAX_LEN)
body: str | None = Field(default=None, max_length=_BODY_MAX_LEN)
content: str | None = Field(default=None, max_length=_BODY_MAX_LEN)
@model_validator(mode="before")
@classmethod
def resolve_body_or_content(cls, data: object) -> object:
if isinstance(data, dict):
if not data.get("body") and data.get("content"):
data["body"] = data["content"]
elif not data.get("content") and data.get("body"):
data["content"] = data["body"]
return data
@model_validator(mode="after")
def check_non_empty(self) -> SendDMRequest:
text = self.body or self.content
if not text or len(text.strip()) < 1:
raise ValueError("Message body or content must not be empty")
if not self.body:
self.body = text
if not self.content:
self.content = text
return self
class StartConversationRequest(BaseModel):
target_user_id: int
class DMMessageResponse(BaseModel):
@@ -111,6 +137,11 @@ class DMMessageResponse(BaseModel):
model_config = {"from_attributes": True}
@computed_field
@property
def content(self) -> str:
return self.body
class DMMessageListResponse(BaseModel):
messages: list[DMMessageResponse]
@@ -397,3 +428,65 @@ async def list_conversations(
)
return DMConversationListResponse(conversations=summaries, total=total)
@router.post("/conversations")
async def start_or_get_conversation_endpoint(
body: StartConversationRequest,
current_user: CurrentUser,
db: DbDep,
):
"""Start or get existing conversation with target_user_id."""
if body.target_user_id == current_user.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot start a conversation with yourself"
)
await _get_target_user(db, body.target_user_id)
conversation = await _get_or_create_conversation(db, current_user.id, body.target_user_id)
return {"id": conversation.id, "target_user_id": body.target_user_id}
@router.get("/conversations/{conversation_id}/messages", response_model=DMMessageListResponse)
async def get_dm_conversation_history(
conversation_id: int,
current_user: CurrentUser,
db: DbDep,
limit: int = Query(50, ge=1, le=100),
before: int | None = Query(None),
):
"""Get messages for a conversation by conversation_id."""
conv_result = await db.execute(
select(Conversation).where(
Conversation.id == conversation_id,
or_(Conversation.user_a_id == current_user.id, Conversation.user_b_id == current_user.id),
)
)
conversation = conv_result.scalar_one_or_none()
if conversation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found")
peer_id = _peer_id(conversation, current_user.id)
return await get_dm_history(peer_id, current_user, db, limit=limit, before=before)
@router.post(
"/conversations/{conversation_id}/messages",
response_model=DMMessageResponse,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(chat_dm_send_rate_limit)],
)
async def send_dm_to_conversation(
conversation_id: int,
body: SendDMRequest,
current_user: CurrentUser,
db: DbDep,
):
"""Send a message to a conversation by conversation_id."""
conv_result = await db.execute(
select(Conversation).where(
Conversation.id == conversation_id,
or_(Conversation.user_a_id == current_user.id, Conversation.user_b_id == current_user.id),
)
)
conversation = conv_result.scalar_one_or_none()
if conversation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found")
peer_id = _peer_id(conversation, current_user.id)
return await send_dm(peer_id, body, current_user, db)