尝试进行 Qt 测试,增加了 AI 人机调试工具 _console,以及 TransPyV 进行修正
This commit is contained in:
195
_console.py
Normal file
195
_console.py
Normal file
@@ -0,0 +1,195 @@
|
||||
import tkinter as tk
|
||||
from tkinter import scrolledtext
|
||||
import subprocess
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import queue
|
||||
import time
|
||||
|
||||
class ConsoleApp:
|
||||
def __init__(self, root, args):
|
||||
self.root = root
|
||||
self.args = args
|
||||
self.proc = None
|
||||
self.output_queue = queue.Queue()
|
||||
|
||||
# 获取系统当前编码,尽量保证原样输出
|
||||
self.encoding = sys.stdout.encoding or 'utf-8'
|
||||
|
||||
# 记录最后一次收到输出的时间
|
||||
self.last_output_time = time.time()
|
||||
|
||||
root.title("AI 交互终端")
|
||||
root.geometry("900x600")
|
||||
|
||||
# GUI 布局
|
||||
# 输出区域
|
||||
self.text_area = scrolledtext.ScrolledText(root, wrap=tk.WORD, font=("Consolas", 10))
|
||||
self.text_area.pack(expand=True, fill='both', padx=5, pady=5)
|
||||
|
||||
# 输入区域
|
||||
input_frame = tk.Frame(root)
|
||||
input_frame.pack(fill='x', padx=5, pady=5)
|
||||
|
||||
self.entry = tk.Entry(input_frame, font=("Consolas", 10))
|
||||
self.entry.pack(side=tk.LEFT, expand=True, fill='x')
|
||||
self.entry.bind('<Return>', self.send_input)
|
||||
|
||||
# 说话模式复选框
|
||||
self.speak_mode = tk.BooleanVar(value=False)
|
||||
self.speak_check = tk.Checkbutton(input_frame, text="提示AI(说话)", variable=self.speak_mode, font=("Consolas", 9))
|
||||
self.speak_check.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
self.kill_btn = tk.Button(input_frame, text="强制终止进程", command=self.force_terminate, bg="red", fg="white")
|
||||
self.kill_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# 状态栏 (用于显示无输出时长)
|
||||
status_frame = tk.Frame(root)
|
||||
status_frame.pack(fill='x', padx=5, pady=(0, 5))
|
||||
self.time_label = tk.Label(status_frame, text="无输出时长: 00:00", font=("Consolas", 10), fg="gray")
|
||||
self.time_label.pack(side=tk.RIGHT)
|
||||
|
||||
# 启动子进程 (合并 stderr 到 stdout)
|
||||
try:
|
||||
self.proc = subprocess.Popen(
|
||||
self.args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
bufsize=0 # 无缓冲,确保实时性
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"启动进程失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# 启动读取子进程输出的线程
|
||||
self.output_thread = threading.Thread(target=self.read_output, daemon=True)
|
||||
self.output_thread.start()
|
||||
|
||||
# 启动读取当前脚本控制台输入的线程 (允许直接在终端输入)
|
||||
self.stdin_thread = threading.Thread(target=self.read_stdin, daemon=True)
|
||||
self.stdin_thread.start()
|
||||
|
||||
# 窗口关闭事件
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||
|
||||
# 开始轮询队列更新GUI
|
||||
self.update_gui()
|
||||
|
||||
def read_output(self):
|
||||
"""后台线程:实时读取子进程的输出"""
|
||||
fd = self.proc.stdout.fileno()
|
||||
while True:
|
||||
try:
|
||||
byte_data = os.read(fd, 1024)
|
||||
if not byte_data:
|
||||
break # 进程结束
|
||||
|
||||
# 只要收到数据,就更新最后输出时间
|
||||
self.last_output_time = time.time()
|
||||
|
||||
# 1. 实时输出到当前脚本的 stdout (不加任何前缀)
|
||||
sys.stdout.buffer.write(byte_data)
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
# 2. 放入队列供 GUI 使用
|
||||
try:
|
||||
text = byte_data.decode(self.encoding, errors='replace')
|
||||
except:
|
||||
text = byte_data.decode('utf-8', errors='replace')
|
||||
self.output_queue.put(text)
|
||||
except OSError:
|
||||
break # 管道已关闭
|
||||
|
||||
def read_stdin(self):
|
||||
"""后台线程:实时读取宿主控制台的 stdin 并转发给子进程"""
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
try:
|
||||
self.proc.stdin.write(line.encode(self.encoding, errors='replace'))
|
||||
self.proc.stdin.flush()
|
||||
except:
|
||||
break
|
||||
|
||||
def send_input(self, event):
|
||||
"""GUI 输入框回车事件"""
|
||||
text = self.entry.get()
|
||||
self.entry.delete(0, tk.END)
|
||||
if not text:
|
||||
return
|
||||
|
||||
# 如果勾选了"提示AI",则直接输出到当前脚本的 stdout,不发给子进程
|
||||
if self.speak_mode.get():
|
||||
speak_text = text + '\n'
|
||||
# 输出到当前脚本控制台,供 AI 捕获
|
||||
sys.stdout.buffer.write(speak_text.encode(self.encoding, errors='replace'))
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
# 在 GUI 中回显,让用户知道说了什么(使用不同颜色区分)
|
||||
self.text_area.insert(tk.END, speak_text, 'speak_tag')
|
||||
self.text_area.tag_config('speak_tag', foreground="blue")
|
||||
self.text_area.see(tk.END)
|
||||
else:
|
||||
# 正常模式:发给子进程
|
||||
text += '\n'
|
||||
try:
|
||||
self.proc.stdin.write(text.encode(self.encoding, errors='replace'))
|
||||
self.proc.stdin.flush()
|
||||
except Exception as e:
|
||||
self.output_queue.put(f"\n[输入发送失败: {e}]\n")
|
||||
|
||||
def force_terminate(self):
|
||||
"""强制终止子进程"""
|
||||
if self.proc and self.proc.poll() is None:
|
||||
print("用户终止了应用程序") # 输出到控制台 stdout
|
||||
self.output_queue.put("\n[用户终止了应用程序]\n")
|
||||
self.proc.kill()
|
||||
|
||||
def on_closing(self):
|
||||
"""关闭窗口时终止子进程"""
|
||||
if self.proc and self.proc.poll() is None:
|
||||
self.proc.kill()
|
||||
self.root.destroy()
|
||||
|
||||
def update_gui(self):
|
||||
"""主线程:从队列获取数据并更新GUI"""
|
||||
try:
|
||||
while True:
|
||||
text = self.output_queue.get_nowait()
|
||||
self.text_area.insert(tk.END, text)
|
||||
self.text_area.see(tk.END)
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
# 计算并更新无输出时长
|
||||
if self.proc and self.proc.poll() is None:
|
||||
idle_seconds = int(time.time() - self.last_output_time)
|
||||
mins = idle_seconds // 60
|
||||
secs = idle_seconds % 60
|
||||
self.time_label.config(text=f"无输出时长: {mins:02d}:{secs:02d}")
|
||||
else:
|
||||
self.time_label.config(text="进程已结束")
|
||||
|
||||
# 检查进程是否已结束
|
||||
if self.proc and self.proc.poll() is not None:
|
||||
if self.output_queue.empty():
|
||||
self.kill_btn.config(state=tk.DISABLED, bg="grey", text="进程已结束")
|
||||
# 进程结束且输出已读取完毕,自动关闭程序
|
||||
self.root.destroy()
|
||||
return # 停止轮询
|
||||
|
||||
# 每 200ms 轮询一次 (减少CPU占用,时间显示不需要50ms那么频繁)
|
||||
self.root.after(200, self.update_gui)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: python ai_console.py <command> [args...]")
|
||||
print("示例: python ai_console.py cmd")
|
||||
sys.exit(1)
|
||||
|
||||
root = tk.Tk()
|
||||
app = ConsoleApp(root, sys.argv[1:])
|
||||
root.mainloop()
|
||||
Reference in New Issue
Block a user