base_agent/uas/inout/inputs/text_input.py

156 lines
5.2 KiB
Python
Raw Normal View History

2026-06-18 03:28:14 +00:00
# -*- coding: utf-8 -*-
"""
文本输入模块
支持
- 控制台标准输入后台线程读取不阻塞 Qt 主循环
- 程序内直接注入inject
- PyQt5 UI 文本框输入
"""
import logging
import sys
from PyQt5.QtCore import QThread, pyqtSlot, Qt
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout,
QLineEdit, QTextEdit, QPushButton, QLabel
)
from uas.io.base_io import BaseInput, IOType
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────────────────────────
# 控制台输入(后台 QThread 读取 stdin
# ─────────────────────────────────────────────────────────────────
class _StdinReaderThread(QThread):
def __init__(self, callback, parent=None):
super().__init__(parent)
self._callback = callback
self._running = False
def run(self):
self._running = True
logger.info("stdin 读取线程启动")
while self._running:
try:
line = sys.stdin.readline()
if not line:
break
text = line.rstrip("\n")
if text:
self._callback(text)
except Exception as e:
logger.error(f"stdin 读取异常: {e}")
break
logger.info("stdin 读取线程退出")
def stop(self):
self._running = False
class ConsoleTextInput(BaseInput):
"""
控制台文本输入
在独立 QThread 中读取 stdin通过信号槽安全回传主线程
"""
def __init__(self, parent=None):
super().__init__("console_stdin", IOType.TEXT, parent)
self._thread = _StdinReaderThread(self._on_line_read, self)
def start(self) -> None:
self._running = True
self._thread.start()
logger.info("✅ ConsoleTextInput 已启动")
def stop(self) -> None:
self._running = False
self._thread.stop()
self._thread.wait(2000)
logger.info("🛑 ConsoleTextInput 已停止")
def _on_line_read(self, text: str):
self._emit_input(text, metadata={"source": "stdin"})
def inject(self, text: str) -> None:
"""程序内直接注入文本(用于测试或 API 调用)"""
self._emit_input(text, metadata={"source": "inject"})
# ─────────────────────────────────────────────────────────────────
# PyQt5 UI 文本输入组件
# ─────────────────────────────────────────────────────────────────
class TextInputWidget(QWidget, BaseInput):
"""
UI 界面的文本输入组件
包含单行输入框 + 多行文本框 + 发送按钮
"""
def __init__(self, parent=None):
QWidget.__init__(self, parent)
BaseInput.__init__(self, "ui_text_input", IOType.TEXT)
self._build_ui()
def _build_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(6)
# 标题
title = QLabel("📝 文本输入")
title.setStyleSheet("font-weight: bold; font-size: 13px;")
layout.addWidget(title)
# 多行输入区
self._text_edit = QTextEdit()
self._text_edit.setPlaceholderText("在此输入多行文本...")
self._text_edit.setMinimumHeight(80)
layout.addWidget(self._text_edit)
# 单行 + 发送
row = QHBoxLayout()
self._line_edit = QLineEdit()
self._line_edit.setPlaceholderText("快速输入(回车发送)...")
self._line_edit.returnPressed.connect(self._on_send_line)
row.addWidget(self._line_edit)
self._send_btn = QPushButton("发送")
self._send_btn.setFixedWidth(60)
self._send_btn.clicked.connect(self._on_send_line)
row.addWidget(self._send_btn)
self._send_multi_btn = QPushButton("发送全文")
self._send_multi_btn.setFixedWidth(70)
self._send_multi_btn.clicked.connect(self._on_send_multi)
row.addWidget(self._send_multi_btn)
layout.addLayout(row)
# 清空按钮
clear_btn = QPushButton("清空")
clear_btn.setFixedWidth(60)
clear_btn.clicked.connect(self._text_edit.clear)
layout.addWidget(clear_btn, alignment=Qt.AlignRight)
@pyqtSlot()
def _on_send_line(self):
text = self._line_edit.text().strip()
if text:
self._emit_input(text, metadata={"source": "ui_line"})
self._line_edit.clear()
@pyqtSlot()
def _on_send_multi(self):
text = self._text_edit.toPlainText().strip()
if text:
self._emit_input(text, metadata={"source": "ui_multiline"})
def start(self) -> None:
self._running = True
self.show()
def stop(self) -> None:
self._running = False