merge: sync upstream/r-dev and resolve real conflicts
This commit is contained in:
175
src/webui/routers/chat/serializers.py
Normal file
175
src/webui/routers/chat/serializers.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""提供 WebUI 聊天路由使用的消息序列化能力。"""
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import base64
|
||||
|
||||
from src.common.data_models.message_component_data_model import (
|
||||
AtComponent,
|
||||
DictComponent,
|
||||
EmojiComponent,
|
||||
ForwardComponent,
|
||||
ForwardNodeComponent,
|
||||
ImageComponent,
|
||||
MessageSequence,
|
||||
ReplyComponent,
|
||||
StandardMessageComponents,
|
||||
TextComponent,
|
||||
VoiceComponent,
|
||||
)
|
||||
|
||||
|
||||
def serialize_message_sequence(message_sequence: MessageSequence) -> List[Dict[str, Any]]:
|
||||
"""将内部统一消息组件序列转换为 WebUI 富文本消息段。
|
||||
|
||||
Args:
|
||||
message_sequence: 内部统一消息组件序列。
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 可直接广播给 WebUI 前端的消息段列表。
|
||||
"""
|
||||
serialized_segments: List[Dict[str, Any]] = []
|
||||
for component in message_sequence.components:
|
||||
serialized_segment = serialize_message_component(component)
|
||||
if serialized_segment is not None:
|
||||
serialized_segments.append(serialized_segment)
|
||||
return serialized_segments
|
||||
|
||||
|
||||
def serialize_message_component(component: StandardMessageComponents) -> Optional[Dict[str, Any]]:
|
||||
"""将单个内部消息组件转换为 WebUI 消息段。
|
||||
|
||||
Args:
|
||||
component: 待序列化的内部消息组件。
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: 序列化后的 WebUI 消息段;若组件不应展示则返回 ``None``。
|
||||
"""
|
||||
if isinstance(component, TextComponent):
|
||||
return {"type": "text", "data": component.text}
|
||||
|
||||
if isinstance(component, ImageComponent):
|
||||
return _serialize_binary_component(
|
||||
segment_type="image",
|
||||
mime_type="image/png",
|
||||
binary_data=component.binary_data,
|
||||
fallback_text=component.content,
|
||||
)
|
||||
|
||||
if isinstance(component, EmojiComponent):
|
||||
return _serialize_binary_component(
|
||||
segment_type="emoji",
|
||||
mime_type="image/gif",
|
||||
binary_data=component.binary_data,
|
||||
fallback_text=component.content,
|
||||
)
|
||||
|
||||
if isinstance(component, VoiceComponent):
|
||||
return _serialize_binary_component(
|
||||
segment_type="voice",
|
||||
mime_type="audio/wav",
|
||||
binary_data=component.binary_data,
|
||||
fallback_text=component.content,
|
||||
)
|
||||
|
||||
if isinstance(component, AtComponent):
|
||||
return {
|
||||
"type": "at",
|
||||
"data": {
|
||||
"target_user_id": component.target_user_id,
|
||||
"target_user_nickname": component.target_user_nickname,
|
||||
"target_user_cardname": component.target_user_cardname,
|
||||
},
|
||||
}
|
||||
|
||||
if isinstance(component, ReplyComponent):
|
||||
return {
|
||||
"type": "reply",
|
||||
"data": {
|
||||
"target_message_id": component.target_message_id,
|
||||
"target_message_content": component.target_message_content,
|
||||
"target_message_sender_id": component.target_message_sender_id,
|
||||
"target_message_sender_nickname": component.target_message_sender_nickname,
|
||||
"target_message_sender_cardname": component.target_message_sender_cardname,
|
||||
},
|
||||
}
|
||||
|
||||
if isinstance(component, ForwardNodeComponent):
|
||||
return {
|
||||
"type": "forward",
|
||||
"data": [_serialize_forward_component(item) for item in component.forward_components],
|
||||
}
|
||||
|
||||
if isinstance(component, DictComponent):
|
||||
return _serialize_dict_component(component.data)
|
||||
|
||||
return {"type": "unknown", "data": str(component)}
|
||||
|
||||
|
||||
def _serialize_binary_component(
|
||||
segment_type: str,
|
||||
mime_type: str,
|
||||
binary_data: bytes,
|
||||
fallback_text: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""序列化带二进制负载的消息组件。
|
||||
|
||||
Args:
|
||||
segment_type: WebUI 消息段类型。
|
||||
mime_type: 对应的数据 MIME 类型。
|
||||
binary_data: 组件二进制数据。
|
||||
fallback_text: 二进制缺失时可退化展示的文本。
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 序列化后的 WebUI 消息段。
|
||||
"""
|
||||
if binary_data:
|
||||
encoded_payload = base64.b64encode(binary_data).decode()
|
||||
return {"type": segment_type, "data": f"data:{mime_type};base64,{encoded_payload}"}
|
||||
|
||||
if fallback_text:
|
||||
return {"type": "text", "data": fallback_text}
|
||||
|
||||
return {"type": "unknown", "original_type": segment_type, "data": ""}
|
||||
|
||||
|
||||
def _serialize_forward_component(component: ForwardComponent) -> Dict[str, Any]:
|
||||
"""序列化单个转发节点。
|
||||
|
||||
Args:
|
||||
component: 待序列化的转发节点组件。
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: WebUI 可消费的转发节点字典。
|
||||
"""
|
||||
return {
|
||||
"message_id": component.message_id,
|
||||
"user_id": component.user_id,
|
||||
"user_nickname": component.user_nickname,
|
||||
"user_cardname": component.user_cardname,
|
||||
"content": serialize_message_sequence(MessageSequence(component.content)),
|
||||
}
|
||||
|
||||
|
||||
def _serialize_dict_component(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""最佳努力地序列化非标准字典组件。
|
||||
|
||||
Args:
|
||||
data: 原始字典组件内容。
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 序列化后的 WebUI 消息段。
|
||||
"""
|
||||
raw_type = str(data.get("type") or "dict").strip()
|
||||
raw_payload = data.get("data", data)
|
||||
|
||||
if raw_type in {"text", "image", "emoji", "voice", "video", "file", "music", "face"}:
|
||||
return {"type": raw_type, "data": raw_payload}
|
||||
|
||||
if raw_type == "reply":
|
||||
return {"type": "reply", "data": raw_payload}
|
||||
|
||||
if raw_type == "forward" and isinstance(raw_payload, list):
|
||||
return {"type": "forward", "data": raw_payload}
|
||||
|
||||
return {"type": "unknown", "original_type": raw_type, "data": raw_payload}
|
||||
Reference in New Issue
Block a user