Files
open-xiaoai/packages/server-python/xiaozhi/services/display/gui_display.py
T

285 lines
10 KiB
Python
Raw Normal View History

import threading
import tkinter as tk
from tkinter import ttk
import queue
import logging
import time
from typing import Optional, Callable
from xiaozhi.services.display.base_display import BaseDisplay
class GuiDisplay(BaseDisplay):
def __init__(self):
super().__init__() # 调用父类初始化
"""创建 GUI 界面"""
# 初始化日志
self.logger = logging.getLogger("Display")
# 创建主窗口
self.root = tk.Tk()
self.root.title("小爱音箱接入小智 AI 演示")
self.root.geometry("520x360")
# 在窗口底部添加作者信息
self.author_label = ttk.Label(self.root, text="作者: https://del.wang")
self.author_label.pack(side=tk.BOTTOM, pady=5)
# 让窗口居中显示
self.root.update_idletasks()
width = self.root.winfo_width()
height = self.root.winfo_height()
x = (self.root.winfo_screenwidth() // 2) - (width // 2)
y = (self.root.winfo_screenheight() // 2) - (height // 2)
self.root.geometry(f"+{x}+{y}")
# 状态显示
self.status_frame = ttk.Frame(self.root)
self.status_frame.pack(pady=20)
self.status_label = ttk.Label(self.status_frame, text="状态: 未连接")
self.status_label.pack(side=tk.LEFT)
# 表情显示
self.emotion_label = tk.Label(self.root, text="😊", font=("Segoe UI Emoji", 32))
self.emotion_label.pack(padx=20, pady=20)
# TTS文本显示
self.tts_text_label = ttk.Label(
self.root, text="很高兴认识你!", wraplength=250
)
self.tts_text_label.pack(padx=20, pady=10)
# 控制按钮
self.btn_frame = ttk.Frame(self.root)
self.btn_frame.pack(pady=20)
# 手动模式按钮
self.manual_btn = ttk.Button(self.btn_frame, text="按住说话")
self.manual_btn.bind("<ButtonPress-1>", self._on_manual_button_press)
self.manual_btn.bind("<ButtonRelease-1>", self._on_manual_button_release)
self.manual_btn.pack(side=tk.LEFT, padx=10)
# 打断按钮
self.abort_btn = ttk.Button(
self.btn_frame, text="停止播放", command=self._on_abort_button_click
)
self.abort_btn.pack(side=tk.LEFT, padx=10)
# 对话模式标志
self.auto_mode = False
# 回调函数
self.button_press_callback = None
self.button_release_callback = None
self.status_update_callback = None
self.text_update_callback = None
self.emotion_update_callback = None
self.mode_callback = None
self.auto_callback = None
self.abort_callback = None
# 更新队列
self.update_queue = queue.Queue()
# 运行标志
self._running = True
# 设置窗口关闭处理
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
# 启动更新处理
self.root.after(100, self._process_updates)
def set_callbacks(
self,
press_callback: Optional[Callable] = None,
release_callback: Optional[Callable] = None,
status_callback: Optional[Callable] = None,
text_callback: Optional[Callable] = None,
emotion_callback: Optional[Callable] = None,
mode_callback: Optional[Callable] = None,
auto_callback: Optional[Callable] = None,
abort_callback: Optional[Callable] = None,
):
"""设置回调函数"""
self.button_press_callback = press_callback
self.button_release_callback = release_callback
self.status_update_callback = status_callback
self.text_update_callback = text_callback
self.emotion_update_callback = emotion_callback
self.mode_callback = mode_callback
self.auto_callback = auto_callback
self.abort_callback = abort_callback
def _process_updates(self):
"""处理更新队列"""
try:
while True:
try:
# 非阻塞方式获取更新
update_func = self.update_queue.get_nowait()
update_func()
self.update_queue.task_done()
except queue.Empty:
break
finally:
if self._running:
self.root.after(100, self._process_updates)
def _on_manual_button_press(self, event):
"""手动模式按钮按下事件处理"""
try:
# 更新按钮文本为"松开以停止"
self.manual_btn.config(text="松开以停止")
# 调用回调函数
if self.button_press_callback:
self.button_press_callback()
except Exception as e:
self.logger.error(f"按钮按下回调执行失败: {e}")
def _on_manual_button_release(self, event):
"""手动模式按钮释放事件处理"""
try:
# 更新按钮文本为"按住说话"
self.manual_btn.config(text="按住说话")
# 调用回调函数
if self.button_release_callback:
self.button_release_callback()
except Exception as e:
self.logger.error(f"按钮释放回调执行失败: {e}")
def _on_auto_button_click(self):
"""自动模式按钮点击事件处理"""
try:
if self.auto_callback:
self.auto_callback()
except Exception as e:
self.logger.error(f"自动模式按钮回调执行失败: {e}")
def _on_abort_button_click(self):
"""打断按钮点击事件处理"""
try:
if self.abort_callback:
self.abort_callback()
except Exception as e:
self.logger.error(f"打断按钮回调执行失败: {e}")
def _on_mode_button_click(self):
"""对话模式切换按钮点击事件"""
try:
# 检查是否可以切换模式(通过回调函数询问应用程序当前状态)
if self.mode_callback:
# 如果回调函数返回False,表示当前不能切换模式
if not self.mode_callback(not self.auto_mode):
return
# 切换模式
self.auto_mode = not self.auto_mode
# 更新按钮显示
if self.auto_mode:
# 切换到自动模式
self.update_mode_button_status("自动对话")
# 隐藏手动按钮,显示自动按钮
self.update_queue.put(lambda: self._switch_to_auto_mode())
else:
# 切换到手动模式
self.update_mode_button_status("手动对话")
# 隐藏自动按钮,显示手动按钮
self.update_queue.put(lambda: self._switch_to_manual_mode())
except Exception as e:
self.logger.error(f"模式切换按钮回调执行失败: {e}")
def _switch_to_auto_mode(self):
"""切换到自动模式的UI更新"""
self.manual_btn.pack_forget() # 移除手动按钮
self.auto_btn.pack(
side=tk.LEFT, padx=10, before=self.abort_btn
) # 显示自动按钮,放在打断按钮前面
def _switch_to_manual_mode(self):
"""切换到手动模式的UI更新"""
self.auto_btn.pack_forget() # 移除自动按钮
self.manual_btn.pack(
side=tk.LEFT, padx=10, before=self.abort_btn
) # 显示手动按钮,放在打断按钮前面
def update_status(self, status: str):
"""更新状态文本"""
self.update_queue.put(lambda: self.status_label.config(text=f"状态: {status}"))
def update_text(self, text: str):
"""更新TTS文本"""
self.update_queue.put(lambda: self.tts_text_label.config(text=text))
def update_emotion(self, emotion: str):
"""更新表情"""
self.update_queue.put(lambda: self.emotion_label.config(text=emotion))
def start_update_threads(self):
"""启动更新线程"""
def update_loop():
while self._running:
try:
# 更新状态
if self.status_update_callback:
status = self.status_update_callback()
if status:
self.update_status(status)
# 更新文本
if self.text_update_callback:
text = self.text_update_callback()
if text:
self.update_text(text)
# 更新表情
if self.emotion_update_callback:
emotion = self.emotion_update_callback()
if emotion:
self.update_emotion(emotion)
except Exception as e:
self.logger.error(f"更新失败: {e}")
time.sleep(0.1)
threading.Thread(target=update_loop, daemon=True).start()
def on_close(self):
"""关闭窗口处理"""
self._running = False
self.root.destroy()
def start(self):
"""启动GUI"""
try:
# 启动更新线程
self.start_update_threads()
# 在主线程中运行主循环
self.logger.info("开始启动GUI主循环")
self.root.mainloop()
except Exception as e:
self.logger.error(f"GUI启动失败: {e}", exc_info=True)
# 尝试回退到CLI模式
print(f"GUI启动失败: {e},请尝试使用CLI模式")
def update_mode_button_status(self, text: str):
"""更新模式按钮状态"""
self.update_queue.put(lambda: self.mode_btn.config(text=text))
def update_button_status(self, text: str):
"""更新按钮状态 - 保留此方法以满足抽象基类要求"""
# 根据当前模式更新相应的按钮
if self.auto_mode:
self.update_queue.put(lambda: self.auto_btn.config(text=text))
else:
# 在手动模式下,不通过此方法更新按钮文本
# 因为按钮文本由按下/释放事件直接控制
pass