import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import requests import json import io # Konfiguracja st.set_page_config(page_title="🤖 DataSense Agent", layout="wide") st.title("🤖 DataSense Agent") st.write("Agent analityczny") # Session state if 'df' not in st.session_state: st.session_state.df = None if 'file_info' not in st.session_state: st.session_state.file_info = None if 'analysis_history' not in st.session_state: st.session_state.analysis_history = [] if 'questions_count' not in st.session_state: st.session_state.questions_count = 0 if 'quick_question' not in st.session_state: st.session_state.quick_question = "" # API DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions" def smart_csv_detection(file_content): """Inteligentne wykrywanie formatu CSV""" separators = [';', ',', '\t', '|'] encodings = ['utf-8', 'cp1250', 'latin1', 'iso-8859-1'] st.info("🔍 Analizuję format pliku...") try: sample_text = None for encoding in encodings: try: file_content.seek(0) sample_text = file_content.read(2000).decode(encoding, errors='ignore') break except: continue if not sample_text: return None, "Nie można określić kodowania pliku" file_content.seek(0) except Exception as e: return None, f"Błąd odczytu pliku: {e}" results = [] for separator in separators: for encoding in encodings: try: file_content.seek(0) test_df = pd.read_csv( file_content, sep=separator, encoding=encoding, nrows=5, quotechar='"', skipinitialspace=True, on_bad_lines='skip' ) num_cols = len(test_df.columns) quality_score = 0 if num_cols > 1: quality_score += num_cols * 10 problematic_names = sum(1 for col in test_df.columns if ';' in str(col) or ',' in str(col)) quality_score -= problematic_names * 50 if num_cols > 1 and len(test_df) > 0: first_row = test_df.iloc[0] valid_cells = sum(1 for val in first_row if pd.notna(val) and str(val).strip()) quality_score += valid_cells * 5 results.append({ 'separator': separator, 'encoding': encoding, 'columns': num_cols, 'quality': quality_score, 'sample_df': test_df, 'column_names': list(test_df.columns) }) except Exception as e: continue if not results: return None, "Nie udało się wykryć formatu pliku" best_result = max(results, key=lambda x: x['quality']) st.success(f"✅ Wykryty format:") st.write(f"**Separator:** '{best_result['separator']}'") st.write(f"**Kodowanie:** {best_result['encoding']}") st.write(f"**Kolumny:** {best_result['columns']}") st.write(f"**Jakość:** {best_result['quality']} punktów") if len(results) > 1: with st.expander("🔧 Inne możliwe formaty", expanded=False): for i, result in enumerate(sorted(results, key=lambda x: x['quality'], reverse=True)[:3]): if result != best_result: st.write(f"Opcja {i+1}: separator='{result['separator']}', kodowanie={result['encoding']}, kolumny={result['columns']}") return best_result, None def load_csv_with_config(file_content, config): """Wczytaj pełny CSV z wykrytą konfiguracją""" try: file_content.seek(0) df = pd.read_csv( file_content, sep=config['separator'], encoding=config['encoding'], quotechar='"', skipinitialspace=True, on_bad_lines='skip' ) return df, None except Exception as e: return None, f"Błąd wczytywania: {e}" def safe_display_data(df, max_rows=5): """Bezpieczne wyświetlanie danych""" try: if len(df) <= max_rows: st.dataframe(df, use_container_width=True) else: st.dataframe(df.head(max_rows), use_container_width=True) st.info(f"Pokazano {max_rows} z {len(df)} wierszy") except: st.warning("⚠️ Problem z wyświetlaniem tabeli. Pokazuję jako tekst:") for i in range(min(max_rows, len(df))): row_data = [] for col in df.columns[:6]: try: val = df.iloc[i][col] row_data.append(f"{col}: {val}") except: row_data.append(f"{col}: [błąd]") st.text(f"Wiersz {i+1}: {' | '.join(row_data)}") def generate_interpretation(question, result, code, api_key): """Generuje interpretację wyników przez AI""" if not api_key: return "⚠️ Brak klucza API - interpretacja niedostępna" prompt = f""" Jako ekspert analizy danych, przygotuj zwięzłą interpretację wyników w języku polskim: Pytanie użytkownika: {question} Wynik analizy: {str(result)[:800] if result is not None else "Brak wyniku"} Użyty kod: {code} Przygotuj interpretację w formacie: ## 📊 Podsumowanie [1-2 zdania o głównym wyniku] ## 🔍 Kluczowe wnioski - [Wniosek 1] - [Wniosek 2] - [Wniosek 3 jeśli jest] ## 💡 Sugestie dalszych analiz [Konkretne pomysły na kolejne pytania] Bądź konkretny i praktyczny. Używaj prostego języka. """ try: headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} payload = { "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}], "temperature": 0.4, "max_tokens": 600 } response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30) if response.status_code == 200: return response.json()['choices'][0]['message']['content'] else: return f"❌ Błąd API interpretacji: {response.status_code}" except Exception as e: return f"❌ Błąd interpretacji: {str(e)}" def simple_query_ai(prompt, api_key): """Zapytanie do AI""" if not api_key: return None headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} payload = { "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}], "temperature": 0.3, "max_tokens": 800 } try: response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30) if response.status_code == 200: return response.json()['choices'][0]['message']['content'] else: st.error(f"API Error: {response.status_code}") return None except Exception as e: st.error(f"Connection error: {e}") return None def extract_python_code(text): """Wyciągnij kod Python""" if "```python" in text: start = text.find("```python") + 9 end = text.find("```", start) return text[start:end].strip() elif "```" in text: start = text.find("```") + 3 end = text.find("```", start) return text[start:end].strip() else: return text.strip() def safe_execute_code(code, df): """Wykonaj kod bezpiecznie z szczegółowymi logami""" # Logi wykonania st.subheader("🔍 Logi wykonania:") log_container = st.container() with log_container: st.write("**Krok 1:** Sprawdzanie bezpieczeństwa kodu...") # Usuń problematyczne linijki problematic_patterns = ['read_csv', 'pd.read', 'pandas.read', 'to_csv', 'read_excel', 'open(', 'file('] lines = code.split('\n') clean_lines = [] removed_lines = [] for line in lines: if not any(pattern in line for pattern in problematic_patterns): clean_lines.append(line) else: removed_lines.append(line.strip()) if removed_lines: st.warning(f"🛡️ Usunięto {len(removed_lines)} potencjalnie niebezpiecznych linijek:") for line in removed_lines: st.code(line, language='python') else: st.success("✅ Kod jest bezpieczny") cleaned_code = '\n'.join(clean_lines) st.write("**Krok 2:** Przygotowanie środowiska wykonania...") # Bezpieczne środowisko z matplotlib safe_globals = { 'pd': pd, 'np': np, 'plt': plt, 'df': df, 'len': len, 'sum': sum, 'max': max, 'min': min, 'abs': abs, 'round': round, 'str': str, 'int': int, 'float': float, 'list': list, 'dict': dict, 'sorted': sorted, 'enumerate': enumerate, 'range': range } st.success(f"✅ Dostępne moduły: pd, np, plt, df") st.write("**Krok 3:** Wykonywanie kodu...") safe_locals = {'result': None, 'fig': None} try: # Wykonaj cały kod naraz (bezpieczniej dla wielolinijkowych struktur) progress_bar = st.progress(0) progress_bar.progress(0.3) # Spróbuj wykonać cały kod exec(cleaned_code, safe_globals, safe_locals) progress_bar.progress(1.0) st.success("✅ Kod wykonany pomyślnie") # Sprawdź co zostało utworzone result = safe_locals.get('result') fig = safe_locals.get('fig') st.write("**Krok 4:** Sprawdzenie wyników...") if result is not None: result_type = type(result).__name__ if hasattr(result, 'shape'): st.info(f"📊 Wynik: {result_type} o rozmiarze {result.shape}") else: st.info(f"📊 Wynik: {result_type}") else: st.warning("⚠️ Brak wyniku w zmiennej 'result'") if fig is not None: st.success("📈 Wykres został utworzony") # Sprawdź czy matplotlib ma aktywne figury if plt.get_fignums(): if fig is None: fig = plt.gcf() # Pobierz aktualną figurę st.info("📈 Wykrywam wykres matplotlib") return result, fig, None except Exception as e: st.error(f"❌ Błąd wykonania: {str(e)}") return None, None, str(e) # Sidebar with st.sidebar: st.header("⚙️ Konfiguracja") api_key = st.text_input("🔑 DeepSeek API Key", type="password") st.header("📁 Upload pliku") uploaded_file = st.file_uploader("Wybierz plik CSV", type=['csv', 'txt']) if uploaded_file: st.info("📋 Aplikacja automatycznie wykryje format pliku") # Opcja ręcznego wyboru manual_override = st.checkbox("🔧 Ręczne ustawienia (zaawansowane)") if manual_override: manual_sep = st.selectbox("Separator", [';', ',', '\t', '|']) manual_enc = st.selectbox("Kodowanie", ['utf-8', 'cp1250', 'latin1']) st.session_state.manual_config = { 'separator': manual_sep, 'encoding': manual_enc, 'columns': 0, 'quality': 0, 'sample_df': None, 'column_names': [] } # Historia i szybkie pytania if st.session_state.df is not None: st.header("📊 Szybkie pytania") # Podstawowe pytania if st.button("📏 Ile wierszy?"): st.session_state.quick_question = "Ile jest wierszy w danych?" if st.button("📋 Jakie kolumny?"): st.session_state.quick_question = "Pokaż nazwy wszystkich kolumn" if st.button("🔢 Statystyki"): st.session_state.quick_question = "Pokaż podstawowe statystyki numeryczne" # Szybkie wykresy st.subheader("📈 Szybkie wykresy") numeric_cols = st.session_state.df.select_dtypes(include=[np.number]).columns categorical_cols = st.session_state.df.select_dtypes(include=['object']).columns if len(numeric_cols) > 0: if st.button("📊 Histogram pierwszej kolumny"): first_numeric = numeric_cols[0] st.session_state.quick_question = f"Stwórz histogram dla kolumny {first_numeric}" if len(categorical_cols) > 0: if st.button("📈 Wykres kategorii"): first_categorical = categorical_cols[0] st.session_state.quick_question = f"Stwórz wykres słupkowy pokazujący ile jest każdej kategorii w kolumnie {first_categorical}" if len(numeric_cols) >= 2: if st.button("🔍 Scatter plot"): col1, col2 = numeric_cols[0], numeric_cols[1] st.session_state.quick_question = f"Stwórz scatter plot dla kolumn {col1} i {col2}" # Historia if st.session_state.analysis_history: st.header("📜 Historia") st.write(f"Wykonanych analiz: **{len(st.session_state.analysis_history)}**") # Ostatnie pytanie z info o wykresie if st.session_state.analysis_history: last = st.session_state.analysis_history[-1] st.write("**Ostatnie pytanie:**") st.write(f"_{last['question'][:50]}..._") if last.get('has_plot', False): st.write("📈 *Zawierało wykres*") # Statystyki historii total_plots = sum(1 for analysis in st.session_state.analysis_history if analysis.get('has_plot', False)) st.write(f"**Wykresy utworzone:** {total_plots}") # Najczęstsze słowa kluczowe all_questions = [analysis['question'].lower() for analysis in st.session_state.analysis_history] common_words = [] for question in all_questions: if 'balans' in question: common_words.append('balans') if 'wykres' in question or 'histogram' in question: common_words.append('wykresy') if 'średnia' in question: common_words.append('średnie') if 'top' in question: common_words.append('rankingi') if common_words: unique_words = list(set(common_words)) st.write(f"**Popularne tematy:** {', '.join(unique_words)}") # Main interface col1, col2 = st.columns([3, 1]) with col1: if uploaded_file and st.button("🚀 WCZYTAJ I ANALIZUJ PLIK", type="primary"): with st.spinner("🔍 Analizuję format pliku..."): # Sprawdź czy użytkownik wybrał ręczne ustawienia if hasattr(st.session_state, 'manual_config') and st.session_state.get('manual_override', False): config = st.session_state.manual_config st.info("🔧 Używam ręcznych ustawień") else: # Automatyczne wykrywanie config, error = smart_csv_detection(uploaded_file) if error: st.error(f"❌ {error}") st.stop() # Wczytaj pełny plik with st.spinner("📊 Wczytuję dane..."): df, error = load_csv_with_config(uploaded_file, config) if error: st.error(f"❌ {error}") st.stop() st.session_state.df = df st.session_state.file_info = config # Pokaż wyniki st.success(f"✅ SUKCES! Wczytano {df.shape[0]:,} wierszy × {df.shape[1]} kolumn") # Info o kolumnach st.write("📋 **Kolumny:**") col_info = [] for i, col in enumerate(df.columns): dtype = str(df[col].dtype) non_null = df[col].count() col_info.append(f"{i+1}. **{col}** ({dtype}) - {non_null:,}/{len(df):,} wartości") # Pokaż kolumny w kolumnach dla czytelności col_chunks = [col_info[i:i+3] for i in range(0, len(col_info), 3)] for chunk in col_chunks: cols = st.columns(len(chunk)) for i, info in enumerate(chunk): cols[i].write(info) # Podgląd danych st.write("📄 **Podgląd danych:**") safe_display_data(df) # Sekcja analizy if st.session_state.df is not None: st.header("🔍 Analiza danych") df = st.session_state.df # Pokaż historię analiz jeśli są if st.session_state.analysis_history: with st.expander(f"📜 Historia analiz ({len(st.session_state.analysis_history)})", expanded=False): for i, analysis in enumerate(reversed(st.session_state.analysis_history[-5:])): # Ostatnie 5 idx = len(st.session_state.analysis_history) - i st.write(f"**{idx}. {analysis['question']}**") # Ikona wykresu jeśli był if analysis.get('has_plot', False): st.write("📈 *Zawierał wykres*") # Krótki wynik result_preview = str(analysis['result'])[:100] if len(str(analysis['result'])) > 100: result_preview += "..." st.write(f"Wynik: {result_preview}") # Timestamp if 'timestamp' in analysis: st.caption(f"⏰ {analysis['timestamp'].strftime('%H:%M:%S')}") st.write("---") # Formularz do pytań with st.form("analysis_form", clear_on_submit=True): # Sprawdź czy jest szybkie pytanie default_question = "" if hasattr(st.session_state, 'quick_question') and st.session_state.quick_question: default_question = st.session_state.quick_question st.session_state.quick_question = "" question = st.text_area( "💬 Zadaj pytanie o dane:", height=100, placeholder="np. Histogram balansu, Top 10 zawodów, Rozkład wieku klientów", key=f"question_input_{st.session_state.questions_count}", value=default_question ) col_btn1, col_btn2 = st.columns(2) with col_btn1: submit_button = st.form_submit_button("🧠 ANALIZUJ", type="primary") with col_btn2: clear_history = st.form_submit_button("🗑️ Wyczyść historię") # Wyczyść historię jeśli kliknięto if clear_history: st.session_state.analysis_history = [] st.success("✅ Historia wyczyszczona") st.rerun() # Przetwórz pytanie if submit_button and api_key and question.strip(): st.session_state.questions_count += 1 # Przygotuj informacje o danych columns_info = f"Kolumny: {', '.join(df.columns)}" dtypes_info = f"Typy danych: {dict(df.dtypes)}" sample_data = [] for col in df.columns[:8]: # Pierwsze 8 kolumn try: if df[col].dtype in ['object', 'string']: unique_vals = df[col].dropna().unique()[:3] sample_data.append(f"{col}: {list(unique_vals)}") else: stats = f"min={df[col].min()}, max={df[col].max()}, średnia={df[col].mean():.2f}" sample_data.append(f"{col}: {stats}") except: sample_data.append(f"{col}: [błąd próbkowania]") sample_info = "Przykładowe dane: " + " | ".join(sample_data) # Prompt dla AI z zachętą do tworzenia wykresów prompt = f""" Odpowiedz na pytanie o dane używając kodu Python. WAŻNE: Jeśli pytanie może być lepiej zobrazowane wykresem, stwórz go! PYTANIE: {question} INFORMACJE O DANYCH: {columns_info} {dtypes_info} Rozmiar: {df.shape[0]} wierszy × {df.shape[1]} kolumn {sample_info} ZASADY: 1. DataFrame nazywa się 'df' i jest już załadowany 2. NIE używaj pd.read_csv(), pd.read_excel() ani podobnych 3. Wynik zapisz w zmiennej 'result' 4. Używaj pandas, numpy, matplotlib (plt) 5. Kod ma być prosty i skuteczny 6. WYKRES: Jeśli pytanie dotyczy rozkładów, trendów, porównań - ZAWSZE utwórz wykres w zmiennej 'fig' KIEDY TWORZYĆ WYKRESY: - Rozkłady wartości → histogram: plt.hist() - Porównania kategorii → wykres słupkowy: plt.bar() - Trendy w czasie → wykres liniowy: plt.plot() - Korelacje → scatter plot: plt.scatter() - Top N → wykres słupkowy - Statystyki grupowe → wykres słupkowy lub pudełkowy PRZYKŁADY KODÓW Z WYKRESAMI: # Histogram rozkładu result = df['balance'].describe() fig, ax = plt.subplots(figsize=(10, 6)) ax.hist(df['balance'], bins=30, alpha=0.7) ax.set_title('Rozkład balansu klientów') ax.set_xlabel('Balans') ax.set_ylabel('Liczba klientów') # Wykres słupkowy dla kategorii result = df['job'].value_counts().head(10) fig, ax = plt.subplots(figsize=(12, 6)) result.plot(kind='bar', ax=ax) ax.set_title('Top 10 zawodów') ax.set_xlabel('Zawód') ax.set_ylabel('Liczba osób') plt.xticks(rotation=45) # Porównanie grup result = df.groupby('job')['balance'].mean().head(10) fig, ax = plt.subplots(figsize=(12, 6)) result.plot(kind='bar', ax=ax) ax.set_title('Średni balans według zawodu') ax.set_ylabel('Średni balans') plt.xticks(rotation=45) PRZYKŁADY BEZ WYKRESÓW (tylko liczby): - Liczba wierszy: result = len(df) - Średnia: result = df['kolumna'].mean() - Filtrowanie: result = len(df[df['kolumna'] > 100]) Zwróć TYLKO kod Python (bez wyjaśnień): """ # Zapytaj AI with st.spinner("🤖 AI generuje kod..."): st.info("🔄 Wysyłam zapytanie do DeepSeek API...") ai_response = simple_query_ai(prompt, api_key) if ai_response: st.success("✅ Otrzymano odpowiedź od AI") code = extract_python_code(ai_response) st.subheader("🔧 Wygenerowany kod:") st.code(code, language='python') # Wykonaj z logowaniem with st.spinner("⚡ Wykonuję analizę..."): result, fig, error = safe_execute_code(code, df) # Wyniki if error: st.error(f"❌ Błąd końcowy: {error}") st.write("**Możliwe rozwiązania:**") st.write("- Sprawdź nazwy kolumn") st.write("- Upewnij się że kolumna zawiera dane liczbowe") st.write("- Spróbuj prostsze pytanie") else: st.subheader("📊 WYNIK:") if result is not None: if isinstance(result, (int, float)): st.metric("Wynik", f"{result:,}") elif isinstance(result, str): st.write(f"**{result}**") elif isinstance(result, pd.DataFrame): st.write("**Tabela wyników:**") safe_display_data(result, max_rows=20) elif isinstance(result, pd.Series): st.write("**Serie danych:**") safe_display_data(result.to_frame(), max_rows=20) else: st.write("**Wynik:**") st.text(str(result)[:1000]) # WYKRESY - ulepszone wyświetlanie if fig is not None: st.subheader("📈 Wykres:") try: plt.tight_layout() st.pyplot(fig, use_container_width=True) st.success("✅ Wykres wyświetlony pomyślnie") except Exception as plot_error: st.error(f"❌ Błąd wyświetlania wykresu: {plot_error}") finally: plt.close(fig) elif any(word in question.lower() for word in ['wykres', 'histogram', 'rozkład', 'porównaj', 'pokaż', 'wizualiz']): st.info("💡 Zapytanie sugeruje wykres, ale AI go nie utworzyło. Spróbuj poprosić bezpośrednio o wykres.") # INTERPRETACJA AI st.subheader("🧠 Interpretacja AI:") with st.spinner("🔮 AI interpretuje wyniki..."): interpretation = generate_interpretation(question, result, code, api_key) st.markdown(interpretation) # Zapisz do historii analysis_record = { 'question': question, 'code': code, 'result': result, 'interpretation': interpretation, 'timestamp': pd.Timestamp.now(), 'has_plot': fig is not None } st.session_state.analysis_history.append(analysis_record) # Zachęć do kolejnych pytań st.markdown("---") st.success("✅ Analiza zakończona! Możesz zadać kolejne pytanie powyżej.") # Sugestie kolejnych pytań na podstawie wyniku if isinstance(result, pd.DataFrame) and len(result) > 1: st.info("💡 Sugestia: Możesz zapytać o wykres dla tych wyników") elif isinstance(result, (int, float)) and 'balans' in question.lower(): st.info("💡 Sugestia: Spróbuj 'Pokaż histogram balansu' lub 'Rozkład balansu klientów'") elif 'top' in question.lower() or 'najwyższ' in question.lower(): st.info("💡 Sugestia: Możesz poprosić o wykres słupkowy dla tych wyników") else: st.error("❌ Nie udało się uzyskać odpowiedzi od AI") st.write("**Możliwe przyczyny:**") st.write("- Błąd klucza API") st.write("- Problem z połączeniem internetowym") st.write("- Przeciążenie serwera DeepSeek") elif submit_button and not api_key: st.warning("⚠️ Wprowadź klucz API DeepSeek w lewym panelu") elif submit_button and not question.strip(): st.warning("⚠️ Wprowadź pytanie do analizy") with col2: st.header("📊 Informacje") if st.session_state.df is not None: df = st.session_state.df # Podstawowe metryki st.metric("📊 Wiersze", f"{len(df):,}") st.metric("📋 Kolumny", len(df.columns)) # Informacje o pliku if st.session_state.file_info: st.subheader("📁 Format pliku") info = st.session_state.file_info st.write(f"**Separator:** `{info['separator']}`") st.write(f"**Kodowanie:** {info['encoding']}") # Typy danych st.subheader("🏷️ Typy kolumn") type_counts = df.dtypes.value_counts() for dtype, count in type_counts.items(): st.write(f"**{str(dtype)}:** {count}") # Braki danych missing_data = df.isnull().sum() missing_cols = missing_data[missing_data > 0] if len(missing_cols) > 0: st.subheader("⚠️ Braki danych") for col, missing in missing_cols.items(): pct = (missing / len(df)) * 100 st.write(f"**{col}:** {missing:,} ({pct:.1f}%)") else: st.success("✅ Brak braków danych") # Kolumny numeryczne - podstawowe statystyki numeric_cols = df.select_dtypes(include=[np.number]).columns if len(numeric_cols) > 0: st.subheader("🔢 Statystyki numeryczne") for col in numeric_cols[:5]: try: mean_val = df[col].mean() min_val = df[col].min() max_val = df[col].max() st.write(f"**{col}:**") st.write(f" Średnia: {mean_val:.2f}") st.write(f" Zakres: {min_val:.2f} - {max_val:.2f}") except: st.write(f"**{col}:** błąd obliczeń") else: st.info("👆 Wczytaj plik CSV aby zobaczyć statystyki") # Footer st.markdown("---") st.markdown("🔧 **Funkcje:**") col_f1, col_f2 = st.columns(2) with col_f1: st.markdown("• **Automatyczne wykrywanie** formatu CSV") st.markdown("• **Obsługa separatorów:** `;` `,` `tab` `|`") st.markdown("• **Różne kodowania:** UTF-8, CP1250, Latin1") st.markdown("• **Inteligentne wyświetlanie** (omija błędy)") st.markdown("• **Szczegółowe logi** wykonania kodu") with col_f2: st.markdown("• **Interpretacja AI** wyników analizy") st.markdown("• **Historia pytań** w sesji") st.markdown("• **Automatyczne wykresy** (histogram, bar, scatter)") st.markdown("• **Szybkie wykresy** jednym kliknięciem") st.markdown("• **Ciągłość analizy** - kolejne pytania") st.info("💡 **Tip:** Pytaj o 'histogram', 'wykres', 'rozkład', 'porównanie' - AI automatycznie utworzy odpowiedni wykres!") # Debug info if st.session_state.analysis_history: plots_count = sum(1 for analysis in st.session_state.analysis_history if analysis.get('has_plot', False)) st.caption(f"🎯 W tej sesji: {len(st.session_state.analysis_history)} analiz, {plots_count} wykresów")