Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 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 |
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 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
else:
|
| 88 |
-
response = """Dear Guest,
|
| 89 |
|
| 90 |
-
Thank you for your feedback - we sincerely apologize
|
| 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
|
| 102 |
-
response +=
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
response +=
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
-
return response + "\
|
| 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:
|
| 144 |
background-color: #f9f9f9;
|
| 145 |
-
margin:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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">
|
| 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:**",
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 186 |
-
|
|
|
|
|
|
|
| 187 |
|
| 188 |
with col2:
|
|
|
|
| 189 |
if aspects:
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
st.write(f"- {aspect} ({score} confidence)")
|
| 193 |
else:
|
| 194 |
-
st.markdown("
|
| 195 |
|
| 196 |
# Generated Response
|
| 197 |
st.divider()
|
| 198 |
-
st.markdown("
|
| 199 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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()
|