Python调用DeepSeek API实现图形化窗口
在人工智能日益普及的今天,利用深度学习模型构建聊天机器人已成为一种常见的应用。本文将介绍如何使用Python的Tkinter库结合OpenAI的API来创建一个图形用户界面的聊天应用。这个应用允许用户选择不同的模型进行对话,保存和加载对话历史,并在必要时切换到长文本输入模式。本文介绍了一个使用Tkinter和OpenAI API构建的聊天GUI应用。这个应用提供了用户友好的界面,允许用户选择不同的
目录
一、背景介绍
在人工智能日益普及的今天,利用深度学习模型构建聊天机器人已成为一种常见的应用。本文将介绍如何使用Python的Tkinter库结合OpenAI的API来创建一个图形用户界面的聊天应用。这个应用允许用户选择不同的模型进行对话,保存和加载对话历史,并在必要时切换到长文本输入模式。
二、代码实现
Linux界面请参考:Python接入deepseek API(官网和腾讯)
更多功能请参考:更多功能
1.导入必要的库
首先,我们需要导入实现这个功能所需的Python库:
from openai import OpenAI
import json
import glob
import threading
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
OpenAI:用于与OpenAI的API进行交互。json:用于文件和数据处理。glob:用于查找符合特定规则的文件路径名。threading:用于在后台线程中处理API请求,避免阻塞主线程。tkinter及其子模块:用于构建图形用户界面。
2.初始化OpenAI客户端
client = OpenAI(
api_key="sk-xxxxx",
base_url="https://api.deepseek.com",
)
我们从环境变量中获取OpenAI的API Key,并初始化客户端。这里假设你使用的是DeepSeek的API端点。
3.设置历史记录目录
HISTORY_FILE_PREFIX = "history_"
HISTORY_FILE_DIR = "history_records"
if not os.path.exists(HISTORY_FILE_DIR):
os.makedirs(HISTORY_FILE_DIR)
我们定义了历史记录文件的前缀和存储目录,并确保该目录存在。
4.创建ChatGUI类
接下来,我们创建一个ChatGUI类,用于构建和管理聊天应用的GUI。
4.1初始化方法
class ChatGUI:
def __init__(self, master):
self.master = master
self.model = "deepseek-chat"
self.long_text_mode = False
self.conversation_history = [{"role": "system", "content": "You are a helpful assistant"}]
self.is_streaming = False
self.setup_ui()
在初始化方法中,我们设置了默认模型、长文本模式、对话历史和流状态,并调用了setup_ui方法来构建用户界面。
4.2构建用户界面
在setup_ui方法中,我们创建了一个顶部控制栏,包含模型选择、保存历史、清空历史和加载历史的功能。
control_frame = ttk.Frame(self.master) # 创建一个顶部框架
control_frame.pack(fill=tk.X, padx=5, pady=5) # 将框架放置在窗口顶部,填充X方向
# 模型选择下拉框
self.model_var = tk.StringVar(value=self.model) # 创建一个字符串变量,用于存储当 前选择的模型
model_combobox = ttk.Combobox(control_frame, textvariable=self.model_var,
values=["deepseek-chat", "deepseek-reasoner", "deepseek-coder"], width=15)
model_combobox.pack(side=tk.LEFT, padx=5) # 将下拉框放置在左侧
model_combobox.bind("<<ComboboxSelected>>", self.on_model_change) # 绑定模型切换 事件
# 保存历史按钮
ttk.Button(control_frame, text="保存历史", command=self.save_history).pack(side=tk.LEFT, padx=5)
# 清空历史按钮
ttk.Button(control_frame, text="清空历史", command=self.clear_history).pack(side=tk.LEFT, padx=5)
# 加载历史按钮
ttk.Button(control_frame, text="加载历史", command=self.load_history_dialog).pack(side=tk.LEFT, padx=5)
ttk.Frame: 创建一个框架,用于容纳其他控件。
ttk.Combobox: 创建一个下拉框,用于选择模型。
ttk.Button: 创建按钮,分别用于保存、清空和加载历史记录。
pack: 将控件放置在窗口中,`side=tk.LEFT`表示控件从左到右排列。
4.3创建聊天显示区域
聊天显示区域是一个可滚动的文本框,用于显示用户和助手的对话。
self.chat_area = scrolledtext.ScrolledText(self.master, wrap=tk.WORD, state=tk.DISABLED)
self.chat_area.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
scrolledtext.ScrolledText: 创建一个带滚动条的文本框。
wrap=tk.WORD: 设置文本自动换行。
state=tk.DISABLED: 禁止用户直接编辑文本框内容。
fill=tk.BOTH, expand=True: 使文本框填充整个可用空间。
4.4创建输入区域
输入区域包括一个多行文本框和一个发送按钮,用户在这里输入消息。
input_frame = ttk.Frame(self.master) # 创建一个输入区域框架
input_frame.pack(fill=tk.X, padx=5, pady=5) # 将框架放置在窗口底部
# 输入文本框
self.input_text = tk.Text(input_frame, height=4) # 创建一个多行文本框,高度为4行
self.input_text.pack(fill=tk.X, pady=5) # 将文本框放置在输入区域中
self.input_text.bind("<Return>", self.on_enter_pressed) # 绑定回车键事件
# 底部按钮框架
btn_frame = ttk.Frame(input_frame) # 创建一个按钮框架
btn_frame.pack(fill=tk.X) # 将按钮框架放置在输入区域底部
# 发送按钮
ttk.Button(btn_frame, text="发送", command=self.send_message).pack(side=tk.RIGHT, padx=5)
# 长文本模式复选框
ttk.Checkbutton(btn_frame, text="长文本模式", command=self.toggle_long_text).pack(side=tk.LEFT, padx=5)
tk.Text: 创建一个多行文本框,用于用户输入消息。
bind("<Return>", self.on_enter_pressed): 绑定回车键事件,按下回车键时调用on_enter_pressed方法。
ttk.Checkbutton: 创建一个复选框,用于切换长文本模式。
4.5处理回车键事件
on_enter_pressed方法用于处理用户在输入框中按下回车键的事件。
def on_enter_pressed(self, event):
if not event.state & 0x1: # 检查是否按下Control键
if not self.long_text_mode: # 如果未启用长文本模式
self.send_message() # 发送消息
return "break" # 阻止默认的换行行为
# 允许换行输入
return None
event.state & 0x1: 检查是否按下了Control键。
return "break": 阻止默认的换行行为。
4.6添加消息到聊天区域
append_to_chat方法用于将消息添加到聊天显示区域。
def append_to_chat(self, role, content):
self.chat_area.configure(state=tk.NORMAL) # 启用文本框编辑
self.chat_area.insert(tk.END, f"\n{role}: {content}\n") # 插入消息
self.chat_area.configure(state=tk.DISABLED) # 禁用文本框编辑
self.chat_area.see(tk.END) # 滚动到文本框底部
configure(state=tk.NORMAL): 启用文本框编辑,以便插入消息。
insert(tk.END, ...): 在文本框的末尾插入消息。
see(tk.END): 自动滚动到文本框的底部。
5.主程序
if __name__ == "__main__":
root = tk.Tk()
app = ChatGUI(root)
root.mainloop()
三、完整代码
from openai import OpenAI
import os
import json
import glob
import threading
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
# 初始化OpenAI客户端
client = OpenAI(
api_key="sk-f4343d0d78d549ab8b789bc34e5239fa",
base_url="https://api.deepseek.com",
)
# 定义历史记录文件前缀和目录
HISTORY_FILE_PREFIX = "history_"
HISTORY_FILE_DIR = "history_records"
# 确保历史记录目录存在
if not os.path.exists(HISTORY_FILE_DIR):
os.makedirs(HISTORY_FILE_DIR)
class ChatGUI:
def __init__(self, master):
self.master = master
self.model = "deepseek-chat"
self.long_text_mode = False
self.conversation_history = [{"role": "system", "content": "You are a helpful assistant"}]
self.is_streaming = False
self.setup_ui()
def setup_ui(self):
self.master.title("DeepSeek Chat GUI")
self.master.geometry("800x600")
# 顶部控制栏
control_frame = ttk.Frame(self.master)
control_frame.pack(fill=tk.X, padx=5, pady=5)
self.model_var = tk.StringVar(value=self.model)
model_combobox = ttk.Combobox(control_frame, textvariable=self.model_var,
values=["deepseek-chat", "deepseek-reasoner", "deepseek-coder"], width=15)
model_combobox.pack(side=tk.LEFT, padx=5)
model_combobox.bind("<<ComboboxSelected>>", self.on_model_change)
ttk.Button(control_frame, text="保存历史", command=self.save_history).pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="清空历史", command=self.clear_history).pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="加载历史", command=self.load_history_dialog).pack(side=tk.LEFT, padx=5)
# 对话显示区域
self.chat_area = scrolledtext.ScrolledText(self.master, wrap=tk.WORD, state=tk.DISABLED)
self.chat_area.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 输入区域
input_frame = ttk.Frame(self.master)
input_frame.pack(fill=tk.X, padx=5, pady=5)
self.input_text = tk.Text(input_frame, height=4)
self.input_text.pack(fill=tk.X, pady=5)
self.input_text.bind("<Return>", self.on_enter_pressed)
# 底部按钮
btn_frame = ttk.Frame(input_frame)
btn_frame.pack(fill=tk.X)
ttk.Button(btn_frame, text="发送", command=self.send_message).pack(side=tk.RIGHT, padx=5)
ttk.Checkbutton(btn_frame, text="长文本模式", command=self.toggle_long_text).pack(side=tk.LEFT, padx=5)
def on_model_change(self, event):
self.model = self.model_var.get()
self.append_to_chat("系统", f"模型已切换为: {self.model}")
def toggle_long_text(self):
self.long_text_mode = not self.long_text_mode
status = "启用" if self.long_text_mode else "禁用"
self.append_to_chat("系统", f"长文本模式已{status}")
def on_enter_pressed(self, event):
if not event.state & 0x1: # 检查是否按下Control键
if not self.long_text_mode:
self.send_message()
return "break"
# 允许换行输入
return None
def append_to_chat(self, role, content):
self.chat_area.configure(state=tk.NORMAL)
self.chat_area.insert(tk.END, f"\n{role}: {content}\n")
self.chat_area.configure(state=tk.DISABLED)
self.chat_area.see(tk.END)
def save_history(self):
file_name = self.get_next_history_file_name()
with open(file_name, 'w', encoding='utf-8') as f:
json.dump(self.conversation_history, f, ensure_ascii=False, indent=4)
self.append_to_chat("系统", "历史记录已保存")
def clear_history(self):
self.conversation_history = [{"role": "system", "content": "You are a helpful assistant"}]
self.append_to_chat("系统", "对话历史已清空")
def load_history_dialog(self):
files = [f for f in os.listdir(HISTORY_FILE_DIR) if f.startswith(HISTORY_FILE_PREFIX)]
if not files:
messagebox.showinfo("提示", "没有历史记录文件")
return
dialog = tk.Toplevel(self.master)
dialog.title("选择历史记录")
lb = tk.Listbox(dialog, width=50, height=15)
lb.pack(padx=10, pady=10)
for f in files:
lb.insert(tk.END, f)
def on_select():
selected = lb.curselection()
if selected:
file_name = os.path.join(HISTORY_FILE_DIR, lb.get(selected[0]))
with open(file_name, 'r', encoding='utf-8') as f:
self.conversation_history = json.load(f)
self.refresh_chat_display()
dialog.destroy()
ttk.Button(dialog, text="加载", command=on_select).pack(pady=5)
def refresh_chat_display(self):
self.chat_area.configure(state=tk.NORMAL)
self.chat_area.delete(1.0, tk.END)
for msg in self.conversation_history:
if msg["role"] == "user":
self.chat_area.insert(tk.END, f"\n用户: {msg['content']}\n")
elif msg["role"] == "assistant":
self.chat_area.insert(tk.END, f"\n助手: {msg['content']}\n")
self.chat_area.configure(state=tk.DISABLED)
def send_message(self):
if self.is_streaming:
return
user_input = self.input_text.get("1.0", tk.END).strip()
if not user_input:
return
self.input_text.delete("1.0", tk.END)
self.conversation_history.append({"role": "user", "content": user_input})
self.append_to_chat("用户", user_input)
# 重置助手索引状态
if hasattr(self, 'last_assistant_index'):
del self.last_assistant_index
# 在新线程中处理API请求
self.is_streaming = True
threading.Thread(target=self.process_stream, args=(user_input,)).start()
def process_stream(self, user_input):
try:
stream = client.chat.completions.create(
model=self.model,
messages=self.conversation_history,
stream=True
)
response = []
reasoning = []
for chunk in stream:
if chunk.choices:
delta = chunk.choices[0].delta
if self.model == "deepseek-reasoner" and hasattr(delta, 'reasoning_content') and delta.reasoning_content:
reasoning.append(delta.reasoning_content)
self.update_reasoning(delta.reasoning_content)
if hasattr(delta, 'content') and delta.content:
response.append(delta.content)
self.update_response(delta.content)
full_response = ''.join(response)
self.conversation_history.append({"role": "assistant", "content": full_response})
except Exception as e:
self.append_to_chat("系统", f"错误: {str(e)}")
finally:
self.is_streaming = False
def update_reasoning(self, content):
self.master.after(0, lambda: self.append_to_chat("思考", content))
def update_response(self, content):
self.master.after(0, lambda: self.append_content_to_chat("助手", content))
def append_content_to_chat(self, role, content):
self.chat_area.configure(state=tk.NORMAL)
if role == "助手":
# 检查是否是当前轮次助手的第一个内容片段
if not hasattr(self, 'last_assistant_index'):
self.chat_area.insert(tk.END, f"\n助手: {content}") # 首条带前缀
self.last_assistant_index = self.chat_area.index(tk.INSERT)
else:
# 直接插入内容到上次位置(不带前缀)
self.chat_area.insert(self.last_assistant_index, content)
self.last_assistant_index = self.chat_area.index(tk.INSERT)
else:
self.chat_area.insert(tk.END, f"\n{role}: {content}\n")
self.chat_area.configure(state=tk.DISABLED)
self.chat_area.see(tk.END)
# 历史文件管理相关方法
def get_next_history_file_name(self):
history_files = glob.glob(os.path.join(HISTORY_FILE_DIR, f"{HISTORY_FILE_PREFIX}*.json"))
if not history_files:
return os.path.join(HISTORY_FILE_DIR, f"{HISTORY_FILE_PREFIX}1.json")
latest_file = max(history_files, key=os.path.getctime)
latest_num = int(os.path.basename(latest_file).split("_")[1].split(".")[0])
return os.path.join(HISTORY_FILE_DIR, f"{HISTORY_FILE_PREFIX}{latest_num + 1}.json")
if __name__ == "__main__":
root = tk.Tk()
app = ChatGUI(root)
root.mainloop()
四、总结
本文介绍了一个使用Tkinter和OpenAI API构建的聊天GUI应用。这个应用提供了用户友好的界面,允许用户选择不同的模型进行对话,保存和加载对话历史,并切换到长文本输入模式。通过结合代码和非代码类型的注释,我们详细解析了应用的实现过程,包括初始化客户端、设置历史记录目录、创建ChatGUI类及其方法。希望这个应用能为你的项目或学习提供有用的参考。
更多推荐


所有评论(0)