Grp38Project / app.py
smtsead's picture
Update app.py
c18bd07 verified
raw
history blame
15.4 kB
import streamlit as st
from transformers import (
pipeline,
AutoModelForSequenceClassification,
AutoTokenizer
)
import torch
import re
# ===== CONSTANTS =====
MAX_CHARS = 1500 # Increased character limit
SUPPORTED_LANGUAGES = {
'en': 'English',
'zh': 'Chinese',
'yue': 'Cantonese',
'ja': 'Japanese',
'ko': 'Korean'
}
# ===== ASPECT CONFIGURATION =====
aspect_map = {
# Location related
"location": ["location", "near", "close", "access", "transport", "distance", "area", "tsim sha tsui", "kowloon"],
"view": ["view", "scenery", "vista", "panorama", "outlook", "skyline"],
"parking": ["parking", "valet", "garage", "car park", "vehicle"],
# Room related
"room comfort": ["comfortable", "bed", "pillows", "mattress", "linens", "cozy", "hard", "soft"],
"room cleanliness": ["clean", "dirty", "spotless", "stains", "hygiene", "sanitation", "dusty"],
"room amenities": ["amenities", "minibar", "coffee", "tea", "fridge", "facilities", "tv", "kettle"],
"bathroom": ["bathroom", "shower", "toilet", "sink", "towel", "faucet", "toiletries"],
# Service related
"staff service": ["staff", "friendly", "helpful", "rude", "welcoming", "employee", "manager"],
"reception": ["reception", "check-in", "check-out", "front desk", "welcome", "registration"],
"housekeeping": ["housekeeping", "maid", "cleaning", "towels", "service", "turndown"],
"concierge": ["concierge", "recommendation", "advice", "tips", "guidance", "directions"],
"room service": ["room service", "food delivery", "order", "meal", "tray"],
# Facilities
"dining": ["breakfast", "dinner", "restaurant", "meal", "food", "buffet", "lunch"],
"bar": ["bar", "drinks", "cocktail", "wine", "lounge", "happy hour"],
"pool": ["pool", "swimming", "jacuzzi", "sun lounger", "deck", "towels"],
"spa": ["spa", "massage", "treatment", "relax", "wellness", "sauna"],
"fitness": ["gym", "fitness", "exercise", "workout", "training", "weights"],
# Technical
"Wi-Fi": ["wifi", "internet", "connection", "online", "network", "speed"],
"AC": ["air conditioning", "AC", "temperature", "heating", "cooling", "ventilation"],
"elevator": ["elevator", "lift", "escalator", "vertical transport", "wait"],
# Value
"pricing": ["price", "expensive", "cheap", "value", "rate", "cost", "worth"],
"extra charges": ["charge", "fee", "bill", "surcharge", "additional", "hidden"]
}
aspect_responses = {
"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.",
"view": "It's wonderful to hear you appreciated the beautiful harbor or city skyline views from your room.",
"room comfort": "Our housekeeping team takes special care with our pillow menu and mattress toppers to ensure your comfort.",
"room cleanliness": "Your commendation of our cleanliness standards means a lot to our dedicated housekeeping staff.",
"staff service": "Your kind words about our team, especially {staff_name}, have been shared with them - such recognition means everything to us.",
"reception": "We're pleased our front desk team made your arrival and departure experience seamless.",
"spa": "Our award-winning spa therapists will be delighted you enjoyed their signature treatments.",
"pool": "We're glad you had a refreshing time at our rooftop pool with its stunning city views.",
"dining": "Thank you for appreciating our culinary offerings at The Burgeroom and Chinese Restaurant - we've shared your feedback with Executive Chef Wong.",
"concierge": "We're happy our concierge team could enhance your stay with their local expertise and recommendations.",
"fitness": "It's great to hear you made use of our 24-hour fitness center with its panoramic views.",
"room service": "We're pleased our 24-hour in-room dining met your expectations for both quality and timeliness.",
"parking": "We're glad our convenient valet parking service made your arrival experience hassle-free.",
"bathroom": "Our housekeeping team takes special pride in maintaining our marble bathrooms with premium amenities."
}
improvement_actions = {
"AC": "completed a comprehensive inspection and maintenance of all air conditioning units",
"housekeeping": "implemented additional training for our housekeeping team and revised cleaning schedules",
"bathroom": "conducted deep cleaning of all bathrooms and replenished premium toiletries",
"parking": "introduced new digital key management with our valet service to reduce wait times",
"dining": "reviewed all menu pricing and quality standards with our culinary leadership team",
"reception": "provided enhanced customer service training focused on cultural sensitivity",
"elevator": "performed full servicing of all elevators and adjusted peak-time scheduling",
"room amenities": "begun upgrading in-room amenities including new coffee machines and smart TVs",
"Wi-Fi": "upgraded our network infrastructure to provide faster and more reliable internet",
"noise": "initiated soundproofing improvements in corridors and between rooms",
"pricing": "started a comprehensive review of our pricing structure and value proposition",
"room service": "revised our in-room dining operations to improve delivery times",
"view": "scheduled window cleaning and tree trimming to maintain optimal views",
"fitness": "upgraded gym equipment based on guest feedback about variety"
}
# ===== MODEL LOADING =====
@st.cache_resource
def load_sentiment_model():
model = AutoModelForSequenceClassification.from_pretrained("smtsead/fine_tuned_bertweet_hotel")
tokenizer = AutoTokenizer.from_pretrained('finiteautomata/bertweet-base-sentiment-analysis')
return model, tokenizer
@st.cache_resource
def load_aspect_classifier():
return pipeline("zero-shot-classification", model="MoritzLaurer/deberta-v3-base-zeroshot-v1.1-all-33")
# ===== CORE FUNCTIONS =====
def analyze_sentiment(text, model, tokenizer):
inputs = tokenizer(text, padding=True, truncation=True, max_length=512, return_tensors='pt')
with torch.no_grad():
outputs = model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
predicted_label = torch.argmax(probs).item()
confidence = torch.max(probs).item()
return {
'label': predicted_label,
'confidence': f"{confidence:.0%}",
'sentiment': 'POSITIVE' if predicted_label else 'NEGATIVE'
}
def detect_aspects(text, aspect_classifier):
relevant_aspects = []
text_lower = text.lower()
for aspect, keywords in aspect_map.items():
if any(re.search(rf'\b{kw}\b', text_lower) for kw in keywords):
relevant_aspects.append(aspect)
if relevant_aspects:
result = aspect_classifier(
text,
candidate_labels=relevant_aspects,
multi_label=True,
hypothesis_template="This review discusses the hotel's {}."
)
return [(aspect, f"{score:.0%}") for aspect, score in
zip(result['labels'], result['scores']) if score > 0.6]
return []
def generate_response(sentiment, aspects, original_text):
# Personalization
guest_name = ""
name_match = re.search(r"(Mr\.|Ms\.|Mrs\.)\s(\w+)", original_text, re.IGNORECASE)
if name_match:
guest_name = f" {name_match.group(2)}"
# Staff name extraction
staff_name = ""
staff_match = re.search(r"(receptionist|manager|concierge|chef)\s(\w+)", original_text, re.IGNORECASE)
if staff_match:
staff_name = staff_match.group(2)
if sentiment['label'] == 1:
response = f"""Dear{guest_name if guest_name else ' Valued Guest'},
Thank you for choosing The Kimberley Hotel Hong Kong and for sharing your wonderful feedback!"""
# Add relevant aspect responses
added_aspects = set()
for aspect, _ in aspects:
if aspect in aspect_responses:
response_text = aspect_responses[aspect]
if "{staff_name}" in response_text and staff_name:
response_text = response_text.format(staff_name=staff_name)
response += "\n\n" + response_text
added_aspects.add(aspect)
if len(added_aspects) >= 3: # Limit to 3 main points
break
# Special offers
if "room" in added_aspects or "dining" in added_aspects:
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."
response += "\n\nWe look forward to welcoming you back to your home in Hong Kong!\n\nWarm regards,"
else:
response = f"""Dear{guest_name if guest_name else ' Guest'},
Thank you for your valuable feedback - we sincerely apologize that your experience didn't meet our usual high standards."""
# Add improvement actions
added_improvements = set()
for aspect, _ in aspects:
if aspect in improvement_actions:
response += f"\n\nRegarding your comments about the {aspect}, we've {improvement_actions[aspect]}."
added_improvements.add(aspect)
if len(added_improvements) >= 2: # Limit to 2 main improvements
break
# Recovery offer
recovery_offer = "\n\nTo make amends, we'd like to offer you:"
if "room" in added_improvements:
recovery_offer += "\n- One night complimentary room upgrade"
if "dining" in added_improvements:
recovery_offer += "\n- HKD 300 dining credit at our restaurants"
if not ("room" in added_improvements or "dining" in added_improvements):
recovery_offer += "\n- 15% discount on your next stay"
response += recovery_offer
response += "\n\nPlease contact our Guest Relations Manager Ms. Chan directly at [email protected] to arrange this."
response += "\n\nWe hope for another opportunity to provide you with the exceptional experience we're known for.\n\nSincerely,"
return response + "\nMichael Wong\nGuest Experience Manager\nThe Kimberley Hotel Hong Kong\n+852 1234 5678"
# ===== STREAMLIT UI =====
def main():
# Page Config
st.set_page_config(
page_title="Kimberley Review Assistant",
page_icon="🏨",
layout="centered"
)
# Custom CSS
st.markdown("""
<style>
.header {
color: #003366;
font-size: 28px;
font-weight: bold;
margin-bottom: 10px;
}
.subheader {
color: #666666;
font-size: 16px;
margin-bottom: 30px;
}
.badge {
background-color: #e6f2ff;
color: #003366;
padding: 3px 10px;
border-radius: 15px;
font-size: 14px;
display: inline-block;
margin: 0 5px 5px 0;
}
.char-counter {
font-size: 12px;
color: #666;
text-align: right;
margin-top: -15px;
margin-bottom: 15px;
}
.char-counter.warning {
color: #ff6b6b;
}
.result-box {
border-left: 4px solid #003366;
padding: 15px;
background-color: #f9f9f9;
margin: 20px 0;
border-radius: 0 8px 8px 0;
white-space: pre-wrap;
}
.aspect-badge {
background-color: #e6f2ff;
color: #003366;
padding: 2px 8px;
border-radius: 4px;
font-size: 14px;
display: inline-block;
margin: 2px;
}
</style>
""", unsafe_allow_html=True)
# Header
st.markdown('<div class="header">The Kimberley Hotel Hong Kong</div>', unsafe_allow_html=True)
st.markdown('<div class="subheader">Guest Review Analysis System</div>', unsafe_allow_html=True)
# Supported Languages
st.markdown("**Supported Review Languages:**")
lang_cols = st.columns(5)
for i, (code, name) in enumerate(SUPPORTED_LANGUAGES.items()):
lang_cols[i].markdown(f'<div class="badge">{name}</div>', unsafe_allow_html=True)
# Review Input with Character Counter
review = st.text_area("**Paste Guest Review:**",
height=250,
max_chars=MAX_CHARS,
placeholder=f"Enter review in any supported language (max {MAX_CHARS} characters)...",
key="review_input")
char_count = len(st.session_state.review_input) if 'review_input' in st.session_state else 0
char_class = "warning" if char_count > MAX_CHARS else ""
st.markdown(f'<div class="char-counter {char_class}">{char_count}/{MAX_CHARS} characters</div>',
unsafe_allow_html=True)
if st.button("Analyze & Generate Response", type="primary"):
if not review.strip():
st.error("Please enter a review")
return
if char_count > MAX_CHARS:
st.warning(f"Review truncated to {MAX_CHARS} characters for analysis")
review = review[:MAX_CHARS]
with st.spinner("Analyzing feedback..."):
# Load models
sentiment_model, tokenizer = load_sentiment_model()
aspect_classifier = load_aspect_classifier()
# Process review
sentiment = analyze_sentiment(review, sentiment_model, tokenizer)
aspects = detect_aspects(review, aspect_classifier)
response = generate_response(sentiment, aspects, review)
# Display results
st.divider()
# Sentiment and Aspects
col1, col2 = st.columns(2)
with col1:
st.markdown("### Sentiment Analysis")
sentiment_icon = "✅" if sentiment['label'] == 1 else "⚠️"
st.markdown(f"{sentiment_icon} **{sentiment['sentiment']}**")
st.caption(f"Confidence level: {sentiment['confidence']}")
with col2:
st.markdown("### Key Aspects Detected")
if aspects:
for aspect, score in sorted(aspects, key=lambda x: float(x[1][:-1]), reverse=True):
st.markdown(f'<div class="aspect-badge">{aspect} ({score})</div>', unsafe_allow_html=True)
else:
st.markdown("_No specific aspects detected_")
# Generated Response
st.divider()
st.markdown("### Draft Response")
st.markdown(f'<div class="result-box">{response}</div>', unsafe_allow_html=True)
# Copy button
if st.button("Copy Response to Clipboard"):
st.session_state.copied = True
st.rerun()
if st.session_state.get("copied", False):
st.success("Response copied to clipboard!")
st.session_state.copied = False
if __name__ == "__main__":
main()