igortech commited on
Commit
2c44f75
·
verified ·
1 Parent(s): d39338b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -78
app.py CHANGED
@@ -1,8 +1,9 @@
1
  import json
2
  import random
 
 
3
  import os
4
  import datetime
5
- import csv
6
  import gradio as gr
7
 
8
  # -----------------------------
@@ -28,141 +29,168 @@ QUOTES = load_quotes()
28
  # -----------------------------
29
  # Text helpers
30
  # -----------------------------
31
- def simple_tokenize(text: str):
32
- return set(text.lower().replace(".", "").replace(",", "").split())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- def best_match_quote(category: str, user_text: str) -> str:
35
- if category not in QUOTES or not QUOTES[category]:
 
 
 
36
  return f"No quotes found for category '{category}'."
37
-
38
- pool = [q["quote"] for q in QUOTES[category] if "quote" in q]
 
 
39
  if not pool:
40
- return f"No quotes available in category '{category}'."
41
-
42
- q_tokens = simple_tokenize(user_text)
43
  best_score = -1
44
  best_quote = None
 
45
  for quote in pool:
46
- qtoks = simple_tokenize(quote)
47
  score = len(q_tokens & qtoks)
48
  if score > best_score:
49
  best_score = score
50
  best_quote = quote
 
51
  if best_quote is None or best_score == 0:
52
- best_quote = random.choice(pool)
53
  return best_quote
54
 
55
- def generate_three_fold_response(category: str, user_text: str):
56
- quote_text = best_match_quote(category, user_text)
57
- # Simple summary: first sentence
58
- summary = quote_text.split(".")[0] + "."
59
- # Fusion: merge similar sentences (naive)
60
- fusion = " ".join(quote_text.split(".")[1:]).strip()
61
- if not fusion:
62
- fusion = summary
63
- # URL placeholder
64
- url = "No related URL available"
65
- return summary, fusion, url
66
-
67
  # -----------------------------
68
  # Gradio callbacks
69
  # -----------------------------
70
- def respond(message, history, category):
71
- if not message.strip():
 
 
 
72
  return "", history
 
73
  if not category:
 
 
 
74
  return "", history
75
- summary, fusion, url = generate_three_fold_response(category, message)
76
- bot_message = [
77
- ("Summary", summary),
78
- ("What real people say", fusion),
79
- ("Related URL", url)
80
- ]
81
- history.append((message, bot_message))
 
 
 
82
  return "", history
83
 
84
  def clear_chat():
85
  return None
86
 
87
- def stage_conversation(message, category):
88
- if not message.strip() or not category:
89
- return "Message is empty."
90
- if "staged_responses" not in QUOTES:
91
- QUOTES["staged_responses"] = []
92
- QUOTES["staged_responses"].append({"quote": message})
93
- return "Message staged for download."
94
-
95
  def upload_json(filepath):
96
- global QUOTES
97
  try:
98
  with open(filepath, "r", encoding="utf-8") as f:
99
  data = json.load(f)
100
  if not isinstance(data, dict):
101
  return gr.update(value="Upload failed: JSON root must be an object."), gr.update(choices=[])
102
  QUOTES = data
 
103
  cats = sorted(list(QUOTES.keys()))
104
- status = f"Loaded {len(cats)} categories from {filepath}."
105
  return status, gr.update(choices=cats, value=(cats[0] if cats else None))
106
  except Exception as e:
107
  return f"Error loading file: {e}", gr.update(choices=[])
108
 
109
- def download_dataset():
110
- tmp = "quotes_export.json"
111
- with open(tmp, "w", encoding="utf-8") as f:
112
- json.dump(QUOTES, f, indent=2, ensure_ascii=False)
113
- return tmp
114
-
115
- def download_conversation(history):
116
- tmp = "conversation_export.csv"
117
- with open(tmp, "w", newline="", encoding="utf-8") as f:
118
- writer = csv.writer(f)
119
- writer.writerow(["User", "Summary", "What real people say", "Related URL"])
120
- for user_msg, bot_msgs in history:
121
- summary = bot_msgs[0][1] if len(bot_msgs) > 0 else ""
122
- fusion = bot_msgs[1][1] if len(bot_msgs) > 1 else ""
123
- url = bot_msgs[2][1] if len(bot_msgs) > 2 else ""
124
- writer.writerow([user_msg, summary, fusion, url])
125
- return tmp
 
 
 
 
 
 
 
126
 
127
  # -----------------------------
128
  # UI
129
  # -----------------------------
130
  with gr.Blocks() as demo:
131
- gr.Markdown("## 🎓 College Life Chatbot — 3-Fold Responses")
132
 
133
  initial_categories = sorted(list(QUOTES.keys()))
 
134
  with gr.Row():
135
- category = gr.Dropdown(
136
- label="Category",
137
- choices=initial_categories,
138
- value=(initial_categories[0] if initial_categories else None)
139
- )
 
 
140
 
141
  chatbot = gr.Chatbot(label="Conversation", height=360, type="messages")
142
  msg = gr.Textbox(label="Your message", placeholder="Ask something like: 'Is food good in college?'", autofocus=True)
143
  send = gr.Button("Send")
144
  clear = gr.Button("Clear")
145
 
146
- with gr.Row():
147
- stage_btn = gr.Button("Stage conversation to category")
148
- stage_status = gr.Textbox(label="Stage status", interactive=False)
149
-
150
  with gr.Row():
151
  uploader = gr.File(label="Upload dataset (.json)", file_types=[".json"], type="filepath")
152
  upload_status = gr.Textbox(label="Upload status", interactive=False)
153
- dataset_download = gr.File(label="Download current dataset")
154
- convo_download = gr.File(label="Download conversation CSV")
155
 
156
  # Wire events
157
- msg.submit(respond, [msg, chatbot, category], [msg, chatbot])
158
- send.click(respond, [msg, chatbot, category], [msg, chatbot])
159
  clear.click(clear_chat, None, chatbot, queue=False)
160
 
161
- stage_btn.click(stage_conversation, [msg, category], stage_status)
162
-
163
  uploader.upload(upload_json, uploader, [upload_status, category])
164
- dataset_download.download(download_dataset)
165
- convo_download.download(lambda history: download_conversation(history), inputs=chatbot)
166
 
167
  # -----------------------------
168
  # Startup log
 
1
  import json
2
  import random
3
+ import re
4
+ import string
5
  import os
6
  import datetime
 
7
  import gradio as gr
8
 
9
  # -----------------------------
 
29
  # -----------------------------
30
  # Text helpers
31
  # -----------------------------
32
+ STOPWORDS = {
33
+ "the","a","an","and","or","but","if","then","so","than","to","of","in","on","at","for",
34
+ "is","are","was","were","be","being","been","it","that","this","these","those","with",
35
+ "as","by","from","about","into","over","after","before","up","down","out"
36
+ }
37
+
38
+ POS_HINTS = {"good","great","love","like","enjoy","awesome","amazing","nice","positive","best","fantastic","excellent"}
39
+ NEG_HINTS = {"bad","hate","dislike","worst","awful","terrible","negative","poor","meh","gross","unsafe","hard","difficult"}
40
+
41
+ punct_re = re.compile(f"[{re.escape(string.punctuation)}]")
42
+
43
+ def normalize(text: str) -> str:
44
+ return punct_re.sub(" ", (text or "").lower())
45
+
46
+ def tokenize(text: str):
47
+ return [t for t in normalize(text).split() if t and t not in STOPWORDS]
48
+
49
+ def infer_sentiment(user_text: str) -> str:
50
+ tl = normalize(user_text)
51
+ has_pos = any(w in tl for w in POS_HINTS)
52
+ has_neg = any(w in tl for w in NEG_HINTS)
53
+ if has_pos and not has_neg:
54
+ return "positive"
55
+ if has_neg and not has_pos:
56
+ return "negative"
57
+ # tie or unknown → default to positive
58
+ return "positive"
59
 
60
+ # -----------------------------
61
+ # Retrieval
62
+ # -----------------------------
63
+ def best_match_quote(category: str, sentiment: str, user_text: str) -> str:
64
+ if category not in QUOTES:
65
  return f"No quotes found for category '{category}'."
66
+ if sentiment not in QUOTES[category]:
67
+ return f"No quotes found for sentiment '{sentiment}' in category '{category}'."
68
+
69
+ pool = QUOTES[category][sentiment]
70
  if not pool:
71
+ return f"No quotes available in '{category}' → '{sentiment}'."
72
+
73
+ q_tokens = set(tokenize(user_text))
74
  best_score = -1
75
  best_quote = None
76
+
77
  for quote in pool:
78
+ qtoks = set(tokenize(quote))
79
  score = len(q_tokens & qtoks)
80
  if score > best_score:
81
  best_score = score
82
  best_quote = quote
83
+
84
  if best_quote is None or best_score == 0:
85
+ return random.choice(pool)
86
  return best_quote
87
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  # -----------------------------
89
  # Gradio callbacks
90
  # -----------------------------
91
+ def respond(message, history, category, sentiment_choice):
92
+ if not QUOTES:
93
+ bot = "No dataset loaded. Please upload a JSON file first."
94
+ history.append({"role":"user", "content":message})
95
+ history.append({"role":"assistant", "content":bot})
96
  return "", history
97
+
98
  if not category:
99
+ bot = "Please select a category."
100
+ history.append({"role":"user", "content":message})
101
+ history.append({"role":"assistant", "content":bot})
102
  return "", history
103
+
104
+ # Resolve sentiment
105
+ if sentiment_choice == "auto":
106
+ sent = infer_sentiment(message)
107
+ else:
108
+ sent = sentiment_choice # "positive" or "negative"
109
+
110
+ bot = best_match_quote(category, sent, message)
111
+ history.append({"role":"user", "content":message})
112
+ history.append({"role":"assistant", "content":bot})
113
  return "", history
114
 
115
  def clear_chat():
116
  return None
117
 
 
 
 
 
 
 
 
 
118
  def upload_json(filepath):
119
+ global QUOTES, DATA_PATH
120
  try:
121
  with open(filepath, "r", encoding="utf-8") as f:
122
  data = json.load(f)
123
  if not isinstance(data, dict):
124
  return gr.update(value="Upload failed: JSON root must be an object."), gr.update(choices=[])
125
  QUOTES = data
126
+ DATA_PATH = os.path.basename(filepath)
127
  cats = sorted(list(QUOTES.keys()))
128
+ status = f"Loaded {len(cats)} categories from {DATA_PATH}."
129
  return status, gr.update(choices=cats, value=(cats[0] if cats else None))
130
  except Exception as e:
131
  return f"Error loading file: {e}", gr.update(choices=[])
132
 
133
+ def download_current():
134
+ out_name = DATA_PATH or "quotes_export.json"
135
+ try:
136
+ if DATA_PATH and os.path.exists(DATA_PATH):
137
+ return DATA_PATH
138
+ tmp = "quotes_export.json"
139
+ with open(tmp, "w", encoding="utf-8") as f:
140
+ json.dump(QUOTES, f, indent=2, ensure_ascii=False)
141
+ return tmp
142
+ except Exception:
143
+ return None
144
+
145
+ def export_chat(history):
146
+ """Export conversation as CSV"""
147
+ try:
148
+ tmp = "conversation_export.csv"
149
+ with open(tmp, "w", newline="", encoding="utf-8") as csvfile:
150
+ writer = csv.writer(csvfile)
151
+ writer.writerow(["role","message"])
152
+ for msg in history:
153
+ writer.writerow([msg["role"], msg["content"]])
154
+ return tmp
155
+ except Exception:
156
+ return None
157
 
158
  # -----------------------------
159
  # UI
160
  # -----------------------------
161
  with gr.Blocks() as demo:
162
+ gr.Markdown("## 🎓 College Life Chatbot — Category-Aware, Similarity Matching")
163
 
164
  initial_categories = sorted(list(QUOTES.keys()))
165
+
166
  with gr.Row():
167
+ category = gr.Dropdown(label="Category",
168
+ choices=initial_categories,
169
+ value=(initial_categories[0] if initial_categories else None))
170
+ sentiment = gr.Dropdown(label="Sentiment",
171
+ choices=["auto","positive","negative"],
172
+ value="auto",
173
+ info="Auto infers from your question (good/bad/etc.)")
174
 
175
  chatbot = gr.Chatbot(label="Conversation", height=360, type="messages")
176
  msg = gr.Textbox(label="Your message", placeholder="Ask something like: 'Is food good in college?'", autofocus=True)
177
  send = gr.Button("Send")
178
  clear = gr.Button("Clear")
179
 
 
 
 
 
180
  with gr.Row():
181
  uploader = gr.File(label="Upload dataset (.json)", file_types=[".json"], type="filepath")
182
  upload_status = gr.Textbox(label="Upload status", interactive=False)
183
+ downloader = gr.File(label="Download current dataset")
184
+ export_btn = gr.File(label="Export conversation to CSV")
185
 
186
  # Wire events
187
+ msg.submit(respond, [msg, chatbot, category, sentiment], [msg, chatbot])
188
+ send.click(respond, [msg, chatbot, category, sentiment], [msg, chatbot])
189
  clear.click(clear_chat, None, chatbot, queue=False)
190
 
 
 
191
  uploader.upload(upload_json, uploader, [upload_status, category])
192
+ downloader.download(download_current)
193
+ export_btn.download(lambda history: export_chat(history), inputs=chatbot)
194
 
195
  # -----------------------------
196
  # Startup log