实现一个简单的客服机器人应用,使用Python的Tkinter库构建了图形用户界面(GUI),并通过与MySQL数据库交互来查询和回复用户的提问。此外,它还支持从CSV或Excel文件中导入话术模板,并提供下载模板的功能。
初始化与GUI设置:通过tkinter
库创建了一个窗口应用程序,设置了文本显示区、用户输入区、发送按钮、导入话术按钮和下载模板按钮。
连接到数据库:尝试连接到本地MySQL数据库,并准备执行查询操作。
消息处理:
话术导入与模板下载:允许用户从外部文件(CSV或Excel)导入新的对话模式到数据库中,并提供下载预设模板的功能以便于用户自定义话术。
tkinter
库构建GUI。mysql.connector
与MySQL数据库进行交互,pandas
用于处理CSV和Excel文件。re
用于正则表达式处理,random
用于随机选择默认响应,chardet
用于检测文件编码。简化状态管理:
current_pattern
和 current_response
。class ChatbotApp:
def __init__(self, root):
...
self.patterns_queue = []
处理单条匹配结果:
def query_response(self, msg):
try:
query = "SELECT pattern, response FROM patterns WHERE pattern LIKE %s"
search_pattern = f"%{msg}%"
print(f"执行查询: {query} WITH {search_pattern}") # 记录查询语句
self.cursor.execute(query, (search_pattern,))
results = self.cursor.fetchall()
print(f"查询结果: {results}") # 记录查询结果
if len(results) == 1:
pattern, response = results[0]
return response
elif len(results) > 1:
self.patterns_queue = list(results)
pattern, response = self.patterns_queue.pop(0)
self.current_pattern = pattern
self.current_response = response
return f"您好 {pattern} 吗?"
else:
return self.query_default_response(msg)
except mysql.connector.Error as err:
messagebox.showerror("错误", f"查询数据库失败: {err}")
print(f"查询数据库失败: {err}")
return "发生内部错误,请稍后再试。"
处理多条匹配结果:
def query_response(self, msg):
try:
query = "SELECT pattern, response FROM patterns WHERE pattern LIKE %s"
search_pattern = f"%{msg}%"
print(f"执行查询: {query} WITH {search_pattern}") # 记录查询语句
self.cursor.execute(query, (search_pattern,))
results = self.cursor.fetchall()
print(f"查询结果: {results}") # 记录查询结果
if len(results) == 1:
pattern, response = results[0]
return response
elif len(results) > 1:
self.patterns_queue = list(results)
pattern, response = self.patterns_queue.pop(0)
self.current_pattern = pattern
self.current_response = response
return f"您好 {pattern} 吗?"
else:
return self.query_default_response(msg)
except mysql.connector.Error as err:
messagebox.showerror("错误", f"查询数据库失败: {err}")
print(f"查询数据库失败: {err}")
return "发生内部错误,请稍后再试。"
处理用户响应:
def send_message(self, event=None):
user_message = self.user_input.get().strip()
if user_message.lower() == "退出":
self.root.quit()
else:
cleaned_message = self.remove_punctuation(user_message).lower()
print(f"清理后的消息: {cleaned_message}") # 调试信息
self.display_message(f"你: {user_message}", "blue")
if self.patterns_queue:
if user_message.lower() in ["是", "yes"]:
response = self.current_response
self.display_message(f"机器人: {response}", "green")
self.patterns_queue.clear()
elif user_message.lower() in ["否", "no"]:
if self.patterns_queue:
pattern, response = self.patterns_queue.pop(0)
self.current_pattern = pattern
self.current_response = response
self.display_message(f"机器人: 您好 {pattern} 吗?", "green")
else:
default_response = self.query_default_response(cleaned_message)
self.display_message(f"机器人: {default_response}", "green")
else:
self.display_message(f"机器人: 请输入 是 或 否", "red")
else:
response = self.query_response(cleaned_message)
self.display_message(f"机器人: {response}", "green")
self.user_input.delete(0, tk.END)
确保安装必要的库:
mysql-connector-python
, pandas
, openpyxl
, 和 chardet
库。pip install mysql-connector-python pandas openpyxl chardet
创建数据库和表:
CREATE DATABASE chatbot_db;
USE chatbot_db;
CREATE TABLE patterns (
id INT AUTO_INCREMENT PRIMARY KEY,
pattern VARCHAR(255) NOT NULL,
response TEXT NOT NULL
);
保存代码:
chatbot_gui_import_download.py
)。运行代码:
python chatbot_gui_import_download.py
测试默认话术:
测试模糊检索:
导入话术:
数据库连接成功
检测到的编码: utf-8 (置信度: 0.99)
话术导入成功
执行查询: SELECT pattern, response FROM patterns WHERE pattern LIKE %s WITH %今天天气%
查询结果: [('今天天气怎么样|今天天气如何|今天的天气|今天是一个好天气吗', '今天天气不错! 今天的天气还行。 天气非常好,适合外出。')]
匹配到模式: 今天天气怎么样|今天天气如何|今天的天气|今天是一个好天气吗, 响应: 今天天气不错! 今天的天气还行。 天气非常好,适合外出。
下载模板:
patterns_template.csv
文件,确认内容如下:pattern,response
我叫(.*),"你好 %1,有什么我可以帮忙的吗?"
你好|嗨|您好,"你好! 嘿,很高兴见到你! 您好!有什么问题吗?"
你是谁,"我是一个简单的聊天机器人,可以帮助你。 我是你的客服助手。"
你怎么样,"我只是个程序,不过谢谢关心!你呢? 我很好,谢谢!"
对不起(.*),"没关系! 别担心。 没事的,继续吧。"
退出,"再见!祝你有美好的一天! 回头见! 感谢使用,再见!"
今天天气怎么样|今天天气如何|今天的天气|今天是一个好天气吗,"今天天气不错! 今天的天气还行。 天气非常好,适合外出。"
订单在哪里,"您的订单正在处理中,稍后会送到。 请您耐心等待,订单很快就会到达。"
退款怎么操作,"您可以通过我们的网站或联系客服进行退款操作。"
产品有问题,"请提供订单号和详细信息,我们会尽快处理。"
发货时间,"预计明天发货,具体时间请关注物流信息。"
售后服务,"您可以联系我们的售后部门获取帮助。"
促销活动,"我们正在进行打折促销,详情请查看我们的官方网站。"
(.),"我不太明白你在说什么,能再说一遍吗? 抱歉,我没有理解你的意思。"
patterns_template.csv
文件中的内容。pattern,response
产品怎么购买,"您可以通过我们的官网或者联系客服了解购买详情。"
patterns_template.csv
文件。数据库连接成功
检测到的编码: utf-8 (置信度: 0.99)
话术导入成功
清理后的消息: 产品怎么购买
执行查询: SELECT pattern, response FROM patterns WHERE pattern LIKE %s WITH %产品怎么购买%
查询结果: [('产品怎么购买', '您可以通过我们的官网或者联系客服了解购买详情。')]
匹配到模式: 产品怎么购买, 响应: 您可以通过我们的官网或者联系客服了解购买详情。
收到的响应: 您可以通过我们的官网或者联系客服了解购买详情。
清理后的消息: 今天天气
执行查询: SELECT pattern, response FROM patterns WHERE pattern LIKE %s WITH %今天天气%
查询结果: [('今天天气怎么样|今天天气如何|今天的天气|今天是一个好天气吗', '今天天气不错! 今天的天气还行。 天气非常好,适合外出。')]
匹配到模式: 今天天气怎么样|今天天气如何|今天的天气|今天是一个好天气吗, 响应: 今天天气不错! 今天的天气还行。 天气非常好,适合外出。
输入“换货”。
查看控制台输出:
清理后的消息: 换货
执行查询: SELECT pattern, response FROM patterns WHERE pattern LIKE %s WITH %换货%
查询结果: [('能不能退换货', '如果符合我们的退换货政策是可以的。具体政策您可以在网站上查看128221'), ('退换货需要承担运费吗', '根据不同情况而定哦如果是产品质量问题我们承担运费若是其他原因可能需要您承担部分运费128666128176')]
匹配到模式: 能不能退换货, 响应: 如果符合我们的退换货政策是可以的。具体政策您可以在网站上查看128221
机器人询问:“您好 能不能退换货 吗?”。
用户回复“是”。
机器人响应:“如果符合我们的退换货政策是可以的。具体政策您可以在网站上查看128221”。
如果用户回复“否”:
如果用户再次回复“否”且没有更多匹配结果:
import tkinter as tk
from tkinter import scrolledtext, messagebox, filedialog
import re
import random
import mysql.connector
import csv
import pandas as pd
import chardet
class ChatbotApp:
def __init__(self, root):
self.root = root
self.root.title("客服机器人")
self.root.geometry("1000x900")
# 设置主框架样式
main_frame = tk.Frame(root, bg="#f0f0f0")
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建滚动文本框用于显示对话
self.chat_display = scrolledtext.ScrolledText(main_frame, width=70, height=15, wrap=tk.WORD,
bg="#ffffff", fg="#000000", font=("Arial", 12),
borderwidth=2, relief=tk.SUNKEN)
self.chat_display.grid(row=0, column=0, columnspan=4, padx=10, pady=10, sticky="nsew")
self.chat_display.config(state=tk.DISABLED)
# 创建输入框用于用户输入
self.user_input = tk.Entry(main_frame, width=50, font=("Arial", 12), bg="#ffffff", fg="#000000",
borderwidth=2, relief=tk.SUNKEN)
self.user_input.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
self.user_input.bind("" , self.send_message)
# 创建发送按钮
self.send_button = tk.Button(main_frame, text="发送", command=self.send_message, font=("Arial", 12),
bg="#4CAF50", fg="#FFFFFF", borderwidth=2, relief=tk.RAISED)
self.send_button.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
# 创建导入按钮
self.import_button = tk.Button(main_frame, text="导入话术", command=self.import_patterns, font=("Arial", 12),
bg="#FF9800", fg="#FFFFFF", borderwidth=2, relief=tk.RAISED)
self.import_button.grid(row=1, column=2, padx=10, pady=10, sticky="ew")
# 创建下载模板按钮
self.download_template_button = tk.Button(main_frame, text="下载模板", command=self.download_template, font=("Arial", 12),
bg="#008CBA", fg="#FFFFFF", borderwidth=2, relief=tk.RAISED)
self.download_template_button.grid(row=1, column=3, padx=10, pady=10, sticky="ew")
# 配置网格布局权重
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
main_frame.grid_columnconfigure(1, weight=1)
main_frame.grid_columnconfigure(2, weight=1)
main_frame.grid_columnconfigure(3, weight=1)
# 初始化聊天机器人
self.connect_to_database()
self.current_pattern = None
self.current_response = None
self.patterns_queue = []
def connect_to_database(self):
try:
self.db_connection = mysql.connector.connect(
host="xxxxx",
user="xxxxx",
password="xxxxx",
database="xxxxx"
)
self.cursor = self.db_connection.cursor()
print("数据库连接成功")
except mysql.connector.Error as err:
messagebox.showerror("错误", f"无法连接到数据库: {err}")
print(f"无法连接到数据库: {err}")
def send_message(self, event=None):
user_message = self.user_input.get().strip()
if user_message.lower() == "退出":
self.root.quit()
else:
cleaned_message = self.remove_punctuation(user_message).lower()
print(f"清理后的消息: {cleaned_message}") # 调试信息
self.display_message(f"你: {user_message}", "blue")
if self.current_pattern is not None:
if user_message.lower() in ["是", "yes"]:
response = self.current_response
self.display_message(f"机器人: {response}", "green")
self.current_pattern = None
self.current_response = None
self.patterns_queue.clear()
elif user_message.lower() in ["否", "no"]:
if self.patterns_queue:
pattern, response = self.patterns_queue.pop(0)
self.current_pattern = pattern
self.current_response = response
self.display_message(f"机器人: 您问的是 {pattern} 吗?", "green")
else:
default_response = self.query_default_response(cleaned_message)
self.display_message(f"机器人: {default_response}", "green")
self.current_pattern = None
self.current_response = None
else:
self.display_message(f"机器人: 请输入 是 或 否", "red")
else:
response = self.query_response(cleaned_message)
self.display_message(f"机器人: {response}", "green")
self.user_input.delete(0, tk.END)
def remove_punctuation(self, text):
return re.sub(r'[^\w\s]', '', text)
def display_message(self, message, color):
self.chat_display.config(state=tk.NORMAL)
self.chat_display.insert(tk.END, message + "\n", color)
self.chat_display.tag_config(color, foreground=color)
self.chat_display.yview(tk.END)
self.chat_display.config(state=tk.DISABLED)
def query_response(self, msg):
try:
query = "SELECT pattern, response FROM patterns WHERE pattern LIKE %s"
search_pattern = f"%{msg}%"
print(f"执行查询: {query} WITH {search_pattern}") # 记录查询语句
self.cursor.execute(query, (search_pattern,))
results = self.cursor.fetchall()
print(f"查询结果: {results}") # 记录查询结果
if len(results) == 1:
pattern, response = results[0]
return response
elif len(results) > 1:
self.patterns_queue = list(results)
pattern, response = self.patterns_queue.pop(0)
self.current_pattern = pattern
self.current_response = response
return f"您问的是 {pattern} 吗?"
else:
return self.query_default_response(msg)
except mysql.connector.Error as err:
messagebox.showerror("错误", f"查询数据库失败: {err}")
print(f"查询数据库失败: {err}")
return "发生内部错误,请稍后再试。"
def query_default_response(self, msg):
try:
default_query = "SELECT response FROM patterns WHERE pattern LIKE '%(.*)%'"
print(f"执行默认查询: {default_query}") # 记录默认查询语句
self.cursor.execute(default_query)
default_results = self.cursor.fetchall()
print(f"默认查询结果: {default_results}") # 记录默认查询结果
if default_results:
default_responses = [row[0] for row in default_results]
return random.choice(default_responses)
return "抱歉,我不太明白你在说什么,能再说一遍吗? 抱歉,我没有理解你的意思。"
except mysql.connector.Error as err:
messagebox.showerror("错误", f"查询数据库失败: {err}")
print(f"查询数据库失败: {err}")
return "发生内部错误,请稍后再试。"
def _substitute(self, str, args):
if "%1" in str and len(args) > 0: str = str.replace("%1", args[0])
if "%2" in str and len(args) > 1: str = str.replace("%2", args[1])
if "%3" in str and len(args) > 2: str = str.replace("%3", args[2])
if "%4" in str and len(args) > 3: str = str.replace("%4", args[3])
if "%5" in str and len(args) > 4: str = str.replace("%5", args[4])
return str
def import_patterns(self):
file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv"), ("Excel files", "*.xlsx *.xls")])
if not file_path:
return
try:
encoding = self.detect_file_encoding(file_path)
if file_path.endswith('.csv'):
df = pd.read_csv(file_path, encoding=encoding)
elif file_path.endswith(('.xlsx', '.xls')):
df = pd.read_excel(file_path, engine='openpyxl')
else:
messagebox.showwarning("警告", "不支持的文件类型")
return
if 'pattern' not in df.columns or 'response' not in df.columns:
messagebox.showwarning("警告", "文件缺少必要的列 (pattern 或 response)")
return
for index, row in df.iterrows():
pattern = row['pattern']
response = row['response']
query = "INSERT INTO patterns (pattern, response) VALUES (%s, %s)"
self.cursor.execute(query, (pattern, response))
self.db_connection.commit()
messagebox.showinfo("成功", "话术导入成功")
print("话术导入成功")
except Exception as e:
messagebox.showerror("错误", f"导入失败: {e}")
print(f"导入失败: {e}")
def detect_file_encoding(self, file_path):
with open(file_path, 'rb') as f:
result = chardet.detect(f.read())
encoding = result['encoding']
confidence = result['confidence']
print(f"检测到的编码: {encoding} (置信度: {confidence})")
return encoding
def download_template(self):
file_path = filedialog.asksaveasfilename(defaultextension=".csv",
filetypes=[("CSV files", "*.csv"),
("Excel files", "*.xlsx *.xls")],
initialfile="patterns_template.csv")
if not file_path:
return
try:
data = {
'pattern': [
'我叫(.*)',
'你好|嗨|您好',
'你是谁',
'你怎么样',
'对不起(.*)',
'退出',
'今天天气怎么样|今天天气如何|今天的天气|今天是一个好天气吗',
'订单在哪里',
'退款怎么操作',
'产品有问题',
'发货时间',
'售后服务',
'促销活动',
'(.)'
],
'response': [
'你好 %1,有什么我可以帮忙的吗?',
'你好! 嘿,很高兴见到你! 您好!有什么问题吗?',
'我是一个简单的聊天机器人,可以帮助你。 我是你的客服助手。',
'我只是个程序,不过谢谢关心!你呢? 我很好,谢谢!',
'没关系! 别担心。 没事的,继续吧。',
'再见!祝你有美好的一天! 回头见! 感谢使用,再见!',
'今天天气不错! 今天的天气还行。 天气非常好,适合外出。',
'您的订单正在处理中,稍后会送到。 请您耐心等待,订单很快就会到达。',
'您可以通过我们的网站或联系客服进行退款操作。',
'请提供订单号和详细信息,我们会尽快处理。',
'预计明天发货,具体时间请关注物流信息。',
'您可以联系我们的售后部门获取帮助。',
'我们正在进行打折促销,详情请查看我们的官方网站。',
'我不太明白你在说什么,能再说一遍吗? 抱歉,我没有理解你的意思。'
]
}
if file_path.endswith('.csv'):
df = pd.DataFrame(data)
df.to_csv(file_path, index=False, encoding='utf-8-sig')
elif file_path.endswith(('.xlsx', '.xls')):
df = pd.DataFrame(data)
df.to_excel(file_path, index=False, engine='openpyxl')
else:
messagebox.showwarning("警告", "不支持的文件类型")
return
messagebox.showinfo("成功", "模板下载成功")
print("模板下载成功")
except Exception as e:
messagebox.showerror("错误", f"下载模板失败: {e}")
print(f"下载模板失败: {e}")
if __name__ == "__main__":
root = tk.Tk()
app = ChatbotApp(root)
root.mainloop()
这里要修改成自己数据库的信息
try:
self.db_connection = mysql.connector.connect(
host="xxxxx",
user="xxxxx",
password="xxxxx",
database="xxxxx"