|
@@ -0,0 +1,305 @@
|
|
|
|
|
+"""
|
|
|
|
|
+DeepSeek Chat - 图形界面问答程序
|
|
|
|
|
+使用 tkinter 构建 GUI,调用 DeepSeek API 进行问答
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+import tkinter as tk
|
|
|
|
|
+from tkinter import scrolledtext, messagebox, ttk
|
|
|
|
|
+import threading
|
|
|
|
|
+import json
|
|
|
|
|
+from datetime import datetime
|
|
|
|
|
+
|
|
|
|
|
+from openai import OpenAI
|
|
|
|
|
+import config
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class DeepSeekChatApp:
|
|
|
|
|
+ """DeepSeek 问答图形界面程序"""
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, root):
|
|
|
|
|
+ self.root = root
|
|
|
|
|
+ self.root.title("DeepSeek 智能问答助手")
|
|
|
|
|
+ self.root.geometry("800x600")
|
|
|
|
|
+ self.root.minsize(600, 400)
|
|
|
|
|
+
|
|
|
|
|
+ # 设置窗口图标和背景色
|
|
|
|
|
+ self.root.configure(bg="#f0f0f0")
|
|
|
|
|
+
|
|
|
|
|
+ # 初始化 API 客户端
|
|
|
|
|
+ self.client = None
|
|
|
|
|
+ self.conversation_history = []
|
|
|
|
|
+ self.is_loading = False
|
|
|
|
|
+
|
|
|
|
|
+ # 创建界面组件
|
|
|
|
|
+ self._create_widgets()
|
|
|
|
|
+
|
|
|
|
|
+ # 初始化 API 连接
|
|
|
|
|
+ self._init_api()
|
|
|
|
|
+
|
|
|
|
|
+ def _init_api(self):
|
|
|
|
|
+ """初始化 DeepSeek API 客户端"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.client = OpenAI(
|
|
|
|
|
+ api_key=config.API_KEY,
|
|
|
|
|
+ base_url=config.API_BASE
|
|
|
|
|
+ )
|
|
|
|
|
+ self.status_var.set("✓ API 连接已就绪")
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self.status_var.set(f"✗ API 初始化失败: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+ def _create_widgets(self):
|
|
|
|
|
+ """创建界面组件"""
|
|
|
|
|
+ # 顶部标题栏
|
|
|
|
|
+ header = tk.Frame(self.root, bg="#4a90d9", height=50)
|
|
|
|
|
+ header.pack(fill=tk.X)
|
|
|
|
|
+ header.pack_propagate(False)
|
|
|
|
|
+
|
|
|
|
|
+ title_label = tk.Label(
|
|
|
|
|
+ header,
|
|
|
|
|
+ text="💬 DeepSeek 智能问答助手",
|
|
|
|
|
+ font=("Microsoft YaHei", 14, "bold"),
|
|
|
|
|
+ bg="#4a90d9",
|
|
|
|
|
+ fg="white"
|
|
|
|
|
+ )
|
|
|
|
|
+ title_label.pack(side=tk.LEFT, padx=15, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ # 清空对话按钮
|
|
|
|
|
+ clear_btn = tk.Button(
|
|
|
|
|
+ header,
|
|
|
|
|
+ text="清空对话",
|
|
|
|
|
+ font=("Microsoft YaHei", 9),
|
|
|
|
|
+ bg="#e74c3c",
|
|
|
|
|
+ fg="white",
|
|
|
|
|
+ relief=tk.FLAT,
|
|
|
|
|
+ cursor="hand2",
|
|
|
|
|
+ command=self.clear_conversation
|
|
|
|
|
+ )
|
|
|
|
|
+ clear_btn.pack(side=tk.RIGHT, padx=15, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ # 主内容区域
|
|
|
|
|
+ main_frame = tk.Frame(self.root, bg="#f0f0f0")
|
|
|
|
|
+ main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ # 对话显示区域
|
|
|
|
|
+ self.chat_display = scrolledtext.ScrolledText(
|
|
|
|
|
+ main_frame,
|
|
|
|
|
+ wrap=tk.WORD,
|
|
|
|
|
+ font=("Microsoft YaHei", 11),
|
|
|
|
|
+ bg="white",
|
|
|
|
|
+ fg="#333333",
|
|
|
|
|
+ state=tk.DISABLED,
|
|
|
|
|
+ relief=tk.FLAT,
|
|
|
|
|
+ padx=10,
|
|
|
|
|
+ pady=10
|
|
|
|
|
+ )
|
|
|
|
|
+ self.chat_display.pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
+
|
|
|
|
|
+ # 配置文本标签样式
|
|
|
|
|
+ self.chat_display.tag_configure(
|
|
|
|
|
+ "user",
|
|
|
|
|
+ foreground="#2c3e50",
|
|
|
|
|
+ font=("Microsoft YaHei", 11, "bold"),
|
|
|
|
|
+ spacing1=10,
|
|
|
|
|
+ spacing3=5
|
|
|
|
|
+ )
|
|
|
|
|
+ self.chat_display.tag_configure(
|
|
|
|
|
+ "assistant",
|
|
|
|
|
+ foreground="#27ae60",
|
|
|
|
|
+ font=("Microsoft YaHei", 11),
|
|
|
|
|
+ spacing1=10,
|
|
|
|
|
+ spacing3=5
|
|
|
|
|
+ )
|
|
|
|
|
+ self.chat_display.tag_configure(
|
|
|
|
|
+ "system",
|
|
|
|
|
+ foreground="#95a5a6",
|
|
|
|
|
+ font=("Microsoft YaHei", 9, "italic"),
|
|
|
|
|
+ spacing1=5,
|
|
|
|
|
+ spacing3=5
|
|
|
|
|
+ )
|
|
|
|
|
+ self.chat_display.tag_configure(
|
|
|
|
|
+ "error",
|
|
|
|
|
+ foreground="#e74c3c",
|
|
|
|
|
+ font=("Microsoft YaHei", 11),
|
|
|
|
|
+ spacing1=10,
|
|
|
|
|
+ spacing3=5
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 输入区域
|
|
|
|
|
+ input_frame = tk.Frame(main_frame, bg="#f0f0f0")
|
|
|
|
|
+ input_frame.pack(fill=tk.X, pady=(10, 0))
|
|
|
|
|
+
|
|
|
|
|
+ # 输入框
|
|
|
|
|
+ self.input_text = tk.Text(
|
|
|
|
|
+ input_frame,
|
|
|
|
|
+ height=3,
|
|
|
|
|
+ font=("Microsoft YaHei", 11),
|
|
|
|
|
+ bg="white",
|
|
|
|
|
+ fg="#333333",
|
|
|
|
|
+ relief=tk.FLAT,
|
|
|
|
|
+ padx=10,
|
|
|
|
|
+ pady=10,
|
|
|
|
|
+ wrap=tk.WORD
|
|
|
|
|
+ )
|
|
|
|
|
+ self.input_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
|
|
|
+ self.input_text.bind("<Return>", self._on_enter)
|
|
|
|
|
+ self.input_text.bind("<Shift-Return>", self._on_shift_enter)
|
|
|
|
|
+
|
|
|
|
|
+ # 发送按钮
|
|
|
|
|
+ self.send_btn = tk.Button(
|
|
|
|
|
+ input_frame,
|
|
|
|
|
+ text="发送 ➤",
|
|
|
|
|
+ font=("Microsoft YaHei", 11, "bold"),
|
|
|
|
|
+ bg="#4a90d9",
|
|
|
|
|
+ fg="white",
|
|
|
|
|
+ relief=tk.FLAT,
|
|
|
|
|
+ cursor="hand2",
|
|
|
|
|
+ padx=20,
|
|
|
|
|
+ pady=10,
|
|
|
|
|
+ command=self.send_message
|
|
|
|
|
+ )
|
|
|
|
|
+ self.send_btn.pack(side=tk.RIGHT, padx=(10, 0))
|
|
|
|
|
+
|
|
|
|
|
+ # 底部状态栏
|
|
|
|
|
+ status_frame = tk.Frame(self.root, bg="#e0e0e0", height=30)
|
|
|
|
|
+ status_frame.pack(fill=tk.X, side=tk.BOTTOM)
|
|
|
|
|
+ status_frame.pack_propagate(False)
|
|
|
|
|
+
|
|
|
|
|
+ self.status_var = tk.StringVar()
|
|
|
|
|
+ self.status_var.set("正在初始化...")
|
|
|
|
|
+ status_label = tk.Label(
|
|
|
|
|
+ status_frame,
|
|
|
|
|
+ textvariable=self.status_var,
|
|
|
|
|
+ font=("Microsoft YaHei", 9),
|
|
|
|
|
+ bg="#e0e0e0",
|
|
|
|
|
+ fg="#666666"
|
|
|
|
|
+ )
|
|
|
|
|
+ status_label.pack(side=tk.LEFT, padx=10, pady=5)
|
|
|
|
|
+
|
|
|
|
|
+ # 显示欢迎消息
|
|
|
|
|
+ self._display_message(
|
|
|
|
|
+ "system",
|
|
|
|
|
+ "欢迎使用 DeepSeek 智能问答助手!\n在下方输入你的问题,按 Enter 发送,Shift+Enter 换行。"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ def _on_enter(self, event):
|
|
|
|
|
+ """处理 Enter 键发送消息"""
|
|
|
|
|
+ if not event.state & 0x0001: # 检查是否按下 Shift
|
|
|
|
|
+ self.send_message()
|
|
|
|
|
+ return "break"
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ def _on_shift_enter(self, event):
|
|
|
|
|
+ """处理 Shift+Enter 换行"""
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ def _display_message(self, role, content):
|
|
|
|
|
+ """在对话区域显示消息"""
|
|
|
|
|
+ self.chat_display.config(state=tk.NORMAL)
|
|
|
|
|
+
|
|
|
|
|
+ # 添加时间戳
|
|
|
|
|
+ timestamp = datetime.now().strftime("%H:%M:%S")
|
|
|
|
|
+
|
|
|
|
|
+ if role == "user":
|
|
|
|
|
+ prefix = f"👤 你 ({timestamp}):\n"
|
|
|
|
|
+ tag = "user"
|
|
|
|
|
+ elif role == "assistant":
|
|
|
|
|
+ prefix = f"🤖 DeepSeek ({timestamp}):\n"
|
|
|
|
|
+ tag = "assistant"
|
|
|
|
|
+ elif role == "error":
|
|
|
|
|
+ prefix = f"⚠️ 错误 ({timestamp}):\n"
|
|
|
|
|
+ tag = "error"
|
|
|
|
|
+ else:
|
|
|
|
|
+ prefix = f"ℹ️ ({timestamp}):\n"
|
|
|
|
|
+ tag = "system"
|
|
|
|
|
+
|
|
|
|
|
+ self.chat_display.insert(tk.END, prefix, tag)
|
|
|
|
|
+ self.chat_display.insert(tk.END, content + "\n\n", tag)
|
|
|
|
|
+ self.chat_display.see(tk.END)
|
|
|
|
|
+ self.chat_display.config(state=tk.DISABLED)
|
|
|
|
|
+
|
|
|
|
|
+ def send_message(self):
|
|
|
|
|
+ """发送消息到 DeepSeek API"""
|
|
|
|
|
+ if self.is_loading:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # 获取用户输入
|
|
|
|
|
+ user_input = self.input_text.get("1.0", tk.END).strip()
|
|
|
|
|
+ if not user_input:
|
|
|
|
|
+ messagebox.showwarning("提示", "请输入问题内容")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # 清空输入框
|
|
|
|
|
+ self.input_text.delete("1.0", tk.END)
|
|
|
|
|
+
|
|
|
|
|
+ # 显示用户消息
|
|
|
|
|
+ self._display_message("user", user_input)
|
|
|
|
|
+
|
|
|
|
|
+ # 添加到对话历史
|
|
|
|
|
+ self.conversation_history.append({"role": "user", "content": user_input})
|
|
|
|
|
+
|
|
|
|
|
+ # 禁用发送按钮,显示加载状态
|
|
|
|
|
+ self.is_loading = True
|
|
|
|
|
+ self.send_btn.config(state=tk.DISABLED, text="思考中...")
|
|
|
|
|
+ self.status_var.set("正在请求 DeepSeek API...")
|
|
|
|
|
+
|
|
|
|
|
+ # 在后台线程中调用 API
|
|
|
|
|
+ thread = threading.Thread(target=self._call_api, daemon=True)
|
|
|
|
|
+ thread.start()
|
|
|
|
|
+
|
|
|
|
|
+ def _call_api(self):
|
|
|
|
|
+ """在后台线程中调用 DeepSeek API"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ if not self.client:
|
|
|
|
|
+ raise Exception("API 客户端未初始化,请检查 config.py 中的 API Key 配置")
|
|
|
|
|
+
|
|
|
|
|
+ # 调用 API
|
|
|
|
|
+ response = self.client.chat.completions.create(
|
|
|
|
|
+ model=config.MODEL,
|
|
|
|
|
+ messages=self.conversation_history,
|
|
|
|
|
+ stream=False
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 获取回复内容
|
|
|
|
|
+ reply = response.choices[0].message.content
|
|
|
|
|
+
|
|
|
|
|
+ # 添加到对话历史
|
|
|
|
|
+ self.conversation_history.append({"role": "assistant", "content": reply})
|
|
|
|
|
+
|
|
|
|
|
+ # 在主线程中更新界面
|
|
|
|
|
+ self.root.after(0, self._display_message, "assistant", reply)
|
|
|
|
|
+ self.root.after(0, self._reset_ui, "✓ 回复完成")
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ error_msg = f"请求失败: {str(e)}\n\n请检查:\n1. API Key 是否正确\n2. 网络连接是否正常\n3. 是否已安装 openai 库"
|
|
|
|
|
+ self.root.after(0, self._display_message, "error", error_msg)
|
|
|
|
|
+ self.root.after(0, self._reset_ui, "✗ 请求失败")
|
|
|
|
|
+
|
|
|
|
|
+ def _reset_ui(self, status_text):
|
|
|
|
|
+ """重置界面状态"""
|
|
|
|
|
+ self.is_loading = False
|
|
|
|
|
+ self.send_btn.config(state=tk.NORMAL, text="发送 ➤")
|
|
|
|
|
+ self.status_var.set(status_text)
|
|
|
|
|
+
|
|
|
|
|
+ def clear_conversation(self):
|
|
|
|
|
+ """清空对话"""
|
|
|
|
|
+ if messagebox.askyesno("确认", "确定要清空所有对话记录吗?"):
|
|
|
|
|
+ self.conversation_history = []
|
|
|
|
|
+ self.chat_display.config(state=tk.NORMAL)
|
|
|
|
|
+ self.chat_display.delete("1.0", tk.END)
|
|
|
|
|
+ self.chat_display.config(state=tk.DISABLED)
|
|
|
|
|
+ self._display_message(
|
|
|
|
|
+ "system",
|
|
|
|
|
+ "对话已清空。开始新的对话吧!"
|
|
|
|
|
+ )
|
|
|
|
|
+ self.status_var.set("对话已清空")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def main():
|
|
|
|
|
+ """程序入口"""
|
|
|
|
|
+ root = tk.Tk()
|
|
|
|
|
+ app = DeepSeekChatApp(root)
|
|
|
|
|
+ root.mainloop()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ main()
|