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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -259
app.py CHANGED
@@ -4,335 +4,199 @@ from transformers import (
4
  AutoModelForSequenceClassification,
5
  AutoTokenizer
6
  )
7
- from langdetect import detect
8
  import torch
9
  import re
10
 
11
- # ===== MODEL LOADING =====
12
- # Translation models configuration
13
- TRANSLATION_MODELS = {
14
- # Translations to English
15
- 'fr-en': 'Helsinki-NLP/opus-mt-fr-en', # French to English
16
- 'es-en': 'Helsinki-NLP/opus-mt-es-en', # Spanish to English
17
- 'de-en': 'Helsinki-NLP/opus-mt-de-en', # German to English
18
- 'zh-en': 'Helsinki-NLP/opus-mt-zh-en', # Chinese to English
19
- 'ja-en': 'Helsinki-NLP/opus-mt-ja-en', # Japanese to English
20
-
21
- # Translations from English
22
- 'en-fr': 'Helsinki-NLP/opus-mt-en-fr', # English to French
23
- 'en-es': 'Helsinki-NLP/opus-mt-en-es', # English to Spanish
24
- 'en-de': 'Helsinki-NLP/opus-mt-en-de', # English to German
25
- 'en-zh': 'Helsinki-NLP/opus-mt-en-zh', # English to Chinese
26
- 'en-ja': 'Helsinki-NLP/opus-mt-en-ja' # English to Japanese
27
  }
28
 
29
- # Sentiment analysis model
30
- SENTIMENT_MODEL_NAME = "smtsead/fine_tuned_bertweet_hotel"
31
- SENTIMENT_TOKENIZER = 'finiteautomata/bertweet-base-sentiment-analysis'
32
-
33
- # Aspect classification model
34
- ASPECT_MODEL = "MoritzLaurer/deberta-v3-base-zeroshot-v1.1-all-33"
35
-
36
- # Initialize models (with caching to avoid reloading)
37
- @st.cache_resource
38
- def load_translation_model(src_lang, target_lang='en'):
39
- """Load translation model for specific language pair"""
40
- model_key = f"{src_lang}-{target_lang}"
41
- if model_key not in TRANSLATION_MODELS:
42
- raise ValueError(f"Unsupported translation: {src_lang}→{target_lang}")
43
- return pipeline("translation", model=TRANSLATION_MODELS[model_key])
44
-
45
  @st.cache_resource
46
  def load_sentiment_model():
47
- """Load sentiment analysis model"""
48
- model = AutoModelForSequenceClassification.from_pretrained(SENTIMENT_MODEL_NAME)
49
- tokenizer = AutoTokenizer.from_pretrained(SENTIMENT_TOKENIZER)
50
  return model, tokenizer
51
 
52
  @st.cache_resource
53
  def load_aspect_classifier():
54
- """Load aspect classification model"""
55
- return pipeline("zero-shot-classification", model=ASPECT_MODEL)
56
-
57
- # ===== PIPELINE FUNCTIONS =====
58
- def translate_text(text, target_lang='en'):
59
- """Translate text to target language"""
60
- try:
61
- # Detect source language
62
- src_lang = detect(text)
63
-
64
- # Handle special case (English to other languages)
65
- if src_lang == 'en' and target_lang != 'en':
66
- translator = load_translation_model('en', target_lang)
67
- else:
68
- translator = load_translation_model(src_lang, target_lang)
69
-
70
- # Perform translation
71
- result = translator(text)[0]['translation_text']
72
-
73
- return {
74
- 'original': text,
75
- 'translation': result,
76
- 'source_lang': src_lang,
77
- 'target_lang': target_lang
78
- }
79
- except Exception as e:
80
- return {'error': str(e)}
81
 
 
82
  def analyze_sentiment(text, model, tokenizer):
83
- """Analyze sentiment of text (positive/negative)"""
84
  inputs = tokenizer(text, padding=True, truncation=True, max_length=512, return_tensors='pt')
85
-
86
  with torch.no_grad():
87
  outputs = model(**inputs)
88
  probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
89
  predicted_label = torch.argmax(probs).item()
90
  confidence = torch.max(probs).item()
91
-
92
  return {
93
  'label': predicted_label,
94
- 'confidence': confidence,
95
  'sentiment': 'POSITIVE' if predicted_label else 'NEGATIVE'
96
  }
97
 
98
  def detect_aspects(text, aspect_classifier):
99
- """Detect aspects mentioned in text"""
100
- # Aspect mapping with keywords
101
  aspect_map = {
102
- "location": ["location", "near", "close", "access", "transport", "distance", "area"],
103
- "view": ["view", "scenery", "vista", "panorama", "outlook"],
104
- "parking": ["parking", "valet", "garage", "car park", "vehicle"],
105
- "room comfort": ["comfortable", "bed", "pillows", "mattress", "linens", "cozy"],
106
- "room cleanliness": ["clean", "dirty", "spotless", "stains", "hygiene", "sanitation"],
107
- "room amenities": ["amenities", "minibar", "coffee", "tea", "fridge", "facilities"],
108
- "bathroom": ["bathroom", "shower", "toilet", "sink", "towel", "faucet"],
109
- "staff service": ["staff", "friendly", "helpful", "rude", "welcoming", "employee"],
110
- "reception": ["reception", "check-in", "check-out", "front desk", "welcome"],
111
- "housekeeping": ["housekeeping", "maid", "cleaning", "towels", "service"],
112
- "concierge": ["concierge", "recommendation", "advice", "tips", "guidance"],
113
- "room service": ["room service", "food delivery", "order", "meal"],
114
- "dining": ["breakfast", "dinner", "restaurant", "meal", "food", "buffet"],
115
- "bar": ["bar", "drinks", "cocktail", "wine", "lounge"],
116
- "pool": ["pool", "swimming", "jacuzzi", "sun lounger", "deck"],
117
- "spa": ["spa", "massage", "treatment", "relax", "wellness"],
118
- "fitness": ["gym", "fitness", "exercise", "workout", "training"],
119
- "Wi-Fi": ["wifi", "internet", "connection", "online", "network"],
120
- "AC": ["air conditioning", "AC", "temperature", "heating", "cooling"],
121
- "elevator": ["elevator", "lift", "escalator", "vertical transport"],
122
- "pricing": ["price", "expensive", "cheap", "value", "rate", "cost"],
123
- "extra charges": ["charge", "fee", "bill", "surcharge", "additional"]
124
  }
125
 
126
- # First stage: keyword filtering
127
- relevant_aspects = []
128
- text_lower = text.lower()
129
- for aspect, keywords in aspect_map.items():
130
- if any(re.search(rf'\b{kw}\b', text_lower) for kw in keywords):
131
- relevant_aspects.append(aspect)
132
 
133
- # Second stage: zero-shot classification
134
  if relevant_aspects:
135
  result = aspect_classifier(
136
  text,
137
  candidate_labels=relevant_aspects,
138
  multi_label=True,
139
- hypothesis_template="This review mentions something about the {} of the hotel."
140
  )
141
- # Return aspects with score > 0.65
142
- return [(aspect, round(score, 2)) for aspect, score in
143
  zip(result['labels'], result['scores']) if score > 0.65]
144
  return []
145
 
146
- def generate_response(label, aspects, text):
147
- """Generate professional response based on sentiment and aspects"""
148
- if label == 1:
149
- # Positive response template
150
- response = "Dear Valued Guest,\n\nThank you for sharing your positive experience with us!\n"
151
 
152
- # Positive aspect responses
153
  aspect_responses = {
154
- "location": "We're delighted you enjoyed our prime location and convenient access to local attractions.",
155
- "view": "It's wonderful to hear you appreciated the beautiful views from our property.",
156
- "room comfort": "Our team is thrilled you found your room comfortable and inviting.",
157
- "room cleanliness": "Your commendation of our cleanliness standards means a lot to our housekeeping staff.",
158
- "staff service": "Your kind words about our team, especially {staff_name}, have been shared with them.",
159
- "reception": "We're pleased our front desk team made your arrival/departure seamless.",
160
- "spa": "Our spa practitioners will be delighted you enjoyed their treatments.",
161
- "pool": "We're glad you had a refreshing time at our pool facilities.",
162
- "dining": "Thank you for appreciating our culinary offerings - we've shared your feedback with our chefs.",
163
- "concierge": "We're happy our concierge could enhance your stay with local insights.",
164
- "fitness": "It's great to hear you made use of our well-equipped fitness center.",
165
- "room service": "We're pleased our in-room dining met your expectations for quality and timeliness."
166
  }
167
 
168
- # Add specific aspect responses
169
- added_aspects = set()
170
  for aspect, _ in aspects:
171
  if aspect in aspect_responses:
172
- if aspect == "staff service" and "lourdes" in text.lower():
173
- response += "\n" + aspect_responses[aspect].format(staff_name="Lourdes")
174
- else:
175
- response += "\n" + aspect_responses[aspect]
176
- added_aspects.add(aspect)
177
- if len(added_aspects) >= 3:
178
- break
179
 
180
- response += "\n\nWe can't wait to welcome you back for another exceptional stay!\n\nWarm regards,"
181
  else:
182
- # Negative response template
183
- response = "Dear Guest,\n\nThank you for your feedback - we're truly sorry your experience didn't meet our usual standards.\n"
 
184
 
185
- # Improvement actions for negative aspects
186
- improvement_actions = {
187
- "AC": "completed a full inspection and maintenance of all AC units",
188
- "housekeeping": "retrained our housekeeping team and adjusted schedules",
189
- "bathroom": "conducted deep cleaning and maintenance on all bathrooms",
190
- "parking": "implemented new key management protocols with our valet service",
191
- "dining": "reviewed our menu pricing and quality with the culinary team",
192
- "reception": "provided additional customer service training to our front desk",
193
- "elevator": "performed full servicing and testing of all elevators",
194
- "room amenities": "begun upgrading in-room amenities based on guest feedback",
195
- "noise": "initiated soundproofing improvements in affected areas",
196
- "pricing": "started a comprehensive review of our pricing structure"
197
  }
198
 
199
- # Add specific improvement actions
200
- added_aspects = set()
201
  for aspect, _ in aspects:
202
- if aspect in improvement_actions and aspect not in added_aspects:
203
- response += f"\nRegarding the {aspect}, we've {improvement_actions[aspect]}."
204
- added_aspects.add(aspect)
205
- if len(added_aspects) >= 2:
206
- break
207
 
208
- response += "\n\nWe sincerely appreciate your patience and hope you'll give us another opportunity to provide the quality experience you deserve.\n\nSincerely,"
209
 
210
- return response + "\nThe Management Team\n"
211
 
212
- # ===== STREAMLIT APP =====
213
  def main():
214
- st.set_page_config(page_title="Review Response Generator", page_icon="📝")
215
- st.title("📝 Hotel Review Response Generator")
 
 
 
 
 
 
216
  st.markdown("""
217
- This tool helps hotel managers generate professional responses to guest reviews in multiple languages.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
- **How it works:**
220
- 1. Enter a guest review in any language
221
- 2. The system will analyze sentiment and key aspects
222
- 3. A professional response will be generated
223
- 4. The response will be translated back to the original language
224
- """)
225
 
226
- # Initialize session state
227
- if 'review_text' not in st.session_state:
228
- st.session_state.review_text = ""
229
- if 'translated_text' not in st.session_state:
230
- st.session_state.translated_text = ""
231
- if 'sentiment_result' not in st.session_state:
232
- st.session_state.sentiment_result = None
233
- if 'aspects' not in st.session_state:
234
- st.session_state.aspects = []
235
- if 'response' not in st.session_state:
236
- st.session_state.response = ""
237
- if 'translated_response' not in st.session_state:
238
- st.session_state.translated_response = ""
239
 
240
- # Input review
241
- review_text = st.text_area("Enter the guest review:", height=150)
 
242
 
243
- if st.button("Generate Response"):
244
- if not review_text.strip():
245
- st.error("Please enter a review first.")
246
  return
247
 
248
- with st.spinner("Processing review..."):
249
- # Step 1: Translate to English if needed
250
- translation_result = translate_text(review_text)
251
-
252
- if 'error' in translation_result:
253
- st.error(f"Translation error: {translation_result['error']}")
254
- return
255
-
256
- st.session_state.review_text = review_text
257
- st.session_state.translated_text = translation_result['translation']
258
- source_lang = translation_result['source_lang']
259
-
260
- # Step 2: Sentiment analysis
261
- sentiment_model, sentiment_tokenizer = load_sentiment_model()
262
- sentiment_result = analyze_sentiment(
263
- st.session_state.translated_text,
264
- sentiment_model,
265
- sentiment_tokenizer
266
- )
267
- st.session_state.sentiment_result = sentiment_result
268
-
269
- # Step 3: Aspect detection
270
  aspect_classifier = load_aspect_classifier()
271
- st.session_state.aspects = detect_aspects(
272
- st.session_state.translated_text,
273
- aspect_classifier
274
- )
275
 
276
- # Step 4: Generate response
277
- st.session_state.response = generate_response(
278
- sentiment_result['label'],
279
- st.session_state.aspects,
280
- st.session_state.translated_text
281
- )
282
 
283
- # Step 5: Translate response back to original language if needed
284
- if source_lang != 'en':
285
- translation_back = translate_text(
286
- st.session_state.response,
287
- target_lang=source_lang
288
- )
289
- if 'error' not in translation_back:
290
- st.session_state.translated_response = translation_back['translation']
291
- else:
292
- st.warning(f"Couldn't translate response back: {translation_back['error']}")
293
- st.session_state.translated_response = st.session_state.response
294
- else:
295
- st.session_state.translated_response = st.session_state.response
296
-
297
- # Display results
298
- if st.session_state.review_text:
299
- st.divider()
300
- st.subheader("Analysis Results")
301
-
302
- # Original review
303
- with st.expander("Original Review", expanded=True):
304
- st.write(st.session_state.review_text)
305
-
306
- # Translation (if applicable)
307
- if hasattr(st.session_state, 'translated_text') and st.session_state.translated_text:
308
- with st.expander("Translated to English"):
309
- st.write(st.session_state.translated_text)
310
-
311
- # Sentiment analysis
312
- if st.session_state.sentiment_result:
313
- sentiment = st.session_state.sentiment_result
314
- sentiment_color = "green" if sentiment['label'] == 1 else "red"
315
- st.markdown(f"**Sentiment:** :{sentiment_color}[{sentiment['sentiment']}] (confidence: {sentiment['confidence']:.2f})")
316
-
317
- # Detected aspects
318
- if st.session_state.aspects:
319
- st.markdown("**Key Aspects Detected:**")
320
- for aspect, confidence in st.session_state.aspects:
321
- st.write(f"- {aspect.title()} (confidence: {confidence})")
322
-
323
- # Generated response
324
- if st.session_state.response:
325
  st.divider()
326
- st.subheader("Generated Response")
327
 
 
328
  col1, col2 = st.columns(2)
329
  with col1:
330
- st.markdown("**English Version**")
331
- st.text_area("English response", st.session_state.response, height=300, label_visibility="collapsed")
332
 
333
  with col2:
334
- st.markdown("**Translated Back**")
335
- st.text_area("Translated response", st.session_state.translated_response, height=300, label_visibility="collapsed")
 
 
 
 
 
 
 
 
 
336
 
337
  if __name__ == "__main__":
338
  main()
 
4
  AutoModelForSequenceClassification,
5
  AutoTokenizer
6
  )
 
7
  import torch
8
  import re
9
 
10
+ # ===== CONSTANTS =====
11
+ SUPPORTED_LANGUAGES = {
12
+ 'en': 'English',
13
+ 'zh': 'Chinese',
14
+ 'yue': 'Cantonese',
15
+ 'ja': 'Japanese',
16
+ 'ko': 'Korean'
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
+ # ===== MODEL LOADING =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  @st.cache_resource
21
  def load_sentiment_model():
22
+ model = AutoModelForSequenceClassification.from_pretrained("smtsead/fine_tuned_bertweet_hotel")
23
+ tokenizer = AutoTokenizer.from_pretrained('finiteautomata/bertweet-base-sentiment-analysis')
 
24
  return model, tokenizer
25
 
26
  @st.cache_resource
27
  def load_aspect_classifier():
28
+ return pipeline("zero-shot-classification", model="MoritzLaurer/deberta-v3-base-zeroshot-v1.1-all-33")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ # ===== CORE FUNCTIONS =====
31
  def analyze_sentiment(text, model, tokenizer):
 
32
  inputs = tokenizer(text, padding=True, truncation=True, max_length=512, return_tensors='pt')
 
33
  with torch.no_grad():
34
  outputs = model(**inputs)
35
  probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
36
  predicted_label = torch.argmax(probs).item()
37
  confidence = torch.max(probs).item()
 
38
  return {
39
  'label': predicted_label,
40
+ 'confidence': f"{confidence:.0%}",
41
  'sentiment': 'POSITIVE' if predicted_label else 'NEGATIVE'
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(
58
  text,
59
  candidate_labels=relevant_aspects,
60
  multi_label=True,
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():
111
+ # Page Config
112
+ st.set_page_config(
113
+ page_title="Kimberley Review Assistant",
114
+ page_icon="🏨",
115
+ layout="centered"
116
+ )
117
+
118
+ # Custom CSS
119
  st.markdown("""
120
+ <style>
121
+ .header {
122
+ color: #003366;
123
+ font-size: 28px;
124
+ font-weight: bold;
125
+ margin-bottom: 10px;
126
+ }
127
+ .subheader {
128
+ color: #666666;
129
+ font-size: 16px;
130
+ margin-bottom: 30px;
131
+ }
132
+ .badge {
133
+ background-color: #e6f2ff;
134
+ color: #003366;
135
+ padding: 3px 10px;
136
+ border-radius: 15px;
137
+ font-size: 14px;
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:**")
156
+ lang_cols = st.columns(5)
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()
 
 
 
 
173
 
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()
 
181
 
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()