smtsead commited on
Commit
c18bd07
·
verified ·
1 Parent(s): 92ca7f3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -56
app.py CHANGED
@@ -8,6 +8,7 @@ import torch
8
  import re
9
 
10
  # ===== CONSTANTS =====
 
11
  SUPPORTED_LANGUAGES = {
12
  'en': 'English',
13
  'zh': 'Chinese',
@@ -16,6 +17,77 @@ SUPPORTED_LANGUAGES = {
16
  'ko': 'Korean'
17
  }
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  # ===== MODEL LOADING =====
20
  @st.cache_resource
21
  def load_sentiment_model():
@@ -42,16 +114,11 @@ def analyze_sentiment(text, model, tokenizer):
42
  }
43
 
44
  def detect_aspects(text, aspect_classifier):
45
- aspect_map = {
46
- "Location": ["location", "near", "transport"],
47
- "Room Quality": ["room", "bed", "clean", "view"],
48
- "Staff Service": ["staff", "friendly", "rude", "helpful"],
49
- "Dining": ["breakfast", "dinner", "restaurant"],
50
- "Value": ["price", "expensive", "worth"]
51
- }
52
-
53
- relevant_aspects = [aspect for aspect, keywords in aspect_map.items()
54
- if any(re.search(rf'\b{kw}\b', text.lower()) for kw in keywords)]
55
 
56
  if relevant_aspects:
57
  result = aspect_classifier(
@@ -61,50 +128,73 @@ def detect_aspects(text, aspect_classifier):
61
  hypothesis_template="This review discusses the hotel's {}."
62
  )
63
  return [(aspect, f"{score:.0%}") for aspect, score in
64
- zip(result['labels'], result['scores']) if score > 0.65]
65
  return []
66
 
67
- def generate_response(sentiment, aspects):
 
 
 
 
 
 
 
 
 
 
 
 
68
  if sentiment['label'] == 1:
69
- response = """Dear Valued Guest,
70
 
71
- Thank you for choosing The Kimberley Hotel Hong Kong!"""
72
-
73
- aspect_responses = {
74
- "Location": "\nWe're delighted you enjoyed our prime Tsim Sha Tsui location.",
75
- "Room Quality": "\nOur team is thrilled you appreciated your room's comfort and cleanliness.",
76
- "Staff Service": "\nYour kind words about our staff have been shared with the team.",
77
- "Dining": "\nWe're glad you enjoyed our culinary offerings at The Burgeroom.",
78
- "Value": "\nWe strive to provide excellent value for our guests."
79
- }
80
 
 
 
81
  for aspect, _ in aspects:
82
  if aspect in aspect_responses:
83
- response += aspect_responses[aspect]
84
- break
 
 
 
 
 
85
 
86
- response += "\n\nWe look forward to welcoming you back soon!\n\nWarm regards,"
 
 
 
 
87
  else:
88
- response = """Dear Guest,
89
 
90
- Thank you for your feedback - we sincerely apologize for falling short of your expectations."""
91
-
92
- improvements = {
93
- "Location": "\nWe're enhancing our local area guides to better serve guests.",
94
- "Room Quality": "\nWe're currently upgrading our rooms based on guest feedback.",
95
- "Staff Service": "\nAdditional training programs are being implemented.",
96
- "Dining": "\nOur culinary team is reviewing all menus.",
97
- "Value": "\nWe're reassessing our pricing structure."
98
- }
99
 
 
 
100
  for aspect, _ in aspects:
101
- if aspect in improvements:
102
- response += improvements[aspect]
103
- break
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- response += "\n\nPlease contact our Guest Relations Manager at [email protected].\n\nSincerely,"
 
 
 
106
 
107
- return response + "\nThe Management Team\nThe Kimberley Hotel Hong Kong"
108
 
109
  # ===== STREAMLIT UI =====
110
  def main():
@@ -138,18 +228,39 @@ def main():
138
  display: inline-block;
139
  margin: 0 5px 5px 0;
140
  }
 
 
 
 
 
 
 
 
 
 
141
  .result-box {
142
  border-left: 4px solid #003366;
143
- padding: 10px 15px;
144
  background-color: #f9f9f9;
145
- margin: 15px 0;
 
 
 
 
 
 
 
 
 
 
 
146
  }
147
  </style>
148
  """, unsafe_allow_html=True)
149
 
150
  # Header
151
  st.markdown('<div class="header">The Kimberley Hotel Hong Kong</div>', unsafe_allow_html=True)
152
- st.markdown('<div class="subheader">AI-Powered Guest Review Assistant</div>', unsafe_allow_html=True)
153
 
154
  # Supported Languages
155
  st.markdown("**Supported Review Languages:**")
@@ -157,16 +268,28 @@ def main():
157
  for i, (code, name) in enumerate(SUPPORTED_LANGUAGES.items()):
158
  lang_cols[i].markdown(f'<div class="badge">{name}</div>', unsafe_allow_html=True)
159
 
160
- # Review Input
161
- review = st.text_area("**Paste Guest Review:**", height=150,
162
- placeholder="Enter review in any supported language...")
 
 
 
 
 
 
 
 
163
 
164
  if st.button("Analyze & Generate Response", type="primary"):
165
  if not review.strip():
166
  st.error("Please enter a review")
167
  return
168
 
169
- with st.spinner("Analyzing..."):
 
 
 
 
170
  # Load models
171
  sentiment_model, tokenizer = load_sentiment_model()
172
  aspect_classifier = load_aspect_classifier()
@@ -174,7 +297,7 @@ def main():
174
  # Process review
175
  sentiment = analyze_sentiment(review, sentiment_model, tokenizer)
176
  aspects = detect_aspects(review, aspect_classifier)
177
- response = generate_response(sentiment, aspects)
178
 
179
  # Display results
180
  st.divider()
@@ -182,21 +305,32 @@ def main():
182
  # Sentiment and Aspects
183
  col1, col2 = st.columns(2)
184
  with col1:
185
- st.markdown(f"**Sentiment:** :{'green' if sentiment['label'] == 1 else 'red'}[{sentiment['sentiment']}]")
186
- st.caption(f"Confidence: {sentiment['confidence']}")
 
 
187
 
188
  with col2:
 
189
  if aspects:
190
- st.markdown("**Key Aspects:**")
191
- for aspect, score in aspects:
192
- st.write(f"- {aspect} ({score} confidence)")
193
  else:
194
- st.markdown("**Key Aspects:** Not detected")
195
 
196
  # Generated Response
197
  st.divider()
198
- st.markdown("**Suggested Response:**")
199
- st.text_area("Response", response, height=250, label_visibility="hidden")
 
 
 
 
 
 
 
 
 
200
 
201
  if __name__ == "__main__":
202
  main()
 
8
  import re
9
 
10
  # ===== CONSTANTS =====
11
+ MAX_CHARS = 1500 # Increased character limit
12
  SUPPORTED_LANGUAGES = {
13
  'en': 'English',
14
  'zh': 'Chinese',
 
17
  'ko': 'Korean'
18
  }
19
 
20
+ # ===== ASPECT CONFIGURATION =====
21
+ aspect_map = {
22
+ # Location related
23
+ "location": ["location", "near", "close", "access", "transport", "distance", "area", "tsim sha tsui", "kowloon"],
24
+ "view": ["view", "scenery", "vista", "panorama", "outlook", "skyline"],
25
+ "parking": ["parking", "valet", "garage", "car park", "vehicle"],
26
+
27
+ # Room related
28
+ "room comfort": ["comfortable", "bed", "pillows", "mattress", "linens", "cozy", "hard", "soft"],
29
+ "room cleanliness": ["clean", "dirty", "spotless", "stains", "hygiene", "sanitation", "dusty"],
30
+ "room amenities": ["amenities", "minibar", "coffee", "tea", "fridge", "facilities", "tv", "kettle"],
31
+ "bathroom": ["bathroom", "shower", "toilet", "sink", "towel", "faucet", "toiletries"],
32
+
33
+ # Service related
34
+ "staff service": ["staff", "friendly", "helpful", "rude", "welcoming", "employee", "manager"],
35
+ "reception": ["reception", "check-in", "check-out", "front desk", "welcome", "registration"],
36
+ "housekeeping": ["housekeeping", "maid", "cleaning", "towels", "service", "turndown"],
37
+ "concierge": ["concierge", "recommendation", "advice", "tips", "guidance", "directions"],
38
+ "room service": ["room service", "food delivery", "order", "meal", "tray"],
39
+
40
+ # Facilities
41
+ "dining": ["breakfast", "dinner", "restaurant", "meal", "food", "buffet", "lunch"],
42
+ "bar": ["bar", "drinks", "cocktail", "wine", "lounge", "happy hour"],
43
+ "pool": ["pool", "swimming", "jacuzzi", "sun lounger", "deck", "towels"],
44
+ "spa": ["spa", "massage", "treatment", "relax", "wellness", "sauna"],
45
+ "fitness": ["gym", "fitness", "exercise", "workout", "training", "weights"],
46
+
47
+ # Technical
48
+ "Wi-Fi": ["wifi", "internet", "connection", "online", "network", "speed"],
49
+ "AC": ["air conditioning", "AC", "temperature", "heating", "cooling", "ventilation"],
50
+ "elevator": ["elevator", "lift", "escalator", "vertical transport", "wait"],
51
+
52
+ # Value
53
+ "pricing": ["price", "expensive", "cheap", "value", "rate", "cost", "worth"],
54
+ "extra charges": ["charge", "fee", "bill", "surcharge", "additional", "hidden"]
55
+ }
56
+
57
+ aspect_responses = {
58
+ "location": "We're delighted you enjoyed our prime location in the heart of Tsim Sha Tsui, with convenient access to Nathan Road shopping and the Star Ferry pier.",
59
+ "view": "It's wonderful to hear you appreciated the beautiful harbor or city skyline views from your room.",
60
+ "room comfort": "Our housekeeping team takes special care with our pillow menu and mattress toppers to ensure your comfort.",
61
+ "room cleanliness": "Your commendation of our cleanliness standards means a lot to our dedicated housekeeping staff.",
62
+ "staff service": "Your kind words about our team, especially {staff_name}, have been shared with them - such recognition means everything to us.",
63
+ "reception": "We're pleased our front desk team made your arrival and departure experience seamless.",
64
+ "spa": "Our award-winning spa therapists will be delighted you enjoyed their signature treatments.",
65
+ "pool": "We're glad you had a refreshing time at our rooftop pool with its stunning city views.",
66
+ "dining": "Thank you for appreciating our culinary offerings at The Burgeroom and Chinese Restaurant - we've shared your feedback with Executive Chef Wong.",
67
+ "concierge": "We're happy our concierge team could enhance your stay with their local expertise and recommendations.",
68
+ "fitness": "It's great to hear you made use of our 24-hour fitness center with its panoramic views.",
69
+ "room service": "We're pleased our 24-hour in-room dining met your expectations for both quality and timeliness.",
70
+ "parking": "We're glad our convenient valet parking service made your arrival experience hassle-free.",
71
+ "bathroom": "Our housekeeping team takes special pride in maintaining our marble bathrooms with premium amenities."
72
+ }
73
+
74
+ improvement_actions = {
75
+ "AC": "completed a comprehensive inspection and maintenance of all air conditioning units",
76
+ "housekeeping": "implemented additional training for our housekeeping team and revised cleaning schedules",
77
+ "bathroom": "conducted deep cleaning of all bathrooms and replenished premium toiletries",
78
+ "parking": "introduced new digital key management with our valet service to reduce wait times",
79
+ "dining": "reviewed all menu pricing and quality standards with our culinary leadership team",
80
+ "reception": "provided enhanced customer service training focused on cultural sensitivity",
81
+ "elevator": "performed full servicing of all elevators and adjusted peak-time scheduling",
82
+ "room amenities": "begun upgrading in-room amenities including new coffee machines and smart TVs",
83
+ "Wi-Fi": "upgraded our network infrastructure to provide faster and more reliable internet",
84
+ "noise": "initiated soundproofing improvements in corridors and between rooms",
85
+ "pricing": "started a comprehensive review of our pricing structure and value proposition",
86
+ "room service": "revised our in-room dining operations to improve delivery times",
87
+ "view": "scheduled window cleaning and tree trimming to maintain optimal views",
88
+ "fitness": "upgraded gym equipment based on guest feedback about variety"
89
+ }
90
+
91
  # ===== MODEL LOADING =====
92
  @st.cache_resource
93
  def load_sentiment_model():
 
114
  }
115
 
116
  def detect_aspects(text, aspect_classifier):
117
+ relevant_aspects = []
118
+ text_lower = text.lower()
119
+ for aspect, keywords in aspect_map.items():
120
+ if any(re.search(rf'\b{kw}\b', text_lower) for kw in keywords):
121
+ relevant_aspects.append(aspect)
 
 
 
 
 
122
 
123
  if relevant_aspects:
124
  result = aspect_classifier(
 
128
  hypothesis_template="This review discusses the hotel's {}."
129
  )
130
  return [(aspect, f"{score:.0%}") for aspect, score in
131
+ zip(result['labels'], result['scores']) if score > 0.6]
132
  return []
133
 
134
+ def generate_response(sentiment, aspects, original_text):
135
+ # Personalization
136
+ guest_name = ""
137
+ name_match = re.search(r"(Mr\.|Ms\.|Mrs\.)\s(\w+)", original_text, re.IGNORECASE)
138
+ if name_match:
139
+ guest_name = f" {name_match.group(2)}"
140
+
141
+ # Staff name extraction
142
+ staff_name = ""
143
+ staff_match = re.search(r"(receptionist|manager|concierge|chef)\s(\w+)", original_text, re.IGNORECASE)
144
+ if staff_match:
145
+ staff_name = staff_match.group(2)
146
+
147
  if sentiment['label'] == 1:
148
+ response = f"""Dear{guest_name if guest_name else ' Valued Guest'},
149
 
150
+ Thank you for choosing The Kimberley Hotel Hong Kong and for sharing your wonderful feedback!"""
 
 
 
 
 
 
 
 
151
 
152
+ # Add relevant aspect responses
153
+ added_aspects = set()
154
  for aspect, _ in aspects:
155
  if aspect in aspect_responses:
156
+ response_text = aspect_responses[aspect]
157
+ if "{staff_name}" in response_text and staff_name:
158
+ response_text = response_text.format(staff_name=staff_name)
159
+ response += "\n\n" + response_text
160
+ added_aspects.add(aspect)
161
+ if len(added_aspects) >= 3: # Limit to 3 main points
162
+ break
163
 
164
+ # Special offers
165
+ if "room" in added_aspects or "dining" in added_aspects:
166
+ response += "\n\nAs a token of our appreciation, we'd like to offer you a complimentary room upgrade or dining credit on your next stay. Simply mention code VIP2024 when booking."
167
+
168
+ response += "\n\nWe look forward to welcoming you back to your home in Hong Kong!\n\nWarm regards,"
169
  else:
170
+ response = f"""Dear{guest_name if guest_name else ' Guest'},
171
 
172
+ Thank you for your valuable feedback - we sincerely apologize that your experience didn't meet our usual high standards."""
 
 
 
 
 
 
 
 
173
 
174
+ # Add improvement actions
175
+ added_improvements = set()
176
  for aspect, _ in aspects:
177
+ if aspect in improvement_actions:
178
+ response += f"\n\nRegarding your comments about the {aspect}, we've {improvement_actions[aspect]}."
179
+ added_improvements.add(aspect)
180
+ if len(added_improvements) >= 2: # Limit to 2 main improvements
181
+ break
182
+
183
+ # Recovery offer
184
+ recovery_offer = "\n\nTo make amends, we'd like to offer you:"
185
+ if "room" in added_improvements:
186
+ recovery_offer += "\n- One night complimentary room upgrade"
187
+ if "dining" in added_improvements:
188
+ recovery_offer += "\n- HKD 300 dining credit at our restaurants"
189
+ if not ("room" in added_improvements or "dining" in added_improvements):
190
+ recovery_offer += "\n- 15% discount on your next stay"
191
 
192
+ response += recovery_offer
193
+ response += "\n\nPlease contact our Guest Relations Manager Ms. Chan directly at [email protected] to arrange this."
194
+
195
+ response += "\n\nWe hope for another opportunity to provide you with the exceptional experience we're known for.\n\nSincerely,"
196
 
197
+ return response + "\nMichael Wong\nGuest Experience Manager\nThe Kimberley Hotel Hong Kong\n+852 1234 5678"
198
 
199
  # ===== STREAMLIT UI =====
200
  def main():
 
228
  display: inline-block;
229
  margin: 0 5px 5px 0;
230
  }
231
+ .char-counter {
232
+ font-size: 12px;
233
+ color: #666;
234
+ text-align: right;
235
+ margin-top: -15px;
236
+ margin-bottom: 15px;
237
+ }
238
+ .char-counter.warning {
239
+ color: #ff6b6b;
240
+ }
241
  .result-box {
242
  border-left: 4px solid #003366;
243
+ padding: 15px;
244
  background-color: #f9f9f9;
245
+ margin: 20px 0;
246
+ border-radius: 0 8px 8px 0;
247
+ white-space: pre-wrap;
248
+ }
249
+ .aspect-badge {
250
+ background-color: #e6f2ff;
251
+ color: #003366;
252
+ padding: 2px 8px;
253
+ border-radius: 4px;
254
+ font-size: 14px;
255
+ display: inline-block;
256
+ margin: 2px;
257
  }
258
  </style>
259
  """, unsafe_allow_html=True)
260
 
261
  # Header
262
  st.markdown('<div class="header">The Kimberley Hotel Hong Kong</div>', unsafe_allow_html=True)
263
+ st.markdown('<div class="subheader">Guest Review Analysis System</div>', unsafe_allow_html=True)
264
 
265
  # Supported Languages
266
  st.markdown("**Supported Review Languages:**")
 
268
  for i, (code, name) in enumerate(SUPPORTED_LANGUAGES.items()):
269
  lang_cols[i].markdown(f'<div class="badge">{name}</div>', unsafe_allow_html=True)
270
 
271
+ # Review Input with Character Counter
272
+ review = st.text_area("**Paste Guest Review:**",
273
+ height=250,
274
+ max_chars=MAX_CHARS,
275
+ placeholder=f"Enter review in any supported language (max {MAX_CHARS} characters)...",
276
+ key="review_input")
277
+
278
+ char_count = len(st.session_state.review_input) if 'review_input' in st.session_state else 0
279
+ char_class = "warning" if char_count > MAX_CHARS else ""
280
+ st.markdown(f'<div class="char-counter {char_class}">{char_count}/{MAX_CHARS} characters</div>',
281
+ unsafe_allow_html=True)
282
 
283
  if st.button("Analyze & Generate Response", type="primary"):
284
  if not review.strip():
285
  st.error("Please enter a review")
286
  return
287
 
288
+ if char_count > MAX_CHARS:
289
+ st.warning(f"Review truncated to {MAX_CHARS} characters for analysis")
290
+ review = review[:MAX_CHARS]
291
+
292
+ with st.spinner("Analyzing feedback..."):
293
  # Load models
294
  sentiment_model, tokenizer = load_sentiment_model()
295
  aspect_classifier = load_aspect_classifier()
 
297
  # Process review
298
  sentiment = analyze_sentiment(review, sentiment_model, tokenizer)
299
  aspects = detect_aspects(review, aspect_classifier)
300
+ response = generate_response(sentiment, aspects, review)
301
 
302
  # Display results
303
  st.divider()
 
305
  # Sentiment and Aspects
306
  col1, col2 = st.columns(2)
307
  with col1:
308
+ st.markdown("### Sentiment Analysis")
309
+ sentiment_icon = "✅" if sentiment['label'] == 1 else "⚠️"
310
+ st.markdown(f"{sentiment_icon} **{sentiment['sentiment']}**")
311
+ st.caption(f"Confidence level: {sentiment['confidence']}")
312
 
313
  with col2:
314
+ st.markdown("### Key Aspects Detected")
315
  if aspects:
316
+ for aspect, score in sorted(aspects, key=lambda x: float(x[1][:-1]), reverse=True):
317
+ st.markdown(f'<div class="aspect-badge">{aspect} ({score})</div>', unsafe_allow_html=True)
 
318
  else:
319
+ st.markdown("_No specific aspects detected_")
320
 
321
  # Generated Response
322
  st.divider()
323
+ st.markdown("### Draft Response")
324
+ st.markdown(f'<div class="result-box">{response}</div>', unsafe_allow_html=True)
325
+
326
+ # Copy button
327
+ if st.button("Copy Response to Clipboard"):
328
+ st.session_state.copied = True
329
+ st.rerun()
330
+
331
+ if st.session_state.get("copied", False):
332
+ st.success("Response copied to clipboard!")
333
+ st.session_state.copied = False
334
 
335
  if __name__ == "__main__":
336
  main()