# -*- 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: """子类实现具体渲染逻辑""" ...