Skip to content

Commit 521b38b

Browse files
authored
Create Simple-Chatbot.py
1 parent cf258c2 commit 521b38b

1 file changed

Lines changed: 309 additions & 0 deletions

File tree

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
import sys
2+
import os
3+
import json
4+
import tkinter as tk
5+
from tkinter import ttk, messagebox, simpledialog
6+
import sv_ttk
7+
8+
# =========================
9+
# Helpers
10+
# =========================
11+
def resource_path(file_name):
12+
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
13+
return os.path.join(base_path, file_name)
14+
15+
QA_FILE = "qa_data.json"
16+
17+
def load_qa():
18+
if os.path.exists(QA_FILE):
19+
with open(QA_FILE, "r", encoding="utf-8") as f:
20+
return json.load(f)
21+
else:
22+
return [
23+
{"condo": "Condo A", "category": "Sell", "question": "Sell Condo", "response": "We can assist with selling Condo A."},
24+
{"condo": "Condo A", "category": "Rent", "question": "Rent Condo", "response": "We can list Condo A for rent."},
25+
{"condo": "Condo B", "category": "Price", "question": "Price", "response": "Condo B prices vary by floor and size."},
26+
{"condo": "Condo B", "category": "Service Fee", "question": "Service Fee", "response": "Service fee depends on condo type."},
27+
{"condo": "Condo C", "category": "Facilities", "question": "Facilities", "response": "Condo C has gym, pool, and security."},
28+
]
29+
30+
def save_qa():
31+
with open(QA_FILE, "w", encoding="utf-8") as f:
32+
json.dump(qa_data, f, ensure_ascii=False, indent=4)
33+
34+
def set_status(msg):
35+
status_var.set(msg)
36+
root.update_idletasks()
37+
38+
# =========================
39+
# App Setup
40+
# =========================
41+
root = tk.Tk()
42+
root.title("💬 Condo Assistant Bot")
43+
root.geometry("1180x650")
44+
sv_ttk.set_theme("light")
45+
46+
# =========================
47+
# Globals
48+
# =========================
49+
qa_data = load_qa()
50+
dark_mode_var = tk.BooleanVar(value=False)
51+
52+
# =========================
53+
# Helper Functions
54+
# =========================
55+
def get_condos(): return sorted(list({qa["condo"] for qa in qa_data}))
56+
def get_categories(condo): return sorted(list({qa["category"] for qa in qa_data if qa["condo"] == condo}))
57+
def get_questions(condo, category): return [qa["question"] for qa in qa_data if qa["condo"] == condo and qa["category"] == category]
58+
def get_response(condo, category, question):
59+
for qa in qa_data:
60+
if qa["condo"] == condo and qa["category"] == category and qa["question"] == question:
61+
return qa["response"]
62+
return "No response found."
63+
64+
# =========================
65+
# Chat Functions with Bubble Styling
66+
# =========================
67+
def insert_bubble(message, sender="user"):
68+
chat_text.config(state="normal")
69+
if sender == "user":
70+
chat_text.insert(tk.END, f" {message} \n\n", "user_bubble")
71+
else:
72+
chat_text.insert(tk.END, f" {message} \n\n", "bot_bubble")
73+
chat_text.see(tk.END)
74+
chat_text.config(state="disabled")
75+
76+
def update_category_dropdown(event=None):
77+
selected_condo = condo_combo.get()
78+
categories = get_categories(selected_condo)
79+
category_combo['values'] = categories
80+
if categories:
81+
category_combo.current(0)
82+
update_question_dropdown()
83+
84+
def update_question_dropdown(event=None):
85+
selected_condo = condo_combo.get()
86+
selected_category = category_combo.get()
87+
questions = get_questions(selected_condo, selected_category)
88+
question_combo['values'] = questions
89+
if questions:
90+
question_combo.current(0)
91+
92+
def send_message():
93+
condo = condo_combo.get()
94+
category = category_combo.get()
95+
question = question_combo.get()
96+
if not (condo and category and question):
97+
messagebox.showwarning("Incomplete Selection", "Please select condo, category, and question.")
98+
return
99+
response = get_response(condo, category, question)
100+
insert_bubble(f"You: {condo} | {category} | {question}", "user")
101+
insert_bubble(f"Bot: {response}", "bot")
102+
set_status(f"Responded to: {question}")
103+
104+
# =========================
105+
# Admin Functions with Top Add Section
106+
# =========================
107+
def manage_qa_ui():
108+
admin_win = tk.Toplevel(root)
109+
admin_win.title("Manage Q&A")
110+
admin_win.geometry("1050x550")
111+
admin_win.resizable(False, False)
112+
113+
# Make it modal
114+
admin_win.transient(root) # Keep above main window
115+
admin_win.grab_set() # Block interaction with main window
116+
117+
# Add New Q&A Above Tree
118+
add_frame = ttk.LabelFrame(admin_win, text="➕ Add New Q&A", padding=10)
119+
add_frame.pack(fill="x", padx=10, pady=5)
120+
121+
ttk.Label(add_frame, text="Condo:").grid(row=0, column=0, padx=5, pady=2)
122+
new_condo = tk.Entry(add_frame, width=15)
123+
new_condo.grid(row=0, column=1, padx=5)
124+
125+
ttk.Label(add_frame, text="Category:").grid(row=0, column=2, padx=5, pady=2)
126+
new_category = tk.Entry(add_frame, width=15)
127+
new_category.grid(row=0, column=3, padx=5)
128+
129+
ttk.Label(add_frame, text="Question:").grid(row=0, column=4, padx=5, pady=2)
130+
new_question = tk.Entry(add_frame, width=25)
131+
new_question.grid(row=0, column=5, padx=5)
132+
133+
ttk.Label(add_frame, text="Response:").grid(row=0, column=6, padx=5, pady=2)
134+
new_response = tk.Entry(add_frame, width=35)
135+
new_response.grid(row=0, column=7, padx=5)
136+
137+
def add_new_qa():
138+
condo_val = new_condo.get().strip()
139+
category_val = new_category.get().strip()
140+
question_val = new_question.get().strip()
141+
response_val = new_response.get().strip()
142+
if condo_val and category_val and question_val and response_val:
143+
qa_data.append({"condo": condo_val, "category": category_val, "question": question_val, "response": response_val})
144+
save_qa()
145+
refresh_tree()
146+
new_condo.delete(0, tk.END)
147+
new_category.delete(0, tk.END)
148+
new_question.delete(0, tk.END)
149+
new_response.delete(0, tk.END)
150+
set_status(f"Added Q&A: {question_val}")
151+
else:
152+
messagebox.showwarning("Incomplete", "Fill all fields to add Q&A.")
153+
154+
ttk.Button(add_frame, text="Add Q&A", command=add_new_qa).grid(row=0, column=8, padx=5)
155+
156+
# Treeview Section
157+
tree_frame = ttk.Frame(admin_win)
158+
tree_frame.pack(expand=True, fill="both", padx=10, pady=10)
159+
columns = ("condo", "category", "question", "response")
160+
global tree
161+
tree = ttk.Treeview(tree_frame, columns=columns, show="headings")
162+
for col in columns:
163+
tree.heading(col, text=col)
164+
tree.column(col, width=150 if col != "response" else 350)
165+
tree.pack(expand=True, fill="both", side="left")
166+
scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=tree.yview)
167+
scrollbar.pack(side="right", fill="y")
168+
tree.configure(yscrollcommand=scrollbar.set)
169+
170+
def refresh_tree():
171+
tree.delete(*tree.get_children())
172+
for qa in qa_data:
173+
tree.insert("", "end", values=(qa["condo"], qa["category"], qa["question"], qa["response"]))
174+
condo_combo['values'] = get_condos()
175+
update_category_dropdown()
176+
save_qa()
177+
178+
def update_selected_qa():
179+
selected = tree.selection()
180+
if not selected:
181+
messagebox.showwarning("No Selection", "Select a Q&A to update.")
182+
return
183+
idx = tree.index(selected[0])
184+
qa = qa_data[idx]
185+
186+
condo_val = simpledialog.askstring("Update Q&A", "Condo:", initialvalue=qa["condo"])
187+
category_val = simpledialog.askstring("Update Q&A", "Category:", initialvalue=qa["category"])
188+
question_val = simpledialog.askstring("Update Q&A", "Question:", initialvalue=qa["question"])
189+
response_val = simpledialog.askstring("Update Q&A", "Response:", initialvalue=qa["response"])
190+
191+
if condo_val and category_val and question_val and response_val:
192+
qa_data[idx] = {"condo": condo_val, "category": category_val, "question": question_val, "response": response_val}
193+
save_qa()
194+
refresh_tree()
195+
set_status(f"Updated Q&A: {question_val}")
196+
197+
ttk.Button(admin_win, text="✏️ Update Selected Q&A", command=update_selected_qa).pack(pady=5)
198+
199+
refresh_tree()
200+
201+
# =========================
202+
# Styles
203+
# =========================
204+
style = ttk.Style()
205+
style.theme_use("clam")
206+
style.configure("TButton", font=("Segoe UI", 11, "bold"), padding=6)
207+
style.configure("Ask.TButton", foreground="white", background="#2196F3")
208+
style.configure("Update.TButton", foreground="white", background="#FF9800")
209+
210+
# Bubble tags
211+
chat_text = tk.Text(root, wrap="word", state="disabled", font=("Segoe UI", 12), bg="#f5f5f5")
212+
chat_text.tag_configure("user_bubble", background="#DCF8C6", foreground="black", spacing3=5, lmargin1=30, rmargin=5)
213+
chat_text.tag_configure("bot_bubble", background="#FFFFFF", foreground="black", spacing3=5, lmargin1=5, rmargin=30)
214+
215+
# =========================
216+
# Main UI
217+
# =========================
218+
main_frame = ttk.Frame(root, padding=20)
219+
main_frame.pack(expand=True, fill="both")
220+
221+
ttk.Label(main_frame, text="💬 Condo Assistant Bot",
222+
font=("Segoe UI", 22, "bold")).pack(pady=(0,5))
223+
ttk.Label(main_frame, text="Select Condo, Category, and Question to ask",
224+
font=("Segoe UI", 12)).pack(pady=(0,10))
225+
226+
# Input Frame
227+
input_frame = ttk.LabelFrame(main_frame, text="Ask a Question", padding=10)
228+
input_frame.pack(fill="x", pady=8)
229+
230+
ttk.Label(input_frame, text="Condo:").grid(row=0, column=0, padx=5, pady=2)
231+
condo_combo = ttk.Combobox(input_frame, state="readonly", width=20)
232+
condo_combo.grid(row=0, column=1, padx=5)
233+
condo_combo['values'] = get_condos()
234+
if condo_combo['values']:
235+
condo_combo.current(0)
236+
237+
ttk.Label(input_frame, text="Category:").grid(row=0, column=2, padx=5)
238+
category_combo = ttk.Combobox(input_frame, state="readonly", width=20)
239+
category_combo.grid(row=0, column=3, padx=5)
240+
241+
ttk.Label(input_frame, text="Question:").grid(row=0, column=4, padx=5)
242+
question_combo = ttk.Combobox(input_frame, state="readonly", width=30)
243+
question_combo.grid(row=0, column=5, padx=5)
244+
245+
condo_combo.bind("<<ComboboxSelected>>", update_category_dropdown)
246+
category_combo.bind("<<ComboboxSelected>>", update_question_dropdown)
247+
update_category_dropdown()
248+
update_question_dropdown()
249+
250+
ttk.Button(input_frame, text="💬 Ask", command=send_message, style="Ask.TButton").grid(row=0, column=6, padx=5)
251+
252+
# Options
253+
options_frame = ttk.LabelFrame(main_frame, text="Options", padding=10)
254+
options_frame.pack(fill="x", pady=8)
255+
ttk.Button(options_frame, text="Manage Q&A", command=manage_qa_ui, style="Update.TButton").pack(side="right")
256+
257+
# =========================
258+
# Chat Frame with Scrollbar
259+
# =========================
260+
chat_frame = ttk.Frame(main_frame)
261+
chat_frame.pack(expand=True, fill="both", pady=10)
262+
263+
# Chat Text widget
264+
chat_text = tk.Text(
265+
chat_frame,
266+
wrap="word",
267+
state="disabled",
268+
font=("Segoe UI", 12),
269+
bg="#f5f5f5",
270+
relief="flat",
271+
padx=10,
272+
pady=10
273+
)
274+
chat_text.pack(side="left", expand=True, fill="both")
275+
276+
# Scrollbar
277+
scrollbar = ttk.Scrollbar(chat_frame, orient="vertical", command=chat_text.yview)
278+
scrollbar.pack(side="right", fill="y")
279+
chat_text.configure(yscrollcommand=scrollbar.set)
280+
281+
# Bubble styling
282+
chat_text.tag_configure(
283+
"user_bubble",
284+
background="#DCF8C6",
285+
foreground="black",
286+
spacing3=5,
287+
lmargin1=30,
288+
rmargin=5,
289+
font=("Segoe UI", 12)
290+
)
291+
chat_text.tag_configure(
292+
"bot_bubble",
293+
background="#FFFFFF",
294+
foreground="black",
295+
spacing3=5,
296+
lmargin1=5,
297+
rmargin=30,
298+
font=("Segoe UI", 12)
299+
)
300+
301+
302+
# Status Bar
303+
status_var = tk.StringVar(value="Ready")
304+
ttk.Label(root, textvariable=status_var, anchor="w").pack(side=tk.BOTTOM, fill="x")
305+
306+
# =========================
307+
# Run App
308+
# =========================
309+
root.mainloop()

0 commit comments

Comments
 (0)