Spaces:
Paused
Paused
| 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") | |