feat: integrate invite API and improve notification handling

- Add invite code display and binding functionality via API
- Fix notification unread count sync on auth state change
- Improve notification mark read with server state validation
- Add auth state listener to trigger notification refresh
- Add YaoCoinConverter for coin-to-yao type conversion
- Remove YaoLegend from divination screens (UI cleanup)
- Abbreviate relation labels in yao detail view
- Add re-register notice to account delete screen
- Update 'coins' terminology to 'points' in localization
- Fix backend points consumption to only run in CHAT mode
- Add HttpxAuthNoiseFilter to suppress auth endpoint logging
- Fix notification static_schema import path
- Add test coverage for notification bloc error handling
- Update AGENTS.md page header rules and image handling
- Delete deprecated run-dev.sh script
This commit is contained in:
qzl
2026-04-13 14:52:22 +08:00
parent da947f9f08
commit 1e22f27de2
52 changed files with 1419 additions and 307 deletions
+28 -16
View File
@@ -170,7 +170,7 @@ class AgentRepository:
session_row.last_activity_at = datetime.now(timezone.utc)
await self._session.flush()
async def get_user_message_count(self, *, session_id: str) -> int:
async def get_assistant_message_count(self, *, session_id: str) -> int:
try:
session_uuid = UUID(session_id)
except ValueError as exc:
@@ -184,7 +184,7 @@ class AgentRepository:
select(func.count(AgentChatMessage.id))
.where(AgentChatMessage.session_id == session_uuid)
.where(AgentChatMessage.deleted_at.is_(None))
.where(AgentChatMessage.role == AgentChatMessageRole.USER)
.where(AgentChatMessage.role == AgentChatMessageRole.ASSISTANT)
)
count = (await self._session.execute(stmt)).scalar_one()
return int(count)
@@ -266,7 +266,11 @@ class AgentRepository:
).scalar_one_or_none() is not None
snapshot_messages: list[dict[str, object]] = []
for message in messages:
snapshot_messages.append(await self._to_snapshot_message(message))
snapshot_messages.append(
(await self._to_chat_message_schema(message)).model_dump(
mode="json", by_alias=True, exclude_none=True
)
)
return {
"day": target_day.isoformat(),
"hasMore": has_more,
@@ -278,7 +282,7 @@ class AgentRepository:
*,
session_id: str,
visibility_mask: int | None = None,
) -> list[dict[str, object]]:
) -> list[AgentChatMessageSchema]:
try:
session_uuid = UUID(session_id)
except ValueError as exc:
@@ -299,9 +303,9 @@ class AgentRepository:
visibility_mask=visibility_mask,
)
messages = (await self._session.execute(message_stmt)).scalars().all()
snapshot_messages: list[dict[str, object]] = []
snapshot_messages: list[AgentChatMessageSchema] = []
for message in messages:
snapshot_messages.append(await self._to_snapshot_message(message))
snapshot_messages.append(await self._to_chat_message_schema(message))
return snapshot_messages
async def get_recent_messages_by_user_window(
@@ -352,7 +356,11 @@ class AgentRepository:
selected = list(reversed(selected_desc))
snapshot_messages: list[dict[str, object]] = []
for message in selected:
snapshot_messages.append(await self._to_snapshot_message(message))
snapshot_messages.append(
(await self._to_chat_message_schema(message)).model_dump(
mode="json", by_alias=True, exclude_none=True
)
)
return snapshot_messages
async def get_latest_session_id_for_user(self, *, user_id: str) -> str | None:
@@ -382,7 +390,7 @@ class AgentRepository:
user_id: str,
visibility_mask: int | None = None,
session_limit: int = 50,
) -> list[dict[str, object]]:
) -> list[AgentChatMessageSchema]:
try:
user_uuid = UUID(user_id)
except ValueError as exc:
@@ -404,7 +412,7 @@ class AgentRepository:
if not session_ids:
return []
snapshots: list[dict[str, object]] = []
snapshots: list[AgentChatMessageSchema] = []
for session_id in session_ids:
message_stmt = (
select(AgentChatMessage)
@@ -423,10 +431,14 @@ class AgentRepository:
)
if not candidate_messages:
continue
selected_snapshot: dict[str, object] | None = None
selected_snapshot: AgentChatMessageSchema | None = None
for message in candidate_messages:
snapshot = await self._to_snapshot_message(message)
metadata = snapshot.get("metadata")
snapshot = await self._to_chat_message_schema(message)
metadata = (
snapshot.metadata.model_dump(mode="json", exclude_none=True)
if snapshot.metadata is not None
else None
)
if not isinstance(metadata, dict):
continue
agent_output = metadata.get("agent_output")
@@ -440,7 +452,7 @@ class AgentRepository:
snapshots.append(selected_snapshot)
snapshots.sort(
key=lambda item: str(item.get("timestamp") or ""),
key=lambda item: str(item.timestamp),
reverse=True,
)
return snapshots
@@ -462,9 +474,9 @@ class AgentRepository:
"config": config_payload,
}
async def _to_snapshot_message(
async def _to_chat_message_schema(
self, message: AgentChatMessage
) -> dict[str, object]:
) -> AgentChatMessageSchema:
role = (
message.role.value
if isinstance(message.role, AgentChatMessageRole)
@@ -487,7 +499,7 @@ class AgentRepository:
"timestamp": message.created_at.astimezone(timezone.utc).isoformat(),
}
)
return payload_model.model_dump(mode="json", exclude_none=True)
return payload_model
def _apply_visibility_filter(
self,