|
|
#!/usr/bin/env python3
|
|
|
# -*- coding: utf-8 -*-
|
|
|
"""
|
|
|
DeepSeek AI助手 - 主界面模块
|
|
|
|
|
|
该模块实现了DeepSeek AI助手的主界面,基于Tkinter和customtkinter构建。
|
|
|
包含聊天功能、对话管理、日程管理、待办事项管理和用户认证等核心功能的UI整合。
|
|
|
"""
|
|
|
|
|
|
# 导入标准库模块
|
|
|
import tkinter as tk # Tkinter基础GUI库
|
|
|
import customtkinter as ctk # Tkinter扩展,提供现代化UI组件
|
|
|
from datetime import datetime # 日期时间处理
|
|
|
import threading # 多线程支持
|
|
|
import time # 时间操作
|
|
|
import re # 正则表达式
|
|
|
import sys # 系统相关操作
|
|
|
import json # JSON数据处理
|
|
|
import os # 文件系统操作
|
|
|
import uuid # 生成唯一标识符
|
|
|
|
|
|
# 系统托盘相关导入
|
|
|
import pystray # 系统托盘库
|
|
|
from PIL import Image # 图像处理库,用于加载图标
|
|
|
|
|
|
# 导入项目内部模块
|
|
|
from auth.user_auth import UserAuth # 用户认证模块
|
|
|
|
|
|
# 导入拆分的功能模块
|
|
|
from todo.todo_manager import TodoManager # 待办事项管理模块
|
|
|
from schedule.schedule_manager import ScheduleManager # 日程管理模块
|
|
|
from auth.user_auth_ui import UserAuthUI # 用户认证UI模块
|
|
|
from chat.chat_manager import ChatManager # 聊天管理模块
|
|
|
from conversation.conversation_manager import ConversationManager # 对话管理模块
|
|
|
from ui.ui_components import UIComponents # UI组件封装模块
|
|
|
from logger.logger_manager import LoggerManager # 日志管理模块
|
|
|
|
|
|
# 启用高清显示支持(Windows平台)
|
|
|
if sys.platform.startswith('win'):
|
|
|
try:
|
|
|
import ctypes
|
|
|
# 设置DPI感知,以支持高清显示
|
|
|
# 参数1表示PROCESS_SYSTEM_DPI_AWARE,兼容更多Windows版本
|
|
|
ctypes.windll.shcore.SetProcessDpiAwareness(1)
|
|
|
except Exception as e:
|
|
|
# 如果DPI设置失败,打印错误信息但不中断程序
|
|
|
print(f"DPI设置错误: {e}")
|
|
|
|
|
|
# 设置customtkinter主题配置
|
|
|
ctk.set_appearance_mode("dark") # 设置外观模式为深色主题,可选: "light", "dark", "system"
|
|
|
ctk.set_default_color_theme("blue") # 设置颜色主题为蓝色,可选: "blue", "green", "dark-blue"
|
|
|
|
|
|
class DeepSeekChatUI:
|
|
|
"""DeepSeek AI助手主界面类
|
|
|
|
|
|
该类是整个应用的核心UI控制器,负责整合所有功能模块并提供用户交互界面。
|
|
|
"""
|
|
|
|
|
|
def __init__(self, root, api, logger_manager):
|
|
|
"""初始化聊天UI
|
|
|
|
|
|
参数:
|
|
|
root: Tkinter主窗口对象
|
|
|
api: DeepSeekAPI客户端实例
|
|
|
logger_manager: 日志管理器实例
|
|
|
"""
|
|
|
# 保存主窗口、API客户端和日志管理器引用
|
|
|
self.root = root
|
|
|
self.api = api
|
|
|
self.logger_manager = logger_manager
|
|
|
|
|
|
# 更新日志管理器的app和root属性
|
|
|
self.logger_manager.app = self
|
|
|
self.logger_manager.root = root
|
|
|
|
|
|
# 初始化用户认证模块
|
|
|
self.auth = UserAuth()
|
|
|
|
|
|
# 会话管理相关变量
|
|
|
self.current_session_id = None # 当前会话ID
|
|
|
self.current_username = None # 当前登录用户名
|
|
|
|
|
|
# 聊天数据相关变量
|
|
|
self.messages = [] # 当前对话的消息列表
|
|
|
self.chat_histories = {} # 所有对话的历史记录
|
|
|
self.current_conversation_id = None # 当前活动对话ID
|
|
|
self.conversations = [] # 对话ID列表
|
|
|
|
|
|
# 系统托盘相关变量
|
|
|
self.tray_icon = None # 托盘图标对象
|
|
|
self.is_minimized_to_tray = False # 是否已最小化到托盘
|
|
|
|
|
|
# 初始化各个功能模块
|
|
|
self.todo_manager = TodoManager(self) # 待办事项管理模块
|
|
|
self.schedule_manager = ScheduleManager(self) # 日程管理模块
|
|
|
self.user_auth_ui = UserAuthUI(self) # 用户认证UI模块
|
|
|
self.chat_manager = ChatManager(self) # 聊天管理模块
|
|
|
self.conversation_manager = ConversationManager(self) # 对话管理模块
|
|
|
self.ui_components = UIComponents(self) # UI组件封装模块
|
|
|
|
|
|
# 将日志管理器传递给API客户端和用户认证模块
|
|
|
self.api.logger = self.logger_manager
|
|
|
self.auth.logger = self.logger_manager
|
|
|
|
|
|
# 先显示登录窗口,用户登录后才初始化主界面
|
|
|
self.login_window = None
|
|
|
self.user_auth_ui.show_login_window()
|
|
|
|
|
|
def _init_window(self):
|
|
|
"""配置主窗口属性
|
|
|
|
|
|
设置窗口标题、大小、最小尺寸、背景色和图标等基本属性。
|
|
|
"""
|
|
|
self.root.title("DeepSeek AI Assistant") # 设置窗口标题
|
|
|
self.root.geometry("1050x600") # 设置窗口初始大小
|
|
|
self.root.minsize(900, 400) # 设置窗口最小尺寸
|
|
|
self.root.configure(bg="#1e1e1e") # 设置窗口背景色
|
|
|
|
|
|
# 绑定窗口关闭事件,确保在关闭时保存聊天历史
|
|
|
self.root.protocol("WM_DELETE_WINDOW", self._on_closing)
|
|
|
|
|
|
# 加载并设置窗口图标
|
|
|
icon_path = os.path.join(
|
|
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), # 项目根目录
|
|
|
"assets", "app_icon.ico" # 图标文件路径
|
|
|
)
|
|
|
if os.path.exists(icon_path):
|
|
|
self.root.iconbitmap(icon_path)
|
|
|
|
|
|
def _on_closing(self):
|
|
|
"""窗口关闭时的事件处理函数
|
|
|
|
|
|
在关闭应用程序之前保存聊天历史记录,并最小化到系统托盘。
|
|
|
"""
|
|
|
self.save_chat_history() # 保存聊天历史
|
|
|
self._hide_main_window() # 隐藏主窗口
|
|
|
if not self.tray_icon:
|
|
|
self._setup_tray_icon() # 设置系统托盘
|
|
|
return "break" # 阻止窗口默认关闭行为
|
|
|
|
|
|
def _hide_main_window(self):
|
|
|
"""隐藏主窗口"""
|
|
|
self.root.withdraw() # 隐藏窗口
|
|
|
self.is_minimized_to_tray = True
|
|
|
|
|
|
def _show_main_window(self):
|
|
|
"""显示主窗口"""
|
|
|
self.root.deiconify() # 显示窗口
|
|
|
self.root.lift() # 窗口置顶
|
|
|
self.root.focus_force() # 获取焦点
|
|
|
self.is_minimized_to_tray = False
|
|
|
|
|
|
def _setup_tray_icon(self):
|
|
|
"""设置系统托盘图标"""
|
|
|
# 获取图标路径
|
|
|
icon_path = os.path.join(
|
|
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
|
"assets", "app_icon.ico"
|
|
|
)
|
|
|
|
|
|
try:
|
|
|
# 加载图标
|
|
|
icon = Image.open(icon_path)
|
|
|
except Exception as e:
|
|
|
# 如果图标加载失败,使用默认图标
|
|
|
icon = None
|
|
|
|
|
|
# 创建托盘菜单
|
|
|
menu = pystray.Menu(
|
|
|
pystray.MenuItem("显示", self._show_main_window),
|
|
|
pystray.MenuItem("退出", self._quit_app)
|
|
|
)
|
|
|
|
|
|
# 创建托盘图标
|
|
|
self.tray_icon = pystray.Icon(
|
|
|
"DeepSeek AI助手",
|
|
|
icon,
|
|
|
"DeepSeek AI助手",
|
|
|
menu
|
|
|
)
|
|
|
|
|
|
# 启动托盘图标线程
|
|
|
self.tray_icon_thread = threading.Thread(target=self.tray_icon.run, daemon=True)
|
|
|
self.tray_icon_thread.start()
|
|
|
|
|
|
def _quit_app(self):
|
|
|
"""退出应用程序"""
|
|
|
# 保存聊天历史
|
|
|
self.save_chat_history()
|
|
|
|
|
|
# 停止托盘图标
|
|
|
if self.tray_icon:
|
|
|
self.tray_icon.stop()
|
|
|
|
|
|
# 销毁主窗口
|
|
|
self.root.destroy()
|
|
|
|
|
|
# 退出程序
|
|
|
sys.exit(0)
|
|
|
|
|
|
def _create_new_conversation(self):
|
|
|
"""创建新对话
|
|
|
|
|
|
委托对话管理器创建新的对话实例,并更新UI显示。
|
|
|
"""
|
|
|
self.conversation_manager.create_new_conversation()
|
|
|
|
|
|
def _on_conversation_select(self, event):
|
|
|
"""选择对话时的事件处理函数
|
|
|
|
|
|
参数:
|
|
|
event: Tkinter事件对象
|
|
|
|
|
|
当用户在对话列表中选择一个对话时,切换到该对话并加载其历史记录。
|
|
|
"""
|
|
|
selection = self.ui_components.get_conversation_listbox().curselection()
|
|
|
if selection: # 如果有选择项
|
|
|
index = selection[0] # 获取选择的索引
|
|
|
|
|
|
# 按最后更新时间降序排序对话,与显示顺序保持一致
|
|
|
# 确保时间戳统一格式(转换为datetime对象)以避免类型错误
|
|
|
sorted_conversations = sorted(
|
|
|
self.conversations,
|
|
|
key=lambda conv_id: self._ensure_datetime(self.chat_histories[conv_id]["last_updated"]),
|
|
|
reverse=True
|
|
|
)
|
|
|
|
|
|
conv_id = sorted_conversations[index] # 获取对应的对话ID
|
|
|
self._switch_conversation(conv_id) # 切换到该对话
|
|
|
|
|
|
def _ensure_datetime(self, timestamp):
|
|
|
"""确保时间戳是datetime对象
|
|
|
|
|
|
参数:
|
|
|
timestamp: 时间戳(可能是datetime对象或字符串)
|
|
|
|
|
|
返回:
|
|
|
datetime对象
|
|
|
"""
|
|
|
from datetime import datetime
|
|
|
if isinstance(timestamp, datetime):
|
|
|
return timestamp
|
|
|
try:
|
|
|
return datetime.fromisoformat(timestamp)
|
|
|
except:
|
|
|
return datetime.now()
|
|
|
|
|
|
def _switch_conversation(self, conv_id):
|
|
|
"""切换到指定ID的对话
|
|
|
|
|
|
参数:
|
|
|
conv_id: 要切换到的对话ID
|
|
|
|
|
|
委托对话管理器处理对话切换逻辑,包括加载历史记录和更新UI。
|
|
|
"""
|
|
|
self.conversation_manager.switch_conversation(conv_id)
|
|
|
|
|
|
def _update_conversation_list(self):
|
|
|
"""更新对话列表显示
|
|
|
|
|
|
委托对话管理器更新对话列表的UI显示,确保显示最新的对话列表。
|
|
|
"""
|
|
|
self.conversation_manager._update_conversation_list()
|
|
|
|
|
|
def _delete_conversation(self):
|
|
|
"""删除当前对话
|
|
|
|
|
|
委托对话管理器删除当前活动的对话,并更新UI显示。
|
|
|
"""
|
|
|
self.conversation_manager.delete_conversation()
|
|
|
|
|
|
def _show_schedules(self):
|
|
|
"""显示日程安排
|
|
|
|
|
|
委托日程管理器显示用户的日程安排界面。
|
|
|
"""
|
|
|
self.schedule_manager.show_schedules()
|
|
|
|
|
|
def _show_todos(self):
|
|
|
"""显示待办事项
|
|
|
|
|
|
委托待办事项管理器显示用户的待办事项界面。
|
|
|
"""
|
|
|
self.todo_manager.show_todos()
|
|
|
|
|
|
def _show_logs(self):
|
|
|
"""显示系统日志
|
|
|
|
|
|
委托日志管理器显示系统操作日志界面。
|
|
|
"""
|
|
|
self.logger_manager.show_logs()
|
|
|
|
|
|
def init_ui(self):
|
|
|
"""初始化所有UI组件
|
|
|
|
|
|
这是UI初始化的主入口方法,负责:
|
|
|
1. 配置主窗口属性
|
|
|
2. 初始化所有UI组件
|
|
|
3. 绑定事件处理函数
|
|
|
4. 加载聊天历史记录
|
|
|
"""
|
|
|
self._init_window() # 配置窗口属性
|
|
|
|
|
|
# 初始化UI组件,并传递所需的回调函数
|
|
|
self.ui_components.init_ui(
|
|
|
self._create_new_conversation, # 创建新对话的回调
|
|
|
self._delete_conversation, # 删除对话的回调
|
|
|
self._show_schedules, # 显示日程的回调
|
|
|
self._show_todos, # 显示待办事项的回调
|
|
|
self._show_logs # 显示日志的回调
|
|
|
)
|
|
|
|
|
|
self._bind_events() # 绑定事件处理函数
|
|
|
self.load_chat_history() # 加载聊天历史
|
|
|
|
|
|
def _bind_events(self):
|
|
|
"""绑定所有UI事件处理函数
|
|
|
|
|
|
将各个UI组件的用户交互事件与相应的处理函数关联起来,包括:
|
|
|
1. 对话列表选择事件
|
|
|
2. 模型选择事件
|
|
|
3. 输入框事件
|
|
|
4. 按钮点击事件
|
|
|
"""
|
|
|
# 绑定对话列表选择事件
|
|
|
self.ui_components.get_conversation_listbox().bind("<<ListboxSelect>>", self._on_conversation_select)
|
|
|
|
|
|
# 绑定模型选择下拉框事件
|
|
|
self.ui_components.get_model_combo().bind("<<ComboboxSelected>>", self._on_model_change)
|
|
|
|
|
|
# 绑定输入框的Shift+Enter事件(用于插入换行符)
|
|
|
self.ui_components.get_user_input().bind("<Shift-Return>", self._insert_newline)
|
|
|
|
|
|
# 绑定发送、清空等按钮事件
|
|
|
self.ui_components.bind_events(
|
|
|
self.send_message, # 发送消息按钮事件
|
|
|
self.clear_chat, # 清空聊天按钮事件
|
|
|
self._on_model_change, # 模型切换事件
|
|
|
self._insert_newline # 插入换行符事件
|
|
|
)
|
|
|
|
|
|
def _on_model_change(self, event=None):
|
|
|
"""模型选择改变时的处理函数
|
|
|
|
|
|
参数:
|
|
|
event: Tkinter事件对象(可选)
|
|
|
|
|
|
当用户选择不同的AI模型时,更新API客户端使用的模型。
|
|
|
"""
|
|
|
# 获取用户选择的模型
|
|
|
model = self.ui_components.get_model_var().get()
|
|
|
# 更新API客户端的模型设置
|
|
|
self.api.model = model
|
|
|
|
|
|
def _insert_newline(self, event):
|
|
|
"""在输入框中插入换行符
|
|
|
|
|
|
参数:
|
|
|
event: Tkinter事件对象
|
|
|
|
|
|
允许用户在输入框中使用Shift+Enter插入换行符,而不是发送消息。
|
|
|
"""
|
|
|
# 在当前光标位置插入换行符
|
|
|
self.ui_components.get_user_input().insert(tk.INSERT, "\n")
|
|
|
return "break" # 阻止事件继续传播,避免触发发送消息
|
|
|
|
|
|
def send_message(self):
|
|
|
"""发送用户消息
|
|
|
|
|
|
委托聊天管理器处理消息发送逻辑,包括:
|
|
|
1. 获取用户输入内容
|
|
|
2. 验证输入
|
|
|
3. 发送到AI API
|
|
|
4. 显示用户消息和AI回复
|
|
|
"""
|
|
|
self.chat_manager.send_message()
|
|
|
|
|
|
def _get_ai_response(self, user_message):
|
|
|
"""获取AI回复
|
|
|
|
|
|
参数:
|
|
|
user_message: 用户发送的消息内容
|
|
|
|
|
|
委托聊天管理器处理AI回复的获取和处理逻辑。
|
|
|
"""
|
|
|
self.chat_manager._get_ai_response(user_message)
|
|
|
|
|
|
def update_chat_display(self):
|
|
|
"""更新聊天界面显示
|
|
|
|
|
|
委托聊天管理器重新渲染整个聊天界面,显示所有消息。
|
|
|
"""
|
|
|
self.chat_manager.update_chat_display()
|
|
|
|
|
|
def _display_message(self, message, index):
|
|
|
"""显示单条消息
|
|
|
|
|
|
参数:
|
|
|
message: 要显示的消息对象
|
|
|
index: 消息在列表中的索引
|
|
|
|
|
|
委托聊天管理器处理单条消息的显示逻辑。
|
|
|
"""
|
|
|
self.chat_manager._display_message(message, index)
|
|
|
|
|
|
def scroll_to_bottom(self):
|
|
|
"""将聊天界面滚动到底部
|
|
|
|
|
|
确保最新的消息总是可见的。
|
|
|
"""
|
|
|
self.chat_manager._scroll_to_bottom()
|
|
|
|
|
|
def clear_chat(self):
|
|
|
"""清空当前聊天
|
|
|
|
|
|
委托对话管理器处理聊天清空逻辑,包括清空UI和内存中的消息。
|
|
|
"""
|
|
|
self.conversation_manager.clear_chat()
|
|
|
|
|
|
def save_chat_history(self):
|
|
|
"""保存聊天历史
|
|
|
|
|
|
委托对话管理器将当前聊天历史保存到文件系统。
|
|
|
"""
|
|
|
self.conversation_manager._save_chat_history()
|
|
|
|
|
|
def load_chat_history(self):
|
|
|
"""加载聊天历史
|
|
|
|
|
|
委托对话管理器从文件系统加载聊天历史记录。
|
|
|
"""
|
|
|
self.conversation_manager.load_chat_history()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
"""模块直接运行时的测试代码
|
|
|
|
|
|
当该模块被直接执行时,创建一个API实例和主窗口,初始化聊天界面。
|
|
|
这主要用于开发和测试目的。
|
|
|
"""
|
|
|
from chat.deepseek_api import DeepSeekAPI # 导入API客户端
|
|
|
api = DeepSeekAPI() # 创建API实例
|
|
|
root = ctk.CTk() # 创建主窗口
|
|
|
app = DeepSeekChatUI(root, api) # 初始化聊天界面
|
|
|
root.mainloop() # 启动主事件循环 |