base_agent/uas/inout/base_io.py

148 lines
5.4 KiB
Python

# -*- coding: utf-8 -*-
"""
IO 数据模型 + 输入/输出抽象基类
所有 Input / Output 适配器必须继承对应基类
"""
import logging
import time
import uuid
from abc import abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional, Dict
from PyQt5.QtCore import QObject
from uas.io.signals import io_signals
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────────────────────────
# 枚举定义
# ─────────────────────────────────────────────────────────────────
class IOType(Enum):
TEXT = "text"
SPEECH = "speech"
FILE = "file"
IMAGE = "image"
BINARY = "binary"
CUSTOM = "custom"
class IOStatus(Enum):
PENDING = "pending"
PROCESSING = "processing"
SUCCESS = "success"
ERROR = "error"
CANCELLED = "cancelled"
# ─────────────────────────────────────────────────────────────────
# 统一数据包
# ─────────────────────────────────────────────────────────────────
@dataclass
class IOData:
"""统一 IO 数据包,贯穿整个输入→处理→输出管道"""
io_type: IOType
raw: Any
data_id: str = field(default_factory=lambda: str(uuid.uuid4()))
timestamp: float = field(default_factory=time.time)
source: str = ""
metadata: Dict = field(default_factory=dict)
status: IOStatus = IOStatus.PENDING
error: Optional[str] = None
processed: Any = None # 处理器填充
def mark_processing(self):
self.status = IOStatus.PROCESSING
io_signals.status_changed.emit(self.data_id, self.status.value)
def mark_success(self, result: Any = None):
self.status = IOStatus.SUCCESS
self.processed = result if result is not None else self.raw
io_signals.status_changed.emit(self.data_id, self.status.value)
def mark_error(self, msg: str):
self.status = IOStatus.ERROR
self.error = msg
io_signals.status_changed.emit(self.data_id, self.status.value)
io_signals.error_occurred.emit(self.data_id, self.source, msg)
def __repr__(self):
return (f"IOData(id={self.data_id[:8]}, "
f"type={self.io_type.value}, status={self.status.value})")
# ─────────────────────────────────────────────────────────────────
# 抽象基类
# ─────────────────────────────────────────────────────────────────
class BaseInput(QObject):
"""
输入模块抽象基类
子类实现 start() / stop() / _do_read()
读取到数据后调用 self._emit_input(data) 即可
"""
def __init__(self, source_name: str, io_type: IOType, parent=None):
super().__init__(parent)
self.source_name = source_name
self.io_type = io_type
self._running = False
self.logger = logging.getLogger(self.__class__.__name__)
@abstractmethod
def start(self) -> None:
"""启动输入监听"""
...
@abstractmethod
def stop(self) -> None:
"""停止输入监听"""
...
def _emit_input(self, raw_data: Any, metadata: Dict = None) -> IOData:
"""子类调用此方法将原始数据注入信号管道"""
data = IOData(
io_type = self.io_type,
raw = raw_data,
source = self.source_name,
metadata = metadata or {},
)
self.logger.debug(f"输入到达: {data}")
io_signals.input_received.emit(data.data_id, data.io_type.value, data)
return data
class BaseOutput(QObject):
"""
输出模块抽象基类
连接 io_signals.output_ready 信号,根据 io_type 过滤并渲染
"""
def __init__(self, target_name: str, accepted_types: list, parent=None):
super().__init__(parent)
self.target_name = target_name
self.accepted_types = [t.value if isinstance(t, IOType) else t
for t in accepted_types]
self.logger = logging.getLogger(self.__class__.__name__)
# 自动连接输出信号
io_signals.output_ready.connect(self._on_output_ready)
def _on_output_ready(self, data_id: str, io_type: str, output_data: object):
"""信号槽:过滤类型后调用子类 render"""
if io_type not in self.accepted_types:
return
try:
self.render(output_data)
except Exception as e:
self.logger.error(f"输出渲染异常: {e}", exc_info=True)
io_signals.error_occurred.emit(data_id, self.target_name, str(e))
@abstractmethod
def render(self, data: IOData) -> None:
"""子类实现具体渲染逻辑"""
...