""" Content display components for sentiment visualization Creates formatted cards and displays for content and comments """ import streamlit as st import pandas as pd from datetime import datetime class ContentCards: """ Creates content display components """ @staticmethod def display_content_card(content_row, rank=None): """ Display a formatted content card Args: content_row: Series containing content information rank: Optional rank number to display """ with st.container(): # Create columns for layout col1, col2 = st.columns([3, 1]) with col1: # Title with rank if rank: st.markdown(f"### đĸ #{rank} - Content") else: st.markdown("### đ Content") # Content description description = content_row.get('content_description', 'No description available') if pd.notna(description) and description: st.markdown(f"**Description:** {description[:200]}..." if len(str(description)) > 200 else f"**Description:** {description}") else: st.markdown("**Description:** *No description available*") # Permalink if 'permalink_url' in content_row and pd.notna(content_row['permalink_url']): st.markdown(f"đ [View Content]({content_row['permalink_url']})") with col2: # Display thumbnail if available (Musora content) if 'thumbnail_url' in content_row and pd.notna(content_row['thumbnail_url']): try: st.image(content_row['thumbnail_url'], use_container_width=True) except Exception as e: # If image fails to load, show a placeholder st.markdown("*đŧī¸ Thumbnail unavailable*") # Statistics st.metric("Total Comments", int(content_row.get('total_comments', 0))) if 'negative_percentage' in content_row: neg_pct = content_row['negative_percentage'] st.metric( "Negative %", f"{neg_pct:.1f}%", delta=None, delta_color="inverse" ) if 'reply_required_count' in content_row: st.metric("Replies Needed", int(content_row['reply_required_count'])) # Additional details in expander with st.expander("đ View Detailed Statistics"): detail_col1, detail_col2, detail_col3 = st.columns(3) with detail_col1: st.write("**Content ID:**", content_row.get('content_sk', 'N/A')) if 'dominant_sentiment' in content_row: st.write("**Dominant Sentiment:**", content_row['dominant_sentiment'].title()) with detail_col2: if 'negative_count' in content_row: st.write("**Negative Count:**", int(content_row['negative_count'])) with detail_col3: if 'total_comments' in content_row: positive_count = int(content_row['total_comments']) - int(content_row.get('negative_count', 0)) st.write("**Positive/Neutral:**", positive_count) st.markdown("---") @staticmethod def display_comment_card(comment_row, show_original=False): """ Display a formatted comment card Args: comment_row: Series containing comment information show_original: Whether to show original text for translated comments """ with st.container(): # Header with metadata col1, col2, col3 = st.columns([2, 1, 1]) with col1: author = comment_row.get('author_name', 'Unknown') st.markdown(f"**đ¤ {author}**") with col2: if 'comment_timestamp' in comment_row and pd.notna(comment_row['comment_timestamp']): timestamp = pd.to_datetime(comment_row['comment_timestamp']) st.markdown(f"*đ {timestamp.strftime('%Y-%m-%d %H:%M')}*") with col3: platform = comment_row.get('platform', 'unknown') st.markdown(f"*đ {platform.title()}*") # Comment text display_text = comment_row.get('display_text', comment_row.get('original_text', 'No text available')) st.markdown(f"đŦ {display_text}") # Sentiment and intent badges badge_col1, badge_col2, badge_col3 = st.columns([2, 2, 1]) with badge_col1: sentiment = comment_row.get('sentiment_polarity', 'unknown') sentiment_emoji = { 'very_positive': 'đ', 'positive': 'đ', 'neutral': 'đ', 'negative': 'đ', 'very_negative': 'đ ' }.get(sentiment, 'â') st.markdown(f"**Sentiment:** {sentiment_emoji} {sentiment.replace('_', ' ').title()}") with badge_col2: intent = comment_row.get('intent', 'unknown') st.markdown(f"**Intent:** {intent}") with badge_col3: if comment_row.get('requires_reply', False): st.markdown("**â ī¸ Reply Required**") # Show original text if translated if show_original and comment_row.get('is_english') == False: with st.expander("đ View Original Text"): original_text = comment_row.get('original_text', 'Not available') detected_lang = comment_row.get('detected_language', 'Unknown') st.markdown(f"**Language:** {detected_lang}") st.markdown(f"**Original:** {original_text}") # Additional details in expander with st.expander("âšī¸ More Details"): detail_col1, detail_col2 = st.columns(2) with detail_col1: st.write("**Comment ID:**", comment_row.get('comment_id', 'N/A')) st.write("**Channel:**", comment_row.get('channel_name', 'N/A')) st.write("**Confidence:**", comment_row.get('sentiment_confidence', 'N/A')) with detail_col2: if 'content_description' in comment_row and pd.notna(comment_row['content_description']): content_desc = comment_row['content_description'] st.write("**Content:**", content_desc[:50] + "..." if len(str(content_desc)) > 50 else content_desc) if 'permalink_url' in comment_row and pd.notna(comment_row['permalink_url']): st.markdown(f"[View Content]({comment_row['permalink_url']})") st.markdown("---") @staticmethod def display_metric_cards(metrics_dict): """ Display a row of metric cards Args: metrics_dict: Dictionary of metrics {label: value} """ cols = st.columns(len(metrics_dict)) for idx, (label, value) in enumerate(metrics_dict.items()): with cols[idx]: if isinstance(value, dict) and 'value' in value: # Advanced metric with delta st.metric( label, value['value'], delta=value.get('delta'), delta_color=value.get('delta_color', 'normal') ) else: # Simple metric st.metric(label, value) @staticmethod def display_summary_stats(df): """ Display summary statistics in a formatted layout Args: df: Sentiment dataframe """ st.markdown("### đ Summary Statistics") col1, col2, col3, col4 = st.columns(4) with col1: st.metric("Total Comments", len(df)) with col2: unique_contents = df['content_sk'].nunique() if 'content_sk' in df.columns else 0 st.metric("Unique Contents", unique_contents) with col3: reply_required = df['requires_reply'].sum() if 'requires_reply' in df.columns else 0 st.metric("Replies Needed", int(reply_required)) with col4: negative_sentiments = ['negative', 'very_negative'] negative_count = df['sentiment_polarity'].isin(negative_sentiments).sum() negative_pct = (negative_count / len(df) * 100) if len(df) > 0 else 0 st.metric("Negative %", f"{negative_pct:.1f}%") @staticmethod def display_filter_summary(applied_filters): """ Display summary of applied filters Args: applied_filters: Dictionary of applied filters """ if not any(applied_filters.values()): return st.markdown("### đ Applied Filters") filter_text = [] for filter_name, filter_value in applied_filters.items(): if filter_value and len(filter_value) > 0: filter_text.append(f"**{filter_name.title()}:** {', '.join(map(str, filter_value))}") if filter_text: st.info(" | ".join(filter_text)) @staticmethod def display_health_indicator(negative_pct): """ Display sentiment health indicator Args: negative_pct: Percentage of negative sentiments """ if negative_pct < 10: status = "Excellent" color = "green" emoji = "â " elif negative_pct < 20: status = "Good" color = "lightgreen" emoji = "đ" elif negative_pct < 30: status = "Fair" color = "orange" emoji = "â ī¸" elif negative_pct < 50: status = "Poor" color = "darkorange" emoji = "âĄ" else: status = "Critical" color = "red" emoji = "đ¨" st.markdown( f"""
Negative Sentiment: {negative_pct:.1f}%