""" 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"""

{emoji} Sentiment Health: {status}

Negative Sentiment: {negative_pct:.1f}%

""", unsafe_allow_html=True ) @staticmethod def display_pagination_controls(total_items, items_per_page, current_page): """ Display pagination controls Args: total_items: Total number of items items_per_page: Number of items per page current_page: Current page number Returns: int: New current page """ total_pages = (total_items - 1) // items_per_page + 1 col1, col2, col3 = st.columns([1, 2, 1]) with col1: if st.button("âŦ…ī¸ Previous", disabled=(current_page <= 1)): current_page -= 1 with col2: st.markdown(f"
Page {current_page} of {total_pages}
", unsafe_allow_html=True) with col3: if st.button("Next âžĄī¸", disabled=(current_page >= total_pages)): current_page += 1 return current_page