igortech commited on
Commit
2e57be7
·
verified ·
1 Parent(s): 9330613

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -161
app.py CHANGED
@@ -1,203 +1,146 @@
1
  import json
2
  import random
3
- import re
4
- import string
5
- import os
6
- import datetime
7
  import difflib
 
8
  import csv
 
9
  import gradio as gr
10
- import tempfile
11
- import shutil
12
 
13
  # -----------------------------
14
  # Config / data loading
15
  # -----------------------------
16
  DATA_PATH = "quotes.json"
17
 
18
- def load_quotes():
19
- if os.path.exists(DATA_PATH):
20
- try:
21
- with open(DATA_PATH, "r", encoding="utf-8") as f:
22
- data = json.load(f)
23
- if isinstance(data, dict):
24
- print(f"Loaded dataset from {DATA_PATH} with {len(data)} categories.")
25
- return data
26
- except Exception as e:
27
- print(f"Failed to load {DATA_PATH}: {e}")
28
- print("No dataset file found. Upload one via the UI.")
29
- return {}
30
-
31
- QUOTES = load_quotes()
32
 
33
  # -----------------------------
34
- # Text helpers
35
  # -----------------------------
36
- STOPWORDS = {
37
- "the","a","an","and","or","but","if","then","so","than","to","of","in","on","at","for",
38
- "is","are","was","were","be","being","been","it","that","this","these","those","with",
39
- "as","by","from","about","into","over","after","before","up","down","out"
40
- }
41
 
42
- POS_HINTS = {"good","great","love","like","enjoy","awesome","amazing","nice","positive","best","fantastic","excellent"}
43
- NEG_HINTS = {"bad","hate","dislike","worst","awful","terrible","negative","poor","meh","gross","unsafe","hard","difficult"}
44
 
45
- punct_re = re.compile(f"[{re.escape(string.punctuation)}]")
 
46
 
47
- def normalize(text: str) -> str:
48
- return punct_re.sub(" ", (text or "").lower())
 
49
 
50
- def tokenize(text: str):
51
- return [t for t in normalize(text).split() if t and t not in STOPWORDS]
52
 
53
- def infer_sentiment(user_text: str) -> str:
54
- tl = normalize(user_text)
55
- has_pos = any(w in tl for w in POS_HINTS)
56
- has_neg = any(w in tl for w in NEG_HINTS)
57
- if has_pos and not has_neg:
58
- return "positive"
59
- if has_neg and not has_pos:
60
- return "negative"
61
- return "positive"
62
 
63
- # -----------------------------
64
- # Retrieval
65
- # -----------------------------
66
- def best_match_quote(category: str, user_text: str) -> str:
67
- """Pick the quote with highest keyword overlap; fallback to random."""
68
- if category not in QUOTES:
69
- return f"No quotes found for category '{category}'."
70
- pool = QUOTES[category]
71
- if not pool:
72
- return f"No quotes available in '{category}'."
73
-
74
- q_tokens = set(tokenize(user_text))
75
- best_score = -1
76
- best_quote = None
77
-
78
- for entry in pool:
79
- quote = entry["quote"]
80
- qtoks = set(tokenize(quote))
81
- score = len(q_tokens & qtoks)
82
- if score > best_score:
83
- best_score = score
84
- best_quote = quote
85
-
86
- if best_quote is None or best_score == 0:
87
- return random.choice([entry["quote"] for entry in pool])
88
- return best_quote
89
 
90
- # -----------------------------
91
- # Gradio callbacks
92
- # -----------------------------
93
- def respond(message, history, category):
94
- if not QUOTES:
95
- bot = "No dataset loaded. Please upload a JSON file first."
96
- history.append({"role":"user","content":message})
97
- history.append({"role":"assistant","content":bot})
98
- return "", history
99
-
100
- if not category:
101
- bot = "Please select a category."
102
- history.append({"role":"user","content":message})
103
- history.append({"role":"assistant","content":bot})
104
- return "", history
105
-
106
- # 3-fold response
107
- quote = best_match_quote(category, message)
108
- summary = f"Summary: The user question seems related to '{category}'."
109
- fusion = f"Details: {quote}"
110
- link = "Reference: [No link provided]"
111
-
112
- response = f"{summary}\n{fusion}\n{link}"
113
- history.append({"role":"user","content":message})
114
- history.append({"role":"assistant","content":response})
115
- return "", history
116
-
117
- def clear_chat():
118
- return []
119
-
120
- def upload_json(filepath):
121
- """Accept a file path, load it into memory, and update category dropdown."""
122
- global QUOTES, DATA_PATH
123
- try:
124
- with open(filepath, "r", encoding="utf-8") as f:
125
- data = json.load(f)
126
- if not isinstance(data, dict):
127
- return gr.update(value="Upload failed: JSON root must be an object."), gr.update(choices=[])
128
- QUOTES = data
129
- DATA_PATH = os.path.basename(filepath)
130
- cats = sorted(list(QUOTES.keys()))
131
- status = f"Loaded {len(cats)} categories from {DATA_PATH}."
132
- return status, gr.update(choices=cats, value=(cats[0] if cats else None))
133
- except Exception as e:
134
- return f"Error loading file: {e}", gr.update(choices=[])
135
-
136
- def download_current_csv(history):
137
- if not history:
138
  return None
139
- tmpdir = tempfile.mkdtemp()
140
- filepath = os.path.join(tmpdir, "conversation_export.csv")
141
- with open(filepath, "w", newline="", encoding="utf-8") as f:
142
  writer = csv.writer(f)
143
- writer.writerow(["role","content"])
144
- for msg in history:
145
  writer.writerow([msg["role"], msg["content"]])
146
- return filepath
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- def download_current_json():
149
- tmpdir = tempfile.mkdtemp()
150
- filepath = os.path.join(tmpdir, "quotes_export.json")
151
- with open(filepath, "w", encoding="utf-8") as f:
152
- json.dump(QUOTES, f, indent=2, ensure_ascii=False)
153
- return filepath
154
 
155
  # -----------------------------
156
  # UI
157
  # -----------------------------
158
  with gr.Blocks() as demo:
159
- gr.Markdown("## 🎓 College Life Chatbot — 3-Fold Response")
160
-
161
- initial_categories = sorted(list(QUOTES.keys()))
162
 
163
  with gr.Row():
164
- category = gr.Dropdown(
165
- label="Category",
166
- choices=initial_categories,
167
- value=(initial_categories[0] if initial_categories else None)
168
- )
 
169
 
170
- chatbot = gr.Chatbot(label="Conversation", height=360, type="messages")
171
- msg = gr.Textbox(label="Your message", placeholder="Ask something like: 'Is food good in college?'", autofocus=True)
172
- send = gr.Button("Send")
173
- clear = gr.Button("Clear")
 
 
 
174
 
175
- with gr.Row():
176
- download_csv_btn = gr.Button("Export conversation to CSV")
177
- download_csv_file = gr.File(label="Download CSV", interactive=False)
 
 
178
 
179
- download_json_btn = gr.Button("Download current dataset")
180
- download_json_file = gr.File(label="Download JSON", interactive=False)
 
181
 
182
- uploader = gr.File(label="Upload dataset (.json)", file_types=[".json"], type="filepath")
183
- upload_status = gr.Textbox(label="Upload status", interactive=False)
184
 
185
- # Wire events
186
- msg.submit(respond, [msg, chatbot, category], [msg, chatbot])
187
- send.click(respond, [msg, chatbot, category], [msg, chatbot])
188
- clear.click(clear_chat, None, chatbot, queue=False)
189
 
190
- uploader.upload(upload_json, uploader, [upload_status, category])
191
- download_csv_btn.click(download_current_csv, inputs=chatbot, outputs=download_csv_file)
192
- download_json_btn.click(download_current_json, outputs=download_json_file)
193
 
194
  # -----------------------------
195
- # Startup log
196
  # -----------------------------
197
- print(f"===== Application Startup at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====")
198
- if QUOTES:
199
- for cat, entries in QUOTES.items():
200
- print(f" - {cat}: {len(entries)} entries")
201
-
202
  if __name__ == "__main__":
203
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
  import json
2
  import random
 
 
 
 
3
  import difflib
4
+ import os
5
  import csv
6
+ import datetime
7
  import gradio as gr
 
 
8
 
9
  # -----------------------------
10
  # Config / data loading
11
  # -----------------------------
12
  DATA_PATH = "quotes.json"
13
 
14
+ if os.path.exists(DATA_PATH):
15
+ with open(DATA_PATH, "r") as f:
16
+ dataset = json.load(f)
17
+ else:
18
+ dataset = {"staged_responses": []}
19
+
 
 
 
 
 
 
 
 
20
 
21
  # -----------------------------
22
+ # Helpers
23
  # -----------------------------
24
+ def find_best_quotes(category, user_input, top_n=3, threshold=0.4):
25
+ """Find top_n most similar quotes for a category or return fallback if none match well"""
26
+ if category not in dataset or not dataset[category]:
27
+ return [f"No data about {user_input} (unknown)."]
 
28
 
29
+ quotes = [q["quote"] for q in dataset[category]]
30
+ scores = [difflib.SequenceMatcher(None, user_input.lower(), q.lower()).ratio() for q in quotes]
31
 
32
+ # Pair scores with quotes and sort
33
+ scored_quotes = sorted(zip(scores, quotes), key=lambda x: x[0], reverse=True)
34
 
35
+ best_score = scored_quotes[0][0] if scored_quotes else 0
36
+ if best_score < threshold:
37
+ return [f"No data about {user_input} (unknown)."]
38
 
39
+ return [q for _, q in scored_quotes[:top_n]]
 
40
 
 
 
 
 
 
 
 
 
 
41
 
42
+ def save_conversation_to_staged(messages, category):
43
+ """Stage conversation under chosen category in dataset (downloadable)"""
44
+ if not messages:
45
+ return "No conversation to stage."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ convo_text = " ".join([msg["content"] for msg in messages if msg["role"] == "user" or msg["role"] == "assistant"])
48
+
49
+ if category not in dataset:
50
+ dataset[category] = []
51
+
52
+ dataset[category].append({"quote": convo_text})
53
+ return f"Conversation staged under {category}."
54
+
55
+
56
+ def export_conversation_csv(messages):
57
+ """Export current conversation as CSV and return filename"""
58
+ if not messages:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  return None
60
+
61
+ filename = f"conversation_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
62
+ with open(filename, "w", newline="", encoding="utf-8") as f:
63
  writer = csv.writer(f)
64
+ writer.writerow(["role", "content"])
65
+ for msg in messages:
66
  writer.writerow([msg["role"], msg["content"]])
67
+ return filename
68
+
69
+
70
+ def download_dataset():
71
+ """Save dataset to a JSON file and return filename"""
72
+ filename = f"quotes_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
73
+ with open(filename, "w", encoding="utf-8") as f:
74
+ json.dump(dataset, f, indent=2, ensure_ascii=False)
75
+ return filename
76
+
77
+
78
+ # -----------------------------
79
+ # Chatbot core
80
+ # -----------------------------
81
+ def chatbot_reply(user_input, history, category):
82
+ """Handle user query and return chatbot response with updated history"""
83
+ if not user_input.strip():
84
+ return history, history
85
+
86
+ # Find 3-fold response
87
+ responses = find_best_quotes(category, user_input, top_n=3)
88
+
89
+ # Format assistant reply
90
+ reply = "\n---\n".join(responses)
91
+
92
+ # Append to history
93
+ history.append({"role": "user", "content": user_input})
94
+ history.append({"role": "assistant", "content": reply})
95
+
96
+ return history, history
97
+
98
+
99
+ def clear_conversation():
100
+ return [], []
101
 
 
 
 
 
 
 
102
 
103
  # -----------------------------
104
  # UI
105
  # -----------------------------
106
  with gr.Blocks() as demo:
107
+ gr.Markdown("# 🎓 Campus Life Chatbot")
 
 
108
 
109
  with gr.Row():
110
+ with gr.Column():
111
+ category_dropdown = gr.Dropdown(
112
+ choices=list(dataset.keys()),
113
+ value="Food" if "Food" in dataset else None,
114
+ label="Select Category",
115
+ )
116
 
117
+ chatbot = gr.Chatbot(label="Conversation", height=360, type="messages")
118
+ user_input = gr.Textbox(
119
+ placeholder="Type your message and press Enter",
120
+ show_label=False,
121
+ )
122
+ send_btn = gr.Button("Send")
123
+ clear_btn = gr.Button("Clear")
124
 
125
+ export_csv_btn = gr.Button("📤 Export Conversation to CSV")
126
+ stage_btn = gr.Button("Stage Conversation to Category")
127
+ download_json_btn = gr.Button("💾 Download Current Dataset")
128
+
129
+ export_status = gr.Label(label="Status", value="")
130
 
131
+ # Events
132
+ send_btn.click(chatbot_reply, [user_input, chatbot, category_dropdown], [chatbot, chatbot])
133
+ user_input.submit(chatbot_reply, [user_input, chatbot, category_dropdown], [chatbot, chatbot])
134
 
135
+ clear_btn.click(clear_conversation, outputs=[chatbot, chatbot])
 
136
 
137
+ export_csv_btn.click(export_conversation_csv, [chatbot], outputs=export_status)
138
+ stage_btn.click(save_conversation_to_staged, [chatbot, category_dropdown], outputs=export_status)
139
+ download_json_btn.click(download_dataset, outputs=export_status)
 
140
 
 
 
 
141
 
142
  # -----------------------------
143
+ # Launch
144
  # -----------------------------
 
 
 
 
 
145
  if __name__ == "__main__":
146
+ demo.launch()