You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

444 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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() # 启动主事件循环