Python+QT5打造智能桌面助手
我接下来要用Python和QT5打造属于你的智能桌面AI助手精灵,首先我在网络上用AI提取了灵感如下:
---------------------------------------------------------------------------------------------------------------------------------
1.1 桌面助手精灵的概念与应用场景 * 定义:一款运行在用户桌面上,提供便捷交互、信息查询、任务提醒、自动化操作等功能的智能软件。 * 典型功能举例:语音交互、日程管理、快速搜索、系统信息展示、自动化脚本执行、个性化提醒等。 * 应用价值:提高工作效率、简化操作流程、提供个性化服务。 * 1.2 为什么选择Python和QT5? * Python的优势: 语法简洁、开发效率高、拥有庞大的第三方库生态(如语音识别、自然语言处理、网络请求、自动化脚本等)。 * QT5的优势: 成熟的跨平台GUI框架、强大的UI设计能力(Qt Designer)、丰富的控件库、良好的信号槽机制支持事件驱动开发、稳定的性能。 * 完美组合: Python处理逻辑和后台任务,QT5构建美观易用的前端界面。
II. 开发环境搭建 * 2.1 安装Python * 推荐使用Python 3.x版本。 * 2.2 安装PyQT5或PySide6库 * 介绍PyQt5或PySide6(两者都是QT5的Python绑定)。 * 使用pip安装:pip install pyqt5 或 pip install pyside6。 * 2.3 安装Qt Designer (可选但推荐) * 介绍Qt Designer的作用:可视化设计UI界面,生成.ui文件。 * 如何获取(通常随PyQT5/PySide6安装或单独安装)。
III. 项目结构与基础框架 * 3.1 创建项目目录 * 3.2 设计主窗口 (MainWindow) * 使用Qt Designer设计主界面或纯代码编写。 * 关键元素:系统托盘图标(QSystemTrayIcon)、主窗口布局。 * 3.3 系统托盘功能实现 * 创建托盘图标,设置图标、提示信息。 * 添加快捷菜单(QMenu):例如“显示/隐藏”、“设置”、“退出”等选项。 * 实现托盘图标的点击事件(激活/显示主窗口)。 * 3.4 主窗口的显示与隐藏逻辑 * 点击托盘图标显示主窗口。 * 主窗口失去焦点或点击关闭按钮时隐藏至托盘(而非退出)。
IV. 核心功能模块设计与实现 * 4.1 语音交互模块 (输入与输出) * 语音识别: * 集成语音识别库(如SpeechRecognition + pyaudio)。 * 实现麦克风监听、语音捕获与识别成文本。 * 语音合成: * 集成语音合成库(如pyttsx3)。 * 将文本回复转换成语音播报给用户。 * 指令解析: 设计简单的规则或集成NLP库(如NLTK, spaCy)解析用户语音指令的意图。 * 4.2 信息查询与展示模块 * 天气信息: 调用天气API(如和风、OpenWeatherMap),解析并展示。 * 时间与日期: 实时显示。 * 系统信息: 获取并显示CPU、内存使用情况等(使用psutil库)。 * 快速搜索: 集成网络搜索(调用浏览器或API)。 * 界面设计: 在主窗口中设计区域展示这些信息。 * 4.3 任务与提醒模块 * 提醒管理: * 设计数据结构存储提醒(时间、内容)。 * 实现提醒的添加、编辑、删除功能(界面 + 逻辑)。 * 使用QTimer定时检查,到达设定时间后触发提醒(弹窗、语音)。 * 简易任务列表: 提供待办事项的增删改查功能。 * 4.4 自动化操作模块 * 基础操作: 使用pyautogui, pywinauto等库模拟鼠标键盘操作,实现如打开程序、文件等。 * 插件机制 (可选进阶): 设计插件接口,允许用户编写自定义脚本扩展助手功能。 * 4.5 设置与配置模块 * 设置界面: 使用Qt Designer设计。 * 可配置项: 如开机启动、语音唤醒词、默认城市、
API密钥、主题颜色等。 * 配置存储: 使用QSettings或文件(如JSON)存储用户配置。
V. 界面美化与交互优化 * 5.1 使用Qt样式表 * 介绍QSS,用于自定义控件外观(颜色、字体、边框等)。 * 5.2 动画效果 * 使用QPropertyAnimation等为窗口显示/隐藏、状态切换添加平滑动画。 * 5.3 托盘图标与菜单美化 * 5.4 响应式设计考虑
VI. 数据持久化 * 6.1 存储选择 * 提醒、任务数据:使用轻量级数据库(SQLite)或文件存储(JSON)。 * 用户配置:使用QSettings(注册表/.ini)或自定义文件。 * 6.2 实现数据读写逻辑
VII. 打包与发布 * 7.1 使用PyInstaller打包 * 将Python脚本打包成可执行文件(.exe等)。 * 处理依赖项和资源文件(图标、声音等)。 * 7.2 创建安装程序 (可选) * 使用Inno Setup等工具制作安装包。 * 7.3 设置开机自启动 * 在打包时或首次运行时实现。
VIII. 总结与展望 * 8.1 项目回顾 * 总结使用Python和QT5开发桌面助手的核心技术和步骤。 * 8.2 功能扩展方向 * 更强大的NLP理解。 * 机器学习个性化推荐。 * 更多自动化场景集成。 * 跨设备同步。 * 社区插件生态。 * 8.3 鼓励读者动手实践
------------------------------------------------------------------------------------------------------------------------------
以上是CSDN AI生成的大岗,本人通过以上提示做出了类似的桌面QT5程序,以下是功能介绍和代码示例,如果喜欢可以上手试试。
- 语音唤醒识别(Vosk)
- (vosk-model-cn-0.22语音识别转文字需要下载离线包到本地)
- AI对话处理(Ollama)
- TTS语音播报(pyttsx3)
- 系统托盘控制
- 界面皮肤系统
- 语音打断机制
- 配置管理
- 自动隐藏界面
代 码 如 下,哈哈。
#encoding:utf-8
import os
import sys
import time
import json
import queue
import threading
import re
import requests
import pyaudio
import pyttsx3
from vosk import Model, KaldiRecognizer
from datetime import datetime
from typing import List, Dict
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QVBoxLayout,
QDesktopWidget, QMenu, QSystemTrayIcon,
QStyle, QDialog, QHBoxLayout, QSlider,
QPushButton, QComboBox, QColorDialog,
QCheckBox, QSpinBox, QGroupBox, QFileDialog,
QMessageBox, QGridLayout)
from PyQt5.QtCore import Qt, QTimer, QPropertyAnimation, QEasingCurve, QThread, pyqtSignal
from PyQt5.QtGui import QPalette, QColor, QFont, QPainter, QRadialGradient, QPen, QBrush, QPixmap
# ==============================
# 配置管理类
# ==============================
class ConfigManager:
def __init__(self):
self.config_file = "voice_assistant_config.json"
self.default_config = {
"model_path": "D:\\vosk\\vosk-model-cn-0.22\\vosk-model-cn-0.22",
"preferred_model": "qwen3:8b",
"tts_rate": 200,
"tts_volume": 0.9,
"ui_theme": "dark",
"animation_speed": "normal",
"max_conversation_history": 50,
"wake_words": ["激活","出来", "助手", "你", "贾维斯", "小助手", "语音助手"],
"hide_words": ["隐藏", "关闭", "关闭助手", "退下", "消失"],
"interrupt_words": ["停", "停停停", "我知道了", "晓得", "可以了", "够了", "别说了",
"停止", "stop", "闭嘴", "安静", "可以停了", "可以了可以了"],
"ui_skins": {
"default": {
"bg_color": [100, 100, 200],
"text_color": [255, 255, 255],
"accent_color": [100, 255, 200]
},
"blue_tech": {
"bg_color": [30, 30, 60],
"text_color": [100, 200, 255],
"accent_color": [0, 150, 255]
},
"green_nature": {
"bg_color": [30, 60, 30],
"text_color": [200, 255, 200],
"accent_color": [100, 255, 100]
},
"purple_magic": {
"bg_color": [60, 30, 60],
"text_color": [255, 200, 255],
"accent_color": [200, 100, 255]
}
},
"current_skin": "default",
"auto_hide": False,
"auto_hide_timeout": 30
}
self.config = self.load_config()
def load_config(self):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
loaded_config = json.load(f)
# 合并配置,确保新版本有默认值
return {**self.default_config, **loaded_config}
except:
return self.default_config
def save_config(self):
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"保存配置失败: {e}")
return False
# ==============================
# 设置对话框(增强版)
# ==============================
class EnhancedSettingsDialog(QDialog):
def __init__(self, config_manager, parent=None):
super().__init__(parent)
self.config_manager = config_manager
self.init_ui()
def init_ui(self):
self.setWindowTitle("语音助手设置")
self.setFixedSize(500, 650) # 增加高度以容纳更多设置
layout = QVBoxLayout()
# AI模型设置
model_group = QGroupBox("AI设置")
model_layout = QGridLayout()
model_layout.addWidget(QLabel("AI模型:"), 0, 0)
self.model_combo = QComboBox()
self.model_combo.addItems(["qwen3:8b", "qwen2:7b", "llama3:8b", "gemma2:2b", "deepseek-coder:6.7b"])
self.model_combo.setCurrentText(self.config_manager.config['preferred_model'])
model_layout.addWidget(self.model_combo, 0, 1, 1, 2)
model_group.setLayout(model_layout)
layout.addWidget(model_group)
# TTS设置
tts_group = QGroupBox("语音设置")
tts_layout = QGridLayout()
# 语速设置
tts_layout.addWidget(QLabel("语速:"), 0, 0)
self.speed_slider = QSlider(Qt.Horizontal)
self.speed_slider.setRange(100, 300)
self.speed_slider.setValue(self.config_manager.config['tts_rate'])
self.speed_value = QLabel(str(self.config_manager.config['tts_rate']))
self.speed_slider.valueChanged.connect(lambda v: self.speed_value.setText(str(v)))
tts_layout.addWidget(self.speed_slider, 0, 1)
tts_layout.addWidget(self.speed_value, 0, 2)
# 音量设置
tts_layout.addWidget(QLabel("音量:"), 100, 0)
self.volume_slider = QSlider(Qt.Horizontal)
self.volume_slider.setRange(0, 100)
self.volume_slider.setValue(int(self.config_manager.config['tts_volume'] * 100))
self.volume_value = QLabel(f"{int(self.config_manager.config['tts_volume'] * 100)}%")
self.volume_slider.valueChanged.connect(lambda v: self.volume_value.setText(f"{v}%"))
tts_layout.addWidget(self.volume_slider, 1, 1)
tts_layout.addWidget(self.volume_value, 1, 2)
tts_group.setLayout(tts_layout)
layout.addWidget(tts_group)
# 唤醒词设置
wake_group = QGroupBox("语音命令设置")
wake_layout = QGridLayout()
wake_layout.addWidget(QLabel("唤醒词(用逗号分隔):"), 0, 0)
self.wake_edit = QLineEdit(','.join(self.config_manager.config['wake_words']))
wake_layout.addWidget(self.wake_edit, 0, 1, 1, 2)
wake_layout.addWidget(QLabel("隐藏词(用逗号分隔):"), 1, 0)
self.hide_edit = QLineEdit(','.join(self.config_manager.config['hide_words']))
wake_layout.addWidget(self.hide_edit, 1, 1, 1, 2)
wake_layout.addWidget(QLabel("打断词(用逗号分隔):"), 2, 0)
self.interrupt_edit = QLineEdit(','.join(self.config_manager.config['interrupt_words']))
wake_layout.addWidget(self.interrupt_edit, 2, 1, 1, 2)
# 打断词说明
interrupt_info = QLabel("注:在语音回答时说出上述词语可以立即停止播放")
interrupt_info.setStyleSheet("color: gray; font-size: 10px;")
wake_layout.addWidget(interrupt_info, 3, 0, 1, 3)
wake_group.setLayout(wake_layout)
layout.addWidget(wake_group)
# UI设置
ui_group = QGroupBox("界面设置")
ui_layout = QGridLayout()
# 皮肤选择
ui_layout.addWidget(QLabel("选择皮肤:"), 0, 0)
self.skin_combo = QComboBox()
self.skin_combo.addItems(list(self.config_manager.config['ui_skins'].keys()))
self.skin_combo.setCurrentText(self.config_manager.config['current_skin'])
self.skin_combo.currentTextChanged.connect(self.on_skin_changed)
ui_layout.addWidget(self.skin_combo, 0, 1)
# 自定义颜色按钮
self.custom_color_btn = QPushButton("自定义颜色")
self.custom_color_btn.clicked.connect(self.customize_colors)
ui_layout.addWidget(self.custom_color_btn, 0, 2)
# 自动隐藏设置
self.auto_hide_check = QCheckBox("自动隐藏界面")
self.auto_hide_check.setChecked(self.config_manager.config['auto_hide'])
self.timeout_spin = QSpinBox()
self.timeout_spin.setRange(10, 300)
self.timeout_spin.setValue(self.config_manager.config['auto_hide_timeout'])
self.timeout_spin.setSuffix(" 秒")
ui_layout.addWidget(self.auto_hide_check, 1, 0)
ui_layout.addWidget(QLabel("超时时间:"), 1, 1)
ui_layout.addWidget(self.timeout_spin, 1, 2)
ui_group.setLayout(ui_layout)
layout.addWidget(ui_group)
# 按钮
button_layout = QHBoxLayout()
save_btn = QPushButton("保存")
cancel_btn = QPushButton("取消")
save_btn.clicked.connect(self.save_settings)
cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(save_btn)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
self.setLayout(layout)
def on_skin_changed(self, skin_name):
"""皮肤切换预览"""
print(f"切换到皮肤: {skin_name}")
def customize_colors(self):
"""自定义颜色"""
current_skin = self.config_manager.config['current_skin']
skin_data = self.config_manager.config['ui_skins'][current_skin]
# 背景颜色选择
bg_color = QColorDialog.getColor(QColor(*skin_data['bg_color']), self, "选择背景颜色")
if bg_color.isValid():
skin_data['bg_color'] = [bg_color.red(), bg_color.green(), bg_color.blue()]
# 文字颜色选择
text_color = QColorDialog.getColor(QColor(*skin_data['text_color']), self, "选择文字颜色")
if text_color.isValid():
skin_data['text_color'] = [text_color.red(), text_color.green(), text_color.blue()]
# 强调颜色选择
accent_color = QColorDialog.getColor(QColor(*skin_data['accent_color']), self, "选择强调颜色")
if accent_color.isValid():
skin_data['accent_color'] = [accent_color.red(), accent_color.green(), accent_color.blue()]
def save_settings(self):
# AI设置
self.config_manager.config['preferred_model'] = self.model_combo.currentText()
# TTS设置
self.config_manager.config['tts_rate'] = self.speed_slider.value()
self.config_manager.config['tts_volume'] = self.volume_slider.value() / 100
# 语音命令设置
self.config_manager.config['wake_words'] = [w.strip() for w in self.wake_edit.text().split(',') if w.strip()]
self.config_manager.config['hide_words'] = [w.strip() for w in self.hide_edit.text().split(',') if w.strip()]
self.config_manager.config['interrupt_words'] = [w.strip() for w in self.interrupt_edit.text().split(',') if w.strip()]
# UI设置
self.config_manager.config['current_skin'] = self.skin_combo.currentText()
self.config_manager.config['auto_hide'] = self.auto_hide_check.isChecked()
self.config_manager.config['auto_hide_timeout'] = self.timeout_spin.value()
if self.config_manager.save_config():
self.accept()
else:
QMessageBox.warning(self, "错误", "保存配置失败")
# ==============================
# TTS管理器(增强版,支持打断)
# ==============================
class TTSManager(threading.Thread):
def __init__(self, config_manager):
super().__init__()
self.config_manager = config_manager
self.tts_queue = queue.Queue()
self._stop_event = threading.Event()
self.engine = None
self.daemon = True
self.is_speaking = False
self.current_text = ""
self.mutex = threading.Lock()
def run(self):
try:
self.engine = pyttsx3.init()
self.engine.setProperty('rate', self.config_manager.config['tts_rate'])
self.engine.setProperty('volume', self.config_manager.config['tts_volume'])
while not self._stop_event.is_set():
try:
text = self.tts_queue.get(timeout=1.0)
if text is None:
break
if text.strip():
print(f"🔊 TTS 准备朗读: {text[:50]}...")
with self.mutex:
self.current_text = text
self.is_speaking = True
self.engine.say(text)
self.engine.runAndWait()
with self.mutex:
self.is_speaking = False
self.current_text = ""
except queue.Empty:
continue
except Exception as e:
print(f"TTS Error: {e}")
with self.mutex:
self.is_speaking = False
self.current_text = ""
finally:
if self.engine:
try:
self.engine.stop()
except:
pass
def speak(self, text):
"""添加文本到TTS队列"""
self.tts_queue.put(text)
def stop_speaking(self):
"""立即停止当前说话的语音"""
with self.mutex:
if self.is_speaking and self.engine:
print("🛑 用户打断了TTS语音")
self.engine.stop()
self.is_speaking = False
self.current_text = ""
# 清空队列中的所有待播放内容
while not self.tts_queue.empty():
try:
self.tts_queue.get_nowait()
except queue.Empty:
break
return True
return False
def is_currently_speaking(self):
"""检查是否正在说话"""
with self.mutex:
return self.is_speaking
def stop(self):
"""停止TTS线程"""
self._stop_event.set()
self.tts_queue.put(None)
# ==============================
# 语音识别器
# ==============================
class SpeechRecognizer(QThread):
text_recognized = pyqtSignal(str)
error_occurred = pyqtSignal(str)
def __init__(self, config_manager):
super().__init__()
self.config_manager = config_manager
self.running = True
self.p = pyaudio.PyAudio()
self.model = None
self.recognizer = None
self.stream = None
def load_model(self):
try:
model_path = self.config_manager.config['model_path']
self.model = Model(model_path)
self.recognizer = KaldiRecognizer(self.model, 16000)
print("✅ Vosk 中文模型加载成功")
return True
except Exception as e:
error_msg = f"❌ 模型加载失败: {e}"
self.error_occurred.emit(error_msg)
return False
def run(self):
if not self.load_model():
return
try:
self.stream = self.p.open(
format=pyaudio.paInt16,
channels=1,
rate=16000,
input=True,
frames_per_buffer=1024
)
print("🎙️ 开始持续监听语音...")
while self.running:
data = self.stream.read(1024, exception_on_overflow=False)
if self.recognizer.AcceptWaveform(data):
result = json.loads(self.recognizer.Result())
text = result.get("text", "").strip()
if text:
print(f"💬 识别结果: {text}")
self.text_recognized.emit(text)
time.sleep(0.01)
except Exception as e:
error_msg = f"❌ 语音识别出错: {e}"
self.error_occurred.emit(error_msg)
finally:
self.stop()
def stop(self):
self.running = False
if self.stream:
self.stream.stop_stream()
self.stream.close()
if self.p:
self.p.terminate()
print("🛑 语音识别已停止")
# ==============================
# 增强版监听指示器(支持皮肤)
# ==============================
class EnhancedListeningIndicator(QWidget):
def __init__(self, config_manager):
super().__init__()
self.config_manager = config_manager
self.current_skin = self.config_manager.config['current_skin']
self.setWindowFlags(
Qt.FramelessWindowHint |
Qt.WindowStaysOnTopHint |
Qt.Tool |
Qt.NoDropShadowWindowHint
)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setFixedSize(250, 250)
self.center_on_screen()
# 状态定义
self.states = {
'idle': {'text': '🎤 等待唤醒', 'icon': '🎤'},
'listening': {'text': '🔊 聆听中...', 'icon': '🔊'},
'processing': {'text': '🤖 思考中', 'icon': '🤖'},
'speaking': {'text': '🗣️ 回复中', 'icon': '🗣️'},
'error': {'text': '❌ 错误', 'icon': '❌'},
'interrupted': {'text': '⏹️ 已打断', 'icon': '⏹️'}
}
self.current_state = 'idle'
self.dragging = False
self.init_ui()
self.init_animations()
self.apply_skin()
def init_ui(self):
# 状态标签
self.status_label = QLabel(self.states['idle']['text'], self)
self.status_label.setAlignment(Qt.AlignCenter)
self.status_label.setGeometry(15, 15, 220, 30)
# 内容显示标签
self.content_label = QLabel("", self)
self.content_label.setAlignment(Qt.AlignCenter)
self.content_label.setGeometry(15, 50, 220, 180)
self.content_label.setWordWrap(True)
self.content_label.setTextFormat(Qt.PlainText)
def init_animations(self):
# 呼吸动画
self.opacity_anim = QPropertyAnimation(self, b"windowOpacity")
self.opacity_anim.setDuration(2000)
self.opacity_anim.setStartValue(0.6)
self.opacity_anim.setEndValue(1.0)
self.opacity_anim.setLoopCount(-1)
self.opacity_anim.setEasingCurve(QEasingCurve.InOutSine)
self.opacity_anim.start()
# 旋转动画
self.rotation = 0
self.rotation_timer = QTimer(self)
self.rotation_timer.timeout.connect(self.update_rotation)
self.rotation_timer.start(30)
def apply_skin(self):
"""应用当前皮肤"""
skin_data = self.config_manager.config['ui_skins'][self.current_skin]
bg_color = QColor(*skin_data['bg_color'])
text_color = QColor(*skin_data['text_color'])
accent_color = QColor(*skin_data['accent_color'])
# 设置标签样式
style_sheet = f"""
QLabel {{
color: rgba{text_color.getRgb()};
font-size: 14px;
font-family: 'Microsoft YaHei', 'PingFang SC';
background: transparent;
border: none;
}}
"""
self.status_label.setStyleSheet(style_sheet)
self.content_label.setStyleSheet(style_sheet.replace('14px', '12px'))
self.bg_color = bg_color
self.accent_color = accent_color
self.update()
def change_skin(self, skin_name):
"""切换皮肤"""
if skin_name in self.config_manager.config['ui_skins']:
self.current_skin = skin_name
self.apply_skin()
def center_on_screen(self):
screen = QDesktopWidget().screenGeometry()
self.move(
screen.center().x() - self.width() // 2,
screen.center().y() - self.height() // 2
)
def update_rotation(self):
self.rotation = (self.rotation + 2) % 360
self.update()
def set_state(self, state, content=""):
if state in self.states:
self.current_state = state
self.status_label.setText(self.states[state]['text'])
if content:
# 智能文本截断
if len(content) > 120:
content = content[:117] + "..."
self.content_label.setText(content)
else:
self.content_label.setText("")
self.update()
# 鼠标事件支持拖动
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragging = True
self.mouse_offset = event.globalPos() - self.frameGeometry().topLeft()
event.accept()
def mouseMoveEvent(self, event):
if self.dragging and event.buttons() & Qt.LeftButton:
new_pos = event.globalPos() - self.mouse_offset
self.move(new_pos)
event.accept()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragging = False
event.accept()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
center = self.rect().center()
radius = int(min(self.width(), self.height()) / 2 - 15)
# 创建渐变背景
radial_grad = QRadialGradient(center, radius)
radial_grad.setColorAt(0, self.bg_color.lighter(180))
radial_grad.setColorAt(0.7, self.bg_color)
radial_grad.setColorAt(1, self.bg_color.darker(150))
# 绘制主圆形
painter.setBrush(QBrush(radial_grad))
painter.setPen(QPen(QColor(0, 0, 0, 100), 3))
painter.drawEllipse(center, radius, radius)
# 绘制旋转光效
painter.save()
painter.translate(center)
painter.rotate(self.rotation)
# 根据状态改变光效颜色
if self.current_state == 'listening':
arc_color = QColor(100, 255, 200, 150)
elif self.current_state == 'speaking':
arc_color = QColor(255, 200, 100, 150)
elif self.current_state == 'processing':
arc_color = QColor(100, 200, 255, 150)
elif self.current_state == 'interrupted':
arc_color = QColor(255, 150, 50, 150)
else:
arc_color = QColor(255, 255, 255, 100)
arc_pen = QPen(arc_color, 8)
arc_pen.setCapStyle(Qt.RoundCap)
painter.setPen(arc_pen)
painter.drawArc(-radius, -radius, radius * 2, radius * 2, 0, 120 * 16)
painter.restore()
# 绘制外边框
border_pen = QPen(self.accent_color, 2)
border_pen.setCapStyle(Qt.RoundCap)
painter.setPen(border_pen)
painter.setBrush(Qt.NoBrush)
painter.drawEllipse(center, radius, radius)
# 绘制内圆点缀
painter.setPen(QPen(self.accent_color.lighter(150), 1))
painter.drawEllipse(center, radius-10, radius-10)
# ==============================
# AI聊天处理器
# ==============================
class AIChatProcessor(QThread):
response_received = pyqtSignal(str)
token_received = pyqtSignal(str)
finished_signal = pyqtSignal()
error_signal = pyqtSignal(str)
def __init__(self, model_name, prompt, conversation_history=None):
super().__init__()
self.model_name = model_name
self.prompt = prompt
self.conversation_history = conversation_history or []
def remove_think_tags(self, text: str) -> str:
"""移除 <think>...</think> 标签"""
cleaned_text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)
cleaned_text = re.sub(r'\n\s*\n', '\n\n', cleaned_text)
return cleaned_text.strip()
def build_prompt(self):
"""构建包含对话历史的提示词"""
if not self.conversation_history:
return f"用户: {self.prompt}\n助手:"
history_text = "\n".join([f"{role}: {content}"
for role, content in self.conversation_history[-6:]])
return f"{history_text}\n用户: {self.prompt}\n助手:"
def run(self):
prompt = self.build_prompt()
try:
params = {
"model": self.model_name,
"prompt": prompt,
"max_tokens": 500,
"temperature": 0.7,
"stream": True
}
response = requests.post(
"http://localhost:11434/api/generate",
json=params,
headers={"Content-Type": "application/json"},
stream=True,
timeout=30
)
response.raise_for_status()
full_response = ""
buffer = ""
for line in response.iter_lines():
if line:
try:
data = json.loads(line.decode("utf-8"))
if "response" in data:
chunk = data["response"]
full_response += chunk
buffer += chunk
# 处理句子边界
if any(mark in chunk for mark in ['。', '!', '?', '.', '!', '?','*']):
clean_buffer = self.remove_think_tags(buffer)
if clean_buffer.strip():
self.token_received.emit(clean_buffer)
buffer = ""
except Exception as e:
print(f"处理流数据出错: {e}")
continue
# 处理剩余缓冲区内容
if buffer.strip():
clean_buffer = self.remove_think_tags(buffer.strip())
if clean_buffer.strip():
self.token_received.emit(clean_buffer)
# 发送完整响应
cleaned_response = self.remove_think_tags(full_response)
self.response_received.emit(cleaned_response)
except requests.exceptions.ConnectionError:
error_msg = "连接失败: 无法连接到 Ollama 服务"
self.error_signal.emit(error_msg)
except Exception as e:
error_msg = f"AI处理失败: {str(e)}"
self.error_signal.emit(error_msg)
finally:
self.finished_signal.emit()
# ==============================
# 主应用类(增强版,支持语音打断)
# ==============================
class VoiceAssistantApp(QWidget):
def __init__(self):
super().__init__()
self.config_manager = ConfigManager()
self.conversation_history = []
self.current_state = 'idle'
self.init_ui()
self.init_services()
self.init_timers()
def init_ui(self):
"""初始化UI"""
# 创建增强版指示器(支持皮肤)
self.indicator = EnhancedListeningIndicator(self.config_manager)
# 默认隐藏界面
self.indicator.hide()
# 初始化系统托盘
self.init_tray()
# 隐藏主窗口
self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setGeometry(0, 0, 1, 1)
def init_tray(self):
"""初始化系统托盘"""
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume))
self.tray_icon.setToolTip("智能语音助手 - 等待唤醒")
self.create_tray_menu()
self.tray_icon.show()
# 启动通知
self.tray_icon.showMessage(
"智能语音助手",
"程序已启动,等待唤醒词...\n唤醒词: " + ", ".join(self.config_manager.config['wake_words']) +
"\n打断词: " + ", ".join(self.config_manager.config['interrupt_words'][:3]) + "...",
QSystemTrayIcon.Information,
5000
)
def create_tray_menu(self):
"""创建托盘菜单"""
menu = QMenu()
# 状态显示
status_action = menu.addAction("🟢 运行中 - 等待唤醒")
status_action.setEnabled(False)
menu.addSeparator()
# 显示/隐藏界面
show_action = menu.addAction("👁️ 显示界面")
show_action.triggered.connect(self.show_interface)
hide_action = menu.addAction("🙈 隐藏界面")
hide_action.triggered.connect(self.hide_interface)
menu.addSeparator()
# 模型选择子菜单
model_menu = menu.addMenu("🤖 AI模型选择")
models = ["qwen3:8b", "qwen2:7b", "llama3:8b", "gemma2:2b", "deepseek-coder:6.7b"]
for model in models:
model_action = model_menu.addAction(model)
model_action.triggered.connect(lambda checked, m=model: self.change_model(m))
# 皮肤选择子菜单
skin_menu = menu.addMenu("🎨 界面皮肤")
for skin_name in self.config_manager.config['ui_skins'].keys():
skin_action = skin_menu.addAction(skin_name)
skin_action.triggered.connect(lambda checked, s=skin_name: self.change_skin(s))
# 设置
settings_action = menu.addAction("⚙️ 设置")
settings_action.triggered.connect(self.show_settings)
menu.addSeparator()
# 对话历史
history_action = menu.addAction("📝 查看对话历史")
history_action.triggered.connect(self.show_conversation_history)
# 清空历史
clear_action = menu.addAction("🗑️ 清空对话历史")
clear_action.triggered.connect(self.clear_conversation_history)
menu.addSeparator()
exit_action = menu.addAction("❌ 退出")
exit_action.triggered.connect(self.exit_app)
self.tray_icon.setContextMenu(menu)
def init_services(self):
"""初始化服务"""
# 语音识别
self.speech_recognizer = SpeechRecognizer(self.config_manager)
self.speech_recognizer.text_recognized.connect(self.on_speech_recognized)
self.speech_recognizer.error_occurred.connect(self.on_speech_error)
self.speech_recognizer.start()
# TTS服务(增强版,支持打断)
self.tts_manager = TTSManager(self.config_manager)
self.tts_manager.start()
def init_timers(self):
"""初始化定时器"""
# 自动隐藏定时器
self.last_activity_time = time.time()
self.auto_hide_timer = QTimer(self)
self.auto_hide_timer.timeout.connect(self.check_auto_hide)
self.auto_hide_timer.start(1000) # 每秒检查一次
def check_auto_hide(self):
"""检查自动隐藏"""
if (self.config_manager.config['auto_hide'] and
self.indicator.isVisible() and
self.current_state not in ['listening', 'processing', 'speaking']):
current_time = time.time()
timeout = self.config_manager.config['auto_hide_timeout']
if current_time - self.last_activity_time > timeout:
self.hide_interface()
def change_model(self, model_name):
"""切换AI模型"""
self.config_manager.config['preferred_model'] = model_name
if self.config_manager.save_config():
self.tray_icon.showMessage("模型已切换", f"当前模型: {model_name}",
QSystemTrayIcon.Information, 2000)
def change_skin(self, skin_name):
"""切换皮肤"""
if skin_name in self.config_manager.config['ui_skins']:
self.config_manager.config['current_skin'] = skin_name
self.config_manager.save_config()
self.indicator.change_skin(skin_name)
self.tray_icon.showMessage("皮肤已切换", f"当前皮肤: {skin_name}",
QSystemTrayIcon.Information, 2000)
def show_settings(self):
"""显示设置对话框"""
dialog = EnhancedSettingsDialog(self.config_manager, self)
if dialog.exec_() == QDialog.Accepted:
# 应用新的设置
self.tts_manager.stop()
self.tts_manager.start()
self.indicator.change_skin(self.config_manager.config['current_skin'])
def show_interface(self):
"""显示界面"""
self.indicator.show()
self.indicator.set_state('idle', "界面已显示")
self.last_activity_time = time.time()
def hide_interface(self):
"""隐藏界面"""
self.indicator.hide()
self.tray_icon.showMessage("界面已隐藏", "界面已最小化到系统托盘",
QSystemTrayIcon.Information, 2000)
def show_conversation_history(self):
"""显示对话历史"""
history_text = "\n".join([f"{role}: {content}"
for role, content in self.conversation_history[-10:]])
if not history_text:
history_text = "暂无对话历史"
self.tray_icon.showMessage("最近对话", history_text,
QSystemTrayIcon.Information, 5000)
def clear_conversation_history(self):
"""清空对话历史"""
self.conversation_history.clear()
self.tray_icon.showMessage("历史已清空", "对话历史已被清除",
QSystemTrayIcon.Information, 2000)
def process_wake_command(self, text):
"""处理唤醒命令"""
wake_words = self.config_manager.config['wake_words']
for word in wake_words:
if word.lower() in text.lower():
return True
return False
def process_hide_command(self, text):
"""处理隐藏命令"""
hide_words = self.config_manager.config['hide_words']
for word in hide_words:
if word.lower() in text.lower():
return True
return False
def process_interrupt_command(self, text):
"""处理打断命令"""
interrupt_words = self.config_manager.config['interrupt_words']
for word in interrupt_words:
if word.lower() in text.lower():
return True
return False
def on_speech_recognized(self, text):
"""处理语音识别结果"""
print(f"🎯 语音输入: {text}")
# 更新活动时间
self.last_activity_time = time.time()
# 检查打断命令(优先级最高)
if self.process_interrupt_command(text) and self.tts_manager.is_currently_speaking():
print("🛑 用户发出了打断命令")
if self.tts_manager.stop_speaking():
self.indicator.set_state('interrupted', "语音已中断")
self.current_state = 'idle'
return
# 检查唤醒命令
if self.process_wake_command(text) and not self.indicator.isVisible():
self.show_interface()
self.indicator.set_state('idle', "已唤醒,请说话...")
self.tray_icon.showMessage("已唤醒", "助手已唤醒,请继续说话",
QSystemTrayIcon.Information, 2000)
return
# 检查隐藏命令
if self.process_hide_command(text) and self.indicator.isVisible():
self.hide_interface()
return
# 如果界面未显示,忽略其他命令
if not self.indicator.isVisible():
return
# 如果在说话时收到其他命令,先停止说话
if self.tts_manager.is_currently_speaking():
print("🛑 正在说话时收到新命令,停止当前语音")
self.tts_manager.stop_speaking()
self.indicator.set_state('interrupted', "中断以处理新命令")
time.sleep(0.5) # 短暂延迟
# 正常对话处理
self.indicator.set_state('listening', "正在聆听...")
# 添加到对话历史
self.conversation_history.append(("用户", text))
# 启动AI处理
self.indicator.set_state('processing', "正在思考...")
self.current_state = 'processing'
self.chat_processor = AIChatProcessor(
self.config_manager.config['preferred_model'],
text,
self.conversation_history
)
self.chat_processor.response_received.connect(self.on_ai_response)
self.chat_processor.token_received.connect(self.on_ai_token)
self.chat_processor.error_signal.connect(self.on_ai_error)
self.chat_processor.finished_signal.connect(self.on_ai_finished)
self.chat_processor.start()
def on_speech_error(self, error_msg):
"""处理语音识别错误"""
print(f"❌ 语音识别错误: {error_msg}")
if self.indicator.isVisible():
self.indicator.set_state('error', error_msg)
self.last_activity_time = time.time()
def on_ai_token(self, token):
"""处理AI流式响应"""
if not hasattr(self, '_current_response'):
self._current_response = ""
self._current_response += token
self.current_state = 'speaking'
self.indicator.set_state('speaking', self._current_response)
def on_ai_response(self, response):
"""处理AI完整响应"""
self.conversation_history.append(("助手", response))
# 只有在没有被打断的情况下才播放语音
if not self.process_interrupt_command(response): # 防止AI回复中包含打断词
self.tts_manager.speak(response)
print(f"🤖 AI回复: {response}")
def on_ai_error(self, error):
"""处理AI错误"""
print(f"❌ AI处理错误: {error}")
self.current_state = 'error'
if self.indicator.isVisible():
self.indicator.set_state('error', error)
self.tts_manager.speak("抱歉,处理请求时出现了错误")
self.last_activity_time = time.time()
def on_ai_finished(self):
"""AI处理完成"""
if hasattr(self, '_current_response'):
del self._current_response
self.current_state = 'idle'
if self.indicator.isVisible():
self.indicator.set_state('idle', "就绪")
print("✅ 对话处理完成")
def exit_app(self):
"""退出应用程序"""
print("正在退出应用程序...")
# 停止定时器
self.auto_hide_timer.stop()
# 停止服务
self.speech_recognizer.stop()
self.speech_recognizer.wait(2000)
self.tts_manager.stop()
# 关闭UI
self.indicator.close()
self.tray_icon.hide()
QApplication.quit()
# ==============================
# 主函数
# ==============================
def main():
# 创建应用
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
# 设置应用样式
app.setStyle("Fusion")
palette = QPalette()
palette.setColor(QPalette.Window, QColor(53, 53, 53))
palette.setColor(QPalette.WindowText, Qt.white)
palette.setColor(QPalette.Base, QColor(25, 25, 25))
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
palette.setColor(QPalette.ToolTipBase, Qt.white)
palette.setColor(QPalette.ToolTipText, Qt.white)
palette.setColor(QPalette.Text, Qt.white)
palette.setColor(QPalette.Button, QColor(53, 53, 53))
palette.setColor(QPalette.ButtonText, Qt.white)
palette.setColor(QPalette.BrightText, Qt.red)
palette.setColor(QPalette.Link, QColor(42, 130, 218))
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
palette.setColor(QPalette.HighlightedText, Qt.black)
app.setPalette(palette)
# 创建主窗口
window = VoiceAssistantApp()
# 运行应用
try:
sys.exit(app.exec_())
except Exception as e:
print(f"应用程序异常退出: {e}")
if __name__ == "__main__":
main()
在代码中引用的ollama模型可以根据你电脑的配置自行修改,安装了所有所需的依赖包后这段python代码在windows中完美运行,如需其他功能可以自行修改和优化。
如下图程序成功运行实例:

可以进行个性化设置理论上电脑配置好的话可以24小时运行做你的AI电脑助手:

更多推荐


所有评论(0)