Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import pandas as pd | |
| import sqlite3 | |
| import plotly.express as px | |
| from wordcloud import WordCloud | |
| import matplotlib.pyplot as plt | |
| from analyzer import analyze_sentiment, calculate_priority_score, classify_theme, get_lemmas | |
| # Конфигурация страницы | |
| st.set_page_config(page_title="Insta Analytics Search", layout="wide", initial_sidebar_state="collapsed") | |
| # Стили | |
| st.markdown(""" | |
| <style> | |
| [data-testid="collapsedControl"] { display: none; } | |
| section[data-testid="stSidebar"] { display: none; } | |
| .stTextInput input { border: 2px solid #E83E8C !important; border-radius: 30px !important; padding: 15px 25px !important; font-size: 18px !important; } | |
| .stTextInput input:focus { border: 2px solid #E83E8C !important; box-shadow: 0px 0px 10px rgba(232, 62, 140, 0.3) !important; } | |
| .stButton>button, .stButton>button p { background-color: #E83E8C !important; color: #FFFFFF !important; border-radius: 30px !important; font-weight: bold !important; border: none !important; } | |
| .metric-card { background-color: rgba(255, 255, 255, 0.05); padding: 20px; border-radius: 15px; border-left: 5px solid #E83E8C; color: white; } | |
| .metric-card span { font-size: 14px; opacity: 0.7; text-transform: uppercase;} | |
| .metric-card b { color: #E83E8C !important; font-size: 28px; display: block; margin-top: 5px;} | |
| div[role="radiogroup"] { justify-content: center; margin-top: 10px; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| def load_data(): | |
| try: | |
| conn = sqlite3.connect('instagram_data.db') | |
| c = pd.read_sql("SELECT * FROM comments", conn) | |
| conn.close() | |
| c['comment_date'] = pd.to_datetime(c['comment_date'], errors='coerce') | |
| return c | |
| except: | |
| return pd.DataFrame() | |
| comments_all = load_data() | |
| st.markdown("<h1 style='text-align: center; font-size: 48px; margin-bottom: 0;'>Insight Search</h1>", unsafe_allow_html=True) | |
| if 'search_results' not in st.session_state: | |
| st.session_state['search_results'] = pd.DataFrame() | |
| col_spacer1, col_search, col_spacer2 = st.columns([1, 4, 1]) | |
| with col_search: | |
| search_query = st.text_input("Поиск", placeholder="Введите URL, слова, никнейм или текст...", label_visibility="collapsed") | |
| mode = st.radio("Режим", ["🔑 По словам", "🔗 По URL поста", "👤 По автору (Нику)", "✍️ Один текст"], label_visibility="collapsed", horizontal=True) | |
| analyze_clicked = st.button("Анализировать", use_container_width=True) | |
| st.divider() | |
| if analyze_clicked: | |
| if not search_query.strip(): | |
| st.warning("Пожалуйста, введите запрос!") | |
| else: | |
| term = search_query.strip().lower() | |
| res = pd.DataFrame() | |
| if mode == "🔑 По словам": | |
| keys = [k.strip().lower() for k in search_query.split(',')] | |
| res = comments_all[comments_all['comment_text'].apply(lambda x: any(k in str(x).lower() for k in keys))].copy() | |
| elif mode == "🔗 По URL поста": | |
| res = comments_all[comments_all['post_url'].astype(str).str.contains(term, na=False, case=False)].copy() | |
| elif mode == "👤 По автору (Нику)": | |
| res = comments_all[comments_all['comment_author'].astype(str).str.lower().str.contains(term, na=False)].copy() | |
| elif mode == "✍️ Один текст": | |
| res = pd.DataFrame([{'comment_text': search_query, 'comment_author': 'Вы', 'comment_date': pd.Timestamp.now()}]) | |
| if res.empty: | |
| st.error("По вашему запросу ничего не найдено.") | |
| st.session_state['search_results'] = pd.DataFrame() | |
| else: | |
| with st.spinner('ИИ анализирует данные...'): | |
| res['sentiment'] = analyze_sentiment(res['comment_text'].tolist()) | |
| res['theme'] = res['comment_text'].apply(classify_theme) | |
| res['priority'] = res.apply(calculate_priority_score, axis=1) | |
| st.session_state['search_results'] = res | |
| # ОТОБРАЖЕНИЕ | |
| if not st.session_state['search_results'].empty: | |
| df_result = st.session_state['search_results'] | |
| st.markdown("<h3 style='text-align:center;'>Фильтр комментариев:</h3>", unsafe_allow_html=True) | |
| sentiment_filter = st.radio("Тональность", ["Все", "🟢 Позитив", "🔴 Негатив", "⚪ Нейтрально"], horizontal=True, label_visibility="collapsed") | |
| df_filtered = df_result.copy() | |
| if sentiment_filter == "🟢 Позитив": | |
| df_filtered = df_filtered[df_filtered['sentiment'] == 'Позитив'] | |
| elif sentiment_filter == "🔴 Негатив": | |
| df_filtered = df_filtered[df_filtered['sentiment'] == 'Негатив'] | |
| elif sentiment_filter == "⚪ Нейтрально": | |
| df_filtered = df_filtered[df_filtered['sentiment'] == 'Нейтрально'] | |
| st.markdown(f"## Результаты ({len(df_filtered)} шт.)") | |
| # Метрики | |
| c1, c2, c3, c4 = st.columns(4) | |
| with c1: st.markdown(f'<div class="metric-card"><span>Всего</span><b>{len(df_filtered)} шт.</b></div>', unsafe_allow_html=True) | |
| with c2: | |
| val = df_filtered["sentiment"].mode()[0] if not df_filtered.empty else "—" | |
| st.markdown(f'<div class="metric-card"><span>Настрой</span><b>{val}</b></div>', unsafe_allow_html=True) | |
| with c3: | |
| val = df_filtered["theme"].mode()[0] if not df_filtered.empty else "—" | |
| st.markdown(f'<div class="metric-card"><span>Тема</span><b>{val}</b></div>', unsafe_allow_html=True) | |
| with c4: | |
| val = df_filtered["priority"].mean() if not df_filtered.empty else 0 | |
| st.markdown(f'<div class="metric-card"><span>Приоритет</span><b>{val:.1f}</b></div>', unsafe_allow_html=True) | |
| # Графики | |
| g1, g2 = st.columns(2) | |
| with g1: | |
| fig = px.pie(df_filtered, names='sentiment', color='sentiment', hole=0.3, | |
| color_discrete_map={'Позитив':'#82ca9d', 'Негатив':'#E83E8C', 'Нейтрально':'#8884d8'}) | |
| fig.update_layout(paper_bgcolor="rgba(0,0,0,0)", font_color="white") | |
| st.plotly_chart(fig, use_container_width=True) | |
| with g2: | |
| if len(df_filtered) > 0: | |
| txt = " ".join(df_filtered['comment_text'].astype(str)) | |
| wc = WordCloud(width=800, height=400, background_color=None, mode="RGBA").generate(txt) | |
| fig_wc, ax = plt.subplots(); ax.imshow(wc); ax.axis("off") | |
| fig_wc.patch.set_alpha(0) | |
| st.pyplot(fig_wc) | |
| # Таблица | |
| st.markdown("### 📋 Детализация комментариев") | |
| df_display = df_filtered.copy() | |
| df_display['comment_date'] = pd.to_datetime(df_display['comment_date']).dt.strftime('%d.%m.%Y %H:%M') | |
| df_display['Профиль'] = df_display['comment_author'].apply(lambda x: f"https://www.instagram.com/{x}/") | |
| df_display = df_display[['priority', 'sentiment', 'theme', 'comment_text', 'comment_author', 'Профиль', 'comment_date']] | |
| df_display.columns = ['Приоритет', 'Тональность', 'Тема', 'Текст', 'Автор', 'Instagram', 'Дата'] | |
| st.dataframe(df_display.sort_values(by='Приоритет', ascending=False), use_container_width=True, hide_index=True, | |
| column_config={"Instagram": st.column_config.LinkColumn("Instagram")}) | |
| st.download_button("📥 Скачать отчет (CSV)", df_display.to_csv(index=False).encode('utf-8-sig'), "report.csv", "text/csv") | |
| # ИНСАЙТЫ (ИСПРАВЛЕНО) | |
| st.markdown("---") | |
| st.markdown("### 💡 Инсайты разведки") | |
| # Проверяем негатив во ВСЕЙ найденной по запросу выборке (df_result) | |
| neg_data = df_result[df_result['sentiment'] == 'Негатив'] | |
| if not neg_data.empty: | |
| count_bad = len(neg_data) | |
| bad_topic = neg_data['theme'].mode()[0] | |
| st.error(f"⚠️ **Внимание:** В результатах поиска обнаружено **{count_bad}** негативных сообщений! Основная проблемная тема: **{bad_topic}**. Требуется анализ рисков.") | |
| else: | |
| st.success("✅ **Положительный инсайт:** В данной выборке не обнаружено негативных проявлений. Аудитория лояльна.") |