127 lines
4.4 KiB
Python
127 lines
4.4 KiB
Python
# -*- 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'<span style="color:#569cd6;">[{ts}]</span> '
|
|
f'<span style="color:#4ec9b0;">{icon} {data.io_type.value.upper()}</span> '
|
|
f'<span style="color:#ce9178;">{data.data_id[:8]}</span><br>'
|
|
f'<span style="color:#d4d4d4;">{result}</span><br>'
|
|
f'<span style="color:#3c3c3c;">{"─" * 50}</span><br>'
|
|
)
|
|
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'<span style="color:#f44747;">❌ 错误 [{data_id[:8]}] {source}: {msg}</span><br>'
|
|
)
|
|
|
|
@pyqtSlot(str, int)
|
|
def _on_progress(self, data_id: str, progress: int):
|
|
self._status_bar.setText(f"处理中: {progress}% [{data_id[:8]}]")
|
|
|