# -*- coding: utf-8 -*-
"""
文本输出 + 语音输出模块
"""
import logging
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout,
QTextEdit, QLabel, QPushButton
)
from uas.io.base_io import BaseOutput, IOData, IOType
from uas.io.signals import io_signals
logger = logging.getLogger(__name__)
try:
import pyttsx3
HAS_TTS = True
except ImportError:
HAS_TTS = False
logger.warning("⚠️ pyttsx3 未安装: pip install pyttsx3")
# ─────────────────────────────────────────────────────────────────
# 文本输出组件
# ─────────────────────────────────────────────────────────────────
class TextOutput(BaseOutput):
def __init__(self):
super().__init__("text_output", [IOType.TEXT, IOType.SPEECH])
def render(self, data: IOData) -> None:
"""将处理结果追加到文本框"""
import time
ts = time.strftime("%H:%M:%S", time.localtime(data.timestamp))
icon = {"text": "📝", "speech": "🎤", "file": "📁"}.get(
data.io_type.value, "📦"
)
result = data.processed if data.processed is not None else data.raw
html = (
f'[{ts}] '
f'{icon} {data.io_type.value.upper()} '
f'{data.data_id[:8]}
'
f'{result}
'
f'{"─" * 50}
'
)
self._output_edit.append(html)
# 自动滚动到底部
sb = self._output_edit.verticalScrollBar()
sb.setValue(sb.maximum())
class TextOutputWidget(QWidget, TextOutput):
"""
文本输出 UI 组件
接收 output_ready 信号,将处理结果显示在滚动文本框中
"""
def __init__(self, parent=None):
QWidget.__init__(self, parent)
TextOutput.__init__(self)
self._build_ui()
def _build_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(6)
# 标题栏
title_row = QHBoxLayout()
title = QLabel("📄 输出结果")
title.setStyleSheet("font-weight: bold; font-size: 13px;")
title_row.addWidget(title)
title_row.addStretch()
clear_btn = QPushButton("清空")
clear_btn.setFixedWidth(50)
clear_btn.clicked.connect(self._output_edit.clear
if hasattr(self, "_output_edit") else lambda: None)
title_row.addWidget(clear_btn)
layout.addLayout(title_row)
# 输出文本框
self._output_edit = QTextEdit()
self._output_edit.setReadOnly(True)
self._output_edit.setStyleSheet("""
QTextEdit {
background: #1e1e1e;
color: #d4d4d4;
font-family: Consolas, monospace;
font-size: 13px;
border-radius: 6px;
padding: 6px;
}
""")
layout.addWidget(self._output_edit)
# 重新绑定清空按钮
clear_btn.clicked.disconnect()
clear_btn.clicked.connect(self._output_edit.clear)
# 状态栏
self._status_bar = QLabel("就绪")
self._status_bar.setStyleSheet("color: gray; font-size: 11px;")
layout.addWidget(self._status_bar)
# 连接状态信号
io_signals.status_changed.connect(self._on_status_changed)
io_signals.error_occurred.connect(self._on_error)
io_signals.progress_updated.connect(self._on_progress)
@pyqtSlot(str, str)
def _on_status_changed(self, data_id: str, status: str):
self._status_bar.setText(f"状态: {status} [{data_id[:8]}]")
@pyqtSlot(str, str, str)
def _on_error(self, data_id: str, source: str, msg: str):
self._output_edit.append(
f'❌ 错误 [{data_id[:8]}] {source}: {msg}
'
)
@pyqtSlot(str, int)
def _on_progress(self, data_id: str, progress: int):
self._status_bar.setText(f"处理中: {progress}% [{data_id[:8]}]")