Marek4321 commited on
Commit
3cb46cb
·
verified ·
1 Parent(s): bfbb471

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +795 -0
app.py ADDED
@@ -0,0 +1,795 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import requests
6
+ import json
7
+ import io
8
+
9
+ # Konfiguracja
10
+ st.set_page_config(page_title="🤖 Agent Analityczny", layout="wide")
11
+
12
+ st.title("🤖 Agent Analityczny")
13
+ st.write("(Logi + wykresy)")
14
+
15
+ # Session state
16
+ if 'df' not in st.session_state:
17
+ st.session_state.df = None
18
+ if 'file_info' not in st.session_state:
19
+ st.session_state.file_info = None
20
+ if 'analysis_history' not in st.session_state:
21
+ st.session_state.analysis_history = []
22
+ if 'questions_count' not in st.session_state:
23
+ st.session_state.questions_count = 0
24
+ if 'quick_question' not in st.session_state:
25
+ st.session_state.quick_question = ""
26
+
27
+ # API
28
+ DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions"
29
+
30
+ def smart_csv_detection(file_content):
31
+ """Inteligentne wykrywanie formatu CSV"""
32
+ separators = [';', ',', '\t', '|']
33
+ encodings = ['utf-8', 'cp1250', 'latin1', 'iso-8859-1']
34
+
35
+ st.info("🔍 Analizuję format pliku...")
36
+
37
+ try:
38
+ sample_text = None
39
+ for encoding in encodings:
40
+ try:
41
+ file_content.seek(0)
42
+ sample_text = file_content.read(2000).decode(encoding, errors='ignore')
43
+ break
44
+ except:
45
+ continue
46
+
47
+ if not sample_text:
48
+ return None, "Nie można określić kodowania pliku"
49
+
50
+ file_content.seek(0)
51
+
52
+ except Exception as e:
53
+ return None, f"Błąd odczytu pliku: {e}"
54
+
55
+ results = []
56
+
57
+ for separator in separators:
58
+ for encoding in encodings:
59
+ try:
60
+ file_content.seek(0)
61
+
62
+ test_df = pd.read_csv(
63
+ file_content,
64
+ sep=separator,
65
+ encoding=encoding,
66
+ nrows=5,
67
+ quotechar='"',
68
+ skipinitialspace=True,
69
+ on_bad_lines='skip'
70
+ )
71
+
72
+ num_cols = len(test_df.columns)
73
+ quality_score = 0
74
+
75
+ if num_cols > 1:
76
+ quality_score += num_cols * 10
77
+
78
+ problematic_names = sum(1 for col in test_df.columns if ';' in str(col) or ',' in str(col))
79
+ quality_score -= problematic_names * 50
80
+
81
+ if num_cols > 1 and len(test_df) > 0:
82
+ first_row = test_df.iloc[0]
83
+ valid_cells = sum(1 for val in first_row if pd.notna(val) and str(val).strip())
84
+ quality_score += valid_cells * 5
85
+
86
+ results.append({
87
+ 'separator': separator,
88
+ 'encoding': encoding,
89
+ 'columns': num_cols,
90
+ 'quality': quality_score,
91
+ 'sample_df': test_df,
92
+ 'column_names': list(test_df.columns)
93
+ })
94
+
95
+ except Exception as e:
96
+ continue
97
+
98
+ if not results:
99
+ return None, "Nie udało się wykryć formatu pliku"
100
+
101
+ best_result = max(results, key=lambda x: x['quality'])
102
+
103
+ st.success(f"✅ Wykryty format:")
104
+ st.write(f"**Separator:** '{best_result['separator']}'")
105
+ st.write(f"**Kodowanie:** {best_result['encoding']}")
106
+ st.write(f"**Kolumny:** {best_result['columns']}")
107
+ st.write(f"**Jakość:** {best_result['quality']} punktów")
108
+
109
+ if len(results) > 1:
110
+ with st.expander("🔧 Inne możliwe formaty", expanded=False):
111
+ for i, result in enumerate(sorted(results, key=lambda x: x['quality'], reverse=True)[:3]):
112
+ if result != best_result:
113
+ st.write(f"Opcja {i+1}: separator='{result['separator']}', kodowanie={result['encoding']}, kolumny={result['columns']}")
114
+
115
+ return best_result, None
116
+
117
+ def load_csv_with_config(file_content, config):
118
+ """Wczytaj pełny CSV z wykrytą konfiguracją"""
119
+ try:
120
+ file_content.seek(0)
121
+
122
+ df = pd.read_csv(
123
+ file_content,
124
+ sep=config['separator'],
125
+ encoding=config['encoding'],
126
+ quotechar='"',
127
+ skipinitialspace=True,
128
+ on_bad_lines='skip'
129
+ )
130
+
131
+ return df, None
132
+
133
+ except Exception as e:
134
+ return None, f"Błąd wczytywania: {e}"
135
+
136
+ def safe_display_data(df, max_rows=5):
137
+ """Bezpieczne wyświetlanie danych"""
138
+ try:
139
+ if len(df) <= max_rows:
140
+ st.dataframe(df, use_container_width=True)
141
+ else:
142
+ st.dataframe(df.head(max_rows), use_container_width=True)
143
+ st.info(f"Pokazano {max_rows} z {len(df)} wierszy")
144
+ except:
145
+ st.warning("⚠️ Problem z wyświetlaniem tabeli. Pokazuję jako tekst:")
146
+ for i in range(min(max_rows, len(df))):
147
+ row_data = []
148
+ for col in df.columns[:6]:
149
+ try:
150
+ val = df.iloc[i][col]
151
+ row_data.append(f"{col}: {val}")
152
+ except:
153
+ row_data.append(f"{col}: [błąd]")
154
+ st.text(f"Wiersz {i+1}: {' | '.join(row_data)}")
155
+
156
+ def generate_interpretation(question, result, code, api_key):
157
+ """Generuje interpretację wyników przez AI"""
158
+ if not api_key:
159
+ return "⚠️ Brak klucza API - interpretacja niedostępna"
160
+
161
+ prompt = f"""
162
+ Jako ekspert analizy danych, przygotuj zwięzłą interpretację wyników w języku polskim:
163
+
164
+ Pytanie użytkownika: {question}
165
+ Wynik analizy: {str(result)[:800] if result is not None else "Brak wyniku"}
166
+ Użyty kod: {code}
167
+
168
+ Przygotuj interpretację w formacie:
169
+
170
+ ## 📊 Podsumowanie
171
+ [1-2 zdania o głównym wyniku]
172
+
173
+ ## 🔍 Kluczowe wnioski
174
+ - [Wniosek 1]
175
+ - [Wniosek 2]
176
+ - [Wniosek 3 jeśli jest]
177
+
178
+ ## 💡 Sugestie dalszych analiz
179
+ [Konkretne pomysły na kolejne pytania]
180
+
181
+ Bądź konkretny i praktyczny. Używaj prostego języka.
182
+ """
183
+
184
+ try:
185
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
186
+ payload = {
187
+ "model": "deepseek-chat",
188
+ "messages": [{"role": "user", "content": prompt}],
189
+ "temperature": 0.4,
190
+ "max_tokens": 600
191
+ }
192
+
193
+ response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30)
194
+
195
+ if response.status_code == 200:
196
+ return response.json()['choices'][0]['message']['content']
197
+ else:
198
+ return f"❌ Błąd API interpretacji: {response.status_code}"
199
+
200
+ except Exception as e:
201
+ return f"❌ Błąd interpretacji: {str(e)}"
202
+
203
+ def simple_query_ai(prompt, api_key):
204
+ """Zapytanie do AI"""
205
+ if not api_key:
206
+ return None
207
+
208
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
209
+ payload = {
210
+ "model": "deepseek-chat",
211
+ "messages": [{"role": "user", "content": prompt}],
212
+ "temperature": 0.3,
213
+ "max_tokens": 800
214
+ }
215
+
216
+ try:
217
+ response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30)
218
+ if response.status_code == 200:
219
+ return response.json()['choices'][0]['message']['content']
220
+ else:
221
+ st.error(f"API Error: {response.status_code}")
222
+ return None
223
+ except Exception as e:
224
+ st.error(f"Connection error: {e}")
225
+ return None
226
+
227
+ def extract_python_code(text):
228
+ """Wyciągnij kod Python"""
229
+ if "```python" in text:
230
+ start = text.find("```python") + 9
231
+ end = text.find("```", start)
232
+ return text[start:end].strip()
233
+ elif "```" in text:
234
+ start = text.find("```") + 3
235
+ end = text.find("```", start)
236
+ return text[start:end].strip()
237
+ else:
238
+ return text.strip()
239
+
240
+ def safe_execute_code(code, df):
241
+ """Wykonaj kod bezpiecznie z szczegółowymi logami"""
242
+
243
+ # Logi wykonania
244
+ st.subheader("🔍 Logi wykonania:")
245
+ log_container = st.container()
246
+
247
+ with log_container:
248
+ st.write("**Krok 1:** Sprawdzanie bezpieczeństwa kodu...")
249
+
250
+ # Usuń problematyczne linijki
251
+ problematic_patterns = ['read_csv', 'pd.read', 'pandas.read', 'to_csv', 'read_excel', 'open(', 'file(']
252
+ lines = code.split('\n')
253
+ clean_lines = []
254
+ removed_lines = []
255
+
256
+ for line in lines:
257
+ if not any(pattern in line for pattern in problematic_patterns):
258
+ clean_lines.append(line)
259
+ else:
260
+ removed_lines.append(line.strip())
261
+
262
+ if removed_lines:
263
+ st.warning(f"🛡️ Usunięto {len(removed_lines)} potencjalnie niebezpiecznych linijek:")
264
+ for line in removed_lines:
265
+ st.code(line, language='python')
266
+ else:
267
+ st.success("✅ Kod jest bezpieczny")
268
+
269
+ cleaned_code = '\n'.join(clean_lines)
270
+
271
+ st.write("**Krok 2:** Przygotowanie środowiska wykonania...")
272
+
273
+ # Bezpieczne środowisko z matplotlib
274
+ safe_globals = {
275
+ 'pd': pd,
276
+ 'np': np,
277
+ 'plt': plt,
278
+ 'df': df,
279
+ 'len': len,
280
+ 'sum': sum,
281
+ 'max': max,
282
+ 'min': min,
283
+ 'abs': abs,
284
+ 'round': round,
285
+ 'str': str,
286
+ 'int': int,
287
+ 'float': float,
288
+ 'list': list,
289
+ 'dict': dict,
290
+ 'sorted': sorted,
291
+ 'enumerate': enumerate,
292
+ 'range': range
293
+ }
294
+
295
+ st.success(f"✅ Dostępne moduły: pd, np, plt, df")
296
+
297
+ st.write("**Krok 3:** Wykonywanie kodu...")
298
+
299
+ safe_locals = {'result': None, 'fig': None}
300
+
301
+ try:
302
+ # Wykonaj cały kod naraz (bezpieczniej dla wielolinijkowych struktur)
303
+ progress_bar = st.progress(0)
304
+ progress_bar.progress(0.3)
305
+
306
+ # Spróbuj wykonać cały kod
307
+ exec(cleaned_code, safe_globals, safe_locals)
308
+
309
+ progress_bar.progress(1.0)
310
+ st.success("✅ Kod wykonany pomyślnie")
311
+
312
+ # Sprawdź co zostało utworzone
313
+ result = safe_locals.get('result')
314
+ fig = safe_locals.get('fig')
315
+
316
+ st.write("**Krok 4:** Sprawdzenie wyników...")
317
+
318
+ if result is not None:
319
+ result_type = type(result).__name__
320
+ if hasattr(result, 'shape'):
321
+ st.info(f"📊 Wynik: {result_type} o rozmiarze {result.shape}")
322
+ else:
323
+ st.info(f"📊 Wynik: {result_type}")
324
+ else:
325
+ st.warning("⚠️ Brak wyniku w zmiennej 'result'")
326
+
327
+ if fig is not None:
328
+ st.success("📈 Wykres został utworzony")
329
+
330
+ # Sprawdź czy matplotlib ma aktywne figury
331
+ if plt.get_fignums():
332
+ if fig is None:
333
+ fig = plt.gcf() # Pobierz aktualną figurę
334
+ st.info("📈 Wykrywam wykres matplotlib")
335
+
336
+ return result, fig, None
337
+
338
+ except Exception as e:
339
+ st.error(f"❌ Błąd wykonania: {str(e)}")
340
+ return None, None, str(e)
341
+
342
+ # Sidebar
343
+ with st.sidebar:
344
+ st.header("⚙️ Konfiguracja")
345
+ api_key = st.text_input("🔑 DeepSeek API Key", type="password")
346
+
347
+ st.header("📁 Upload pliku")
348
+ uploaded_file = st.file_uploader("Wybierz plik CSV", type=['csv', 'txt'])
349
+
350
+ if uploaded_file:
351
+ st.info("📋 Aplikacja automatycznie wykryje format pliku")
352
+
353
+ # Opcja ręcznego wyboru
354
+ manual_override = st.checkbox("🔧 Ręczne ustawienia (zaawansowane)")
355
+
356
+ if manual_override:
357
+ manual_sep = st.selectbox("Separator", [';', ',', '\t', '|'])
358
+ manual_enc = st.selectbox("Kodowanie", ['utf-8', 'cp1250', 'latin1'])
359
+ st.session_state.manual_config = {
360
+ 'separator': manual_sep,
361
+ 'encoding': manual_enc,
362
+ 'columns': 0,
363
+ 'quality': 0,
364
+ 'sample_df': None,
365
+ 'column_names': []
366
+ }
367
+
368
+ # Historia i szybkie pytania
369
+ if st.session_state.df is not None:
370
+ st.header("📊 Szybkie pytania")
371
+
372
+ # Podstawowe pytania
373
+ if st.button("📏 Ile wierszy?"):
374
+ st.session_state.quick_question = "Ile jest wierszy w danych?"
375
+ if st.button("📋 Jakie kolumny?"):
376
+ st.session_state.quick_question = "Pokaż nazwy wszystkich kolumn"
377
+ if st.button("🔢 Statystyki"):
378
+ st.session_state.quick_question = "Pokaż podstawowe statystyki numeryczne"
379
+
380
+ # Szybkie wykresy
381
+ st.subheader("📈 Szybkie wykresy")
382
+ numeric_cols = st.session_state.df.select_dtypes(include=[np.number]).columns
383
+ categorical_cols = st.session_state.df.select_dtypes(include=['object']).columns
384
+
385
+ if len(numeric_cols) > 0:
386
+ if st.button("📊 Histogram pierwszej kolumny"):
387
+ first_numeric = numeric_cols[0]
388
+ st.session_state.quick_question = f"Stwórz histogram dla kolumny {first_numeric}"
389
+
390
+ if len(categorical_cols) > 0:
391
+ if st.button("📈 Wykres kategorii"):
392
+ first_categorical = categorical_cols[0]
393
+ st.session_state.quick_question = f"Stwórz wykres słupkowy pokazujący ile jest każdej kategorii w kolumnie {first_categorical}"
394
+
395
+ if len(numeric_cols) >= 2:
396
+ if st.button("🔍 Scatter plot"):
397
+ col1, col2 = numeric_cols[0], numeric_cols[1]
398
+ st.session_state.quick_question = f"Stwórz scatter plot dla kolumn {col1} i {col2}"
399
+
400
+ # Historia
401
+ if st.session_state.analysis_history:
402
+ st.header("📜 Historia")
403
+ st.write(f"Wykonanych analiz: **{len(st.session_state.analysis_history)}**")
404
+
405
+ # Ostatnie pytanie z info o wykresie
406
+ if st.session_state.analysis_history:
407
+ last = st.session_state.analysis_history[-1]
408
+ st.write("**Ostatnie pytanie:**")
409
+ st.write(f"_{last['question'][:50]}..._")
410
+ if last.get('has_plot', False):
411
+ st.write("📈 *Zawierało wykres*")
412
+
413
+ # Statystyki historii
414
+ total_plots = sum(1 for analysis in st.session_state.analysis_history if analysis.get('has_plot', False))
415
+ st.write(f"**Wykresy utworzone:** {total_plots}")
416
+
417
+ # Najczęstsze słowa kluczowe
418
+ all_questions = [analysis['question'].lower() for analysis in st.session_state.analysis_history]
419
+ common_words = []
420
+ for question in all_questions:
421
+ if 'balans' in question: common_words.append('balans')
422
+ if 'wykres' in question or 'histogram' in question: common_words.append('wykresy')
423
+ if 'średnia' in question: common_words.append('średnie')
424
+ if 'top' in question: common_words.append('rankingi')
425
+
426
+ if common_words:
427
+ unique_words = list(set(common_words))
428
+ st.write(f"**Popularne tematy:** {', '.join(unique_words)}")
429
+
430
+ # Main interface
431
+ col1, col2 = st.columns([3, 1])
432
+
433
+ with col1:
434
+ if uploaded_file and st.button("🚀 WCZYTAJ I ANALIZUJ PLIK", type="primary"):
435
+ with st.spinner("🔍 Analizuję format pliku..."):
436
+
437
+ # Sprawdź czy użytkownik wybrał ręczne ustawienia
438
+ if hasattr(st.session_state, 'manual_config') and st.session_state.get('manual_override', False):
439
+ config = st.session_state.manual_config
440
+ st.info("🔧 Używam ręcznych ustawień")
441
+ else:
442
+ # Automatyczne wykrywanie
443
+ config, error = smart_csv_detection(uploaded_file)
444
+
445
+ if error:
446
+ st.error(f"❌ {error}")
447
+ st.stop()
448
+
449
+ # Wczytaj pełny plik
450
+ with st.spinner("📊 Wczytuję dane..."):
451
+ df, error = load_csv_with_config(uploaded_file, config)
452
+
453
+ if error:
454
+ st.error(f"❌ {error}")
455
+ st.stop()
456
+
457
+ st.session_state.df = df
458
+ st.session_state.file_info = config
459
+
460
+ # Pokaż wyniki
461
+ st.success(f"✅ SUKCES! Wczytano {df.shape[0]:,} wierszy × {df.shape[1]} kolumn")
462
+
463
+ # Info o kolumnach
464
+ st.write("📋 **Kolumny:**")
465
+ col_info = []
466
+ for i, col in enumerate(df.columns):
467
+ dtype = str(df[col].dtype)
468
+ non_null = df[col].count()
469
+ col_info.append(f"{i+1}. **{col}** ({dtype}) - {non_null:,}/{len(df):,} wartości")
470
+
471
+ # Pokaż kolumny w kolumnach dla czytelności
472
+ col_chunks = [col_info[i:i+3] for i in range(0, len(col_info), 3)]
473
+ for chunk in col_chunks:
474
+ cols = st.columns(len(chunk))
475
+ for i, info in enumerate(chunk):
476
+ cols[i].write(info)
477
+
478
+ # Podgląd danych
479
+ st.write("📄 **Podgląd danych:**")
480
+ safe_display_data(df)
481
+
482
+ # Sekcja analizy
483
+ if st.session_state.df is not None:
484
+ st.header("🔍 Analiza danych")
485
+
486
+ df = st.session_state.df
487
+
488
+ # Pokaż historię analiz jeśli są
489
+ if st.session_state.analysis_history:
490
+ with st.expander(f"📜 Historia analiz ({len(st.session_state.analysis_history)})", expanded=False):
491
+ for i, analysis in enumerate(reversed(st.session_state.analysis_history[-5:])): # Ostatnie 5
492
+ idx = len(st.session_state.analysis_history) - i
493
+ st.write(f"**{idx}. {analysis['question']}**")
494
+
495
+ # Ikona wykresu jeśli był
496
+ if analysis.get('has_plot', False):
497
+ st.write("📈 *Zawierał wykres*")
498
+
499
+ # Krótki wynik
500
+ result_preview = str(analysis['result'])[:100]
501
+ if len(str(analysis['result'])) > 100:
502
+ result_preview += "..."
503
+ st.write(f"Wynik: {result_preview}")
504
+
505
+ # Timestamp
506
+ if 'timestamp' in analysis:
507
+ st.caption(f"⏰ {analysis['timestamp'].strftime('%H:%M:%S')}")
508
+
509
+ st.write("---")
510
+
511
+ # Formularz do pytań
512
+ with st.form("analysis_form", clear_on_submit=True):
513
+ # Sprawdź czy jest szybkie pytanie
514
+ default_question = ""
515
+ if hasattr(st.session_state, 'quick_question') and st.session_state.quick_question:
516
+ default_question = st.session_state.quick_question
517
+ st.session_state.quick_question = ""
518
+
519
+ question = st.text_area(
520
+ "💬 Zadaj pytanie o dane:",
521
+ height=100,
522
+ placeholder="np. Histogram balansu, Top 10 zawodów, Rozkład wieku klientów",
523
+ key=f"question_input_{st.session_state.questions_count}",
524
+ value=default_question
525
+ )
526
+
527
+ col_btn1, col_btn2 = st.columns(2)
528
+ with col_btn1:
529
+ submit_button = st.form_submit_button("🧠 ANALIZUJ", type="primary")
530
+ with col_btn2:
531
+ clear_history = st.form_submit_button("🗑️ Wyczyść historię")
532
+
533
+ # Wyczyść historię jeśli kliknięto
534
+ if clear_history:
535
+ st.session_state.analysis_history = []
536
+ st.success("✅ Historia wyczyszczona")
537
+ st.rerun()
538
+
539
+ # Przetwórz pytanie
540
+ if submit_button and api_key and question.strip():
541
+ st.session_state.questions_count += 1
542
+
543
+ # Przygotuj informacje o danych
544
+ columns_info = f"Kolumny: {', '.join(df.columns)}"
545
+ dtypes_info = f"Typy danych: {dict(df.dtypes)}"
546
+
547
+ sample_data = []
548
+ for col in df.columns[:8]: # Pierwsze 8 kolumn
549
+ try:
550
+ if df[col].dtype in ['object', 'string']:
551
+ unique_vals = df[col].dropna().unique()[:3]
552
+ sample_data.append(f"{col}: {list(unique_vals)}")
553
+ else:
554
+ stats = f"min={df[col].min()}, max={df[col].max()}, średnia={df[col].mean():.2f}"
555
+ sample_data.append(f"{col}: {stats}")
556
+ except:
557
+ sample_data.append(f"{col}: [błąd próbkowania]")
558
+
559
+ sample_info = "Przykładowe dane: " + " | ".join(sample_data)
560
+
561
+ # Prompt dla AI z zachętą do tworzenia wykresów
562
+ prompt = f"""
563
+ Odpowiedz na pytanie o dane używając kodu Python. WAŻNE: Jeśli pytanie może być lepiej zobrazowane wykresem, stwórz go!
564
+
565
+ PYTANIE: {question}
566
+
567
+ INFORMACJE O DANYCH:
568
+ {columns_info}
569
+ {dtypes_info}
570
+ Rozmiar: {df.shape[0]} wierszy × {df.shape[1]} kolumn
571
+ {sample_info}
572
+
573
+ ZASADY:
574
+ 1. DataFrame nazywa się 'df' i jest już załadowany
575
+ 2. NIE używaj pd.read_csv(), pd.read_excel() ani podobnych
576
+ 3. Wynik zapisz w zmiennej 'result'
577
+ 4. Używaj pandas, numpy, matplotlib (plt)
578
+ 5. Kod ma być prosty i skuteczny
579
+ 6. WYKRES: Jeśli pytanie dotyczy rozkładów, trendów, porównań - ZAWSZE utwórz wykres w zmiennej 'fig'
580
+
581
+ KIEDY TWORZYĆ WYKRESY:
582
+ - Rozkłady wartości → histogram: plt.hist()
583
+ - Porównania kategorii → wykres słupkowy: plt.bar()
584
+ - Trendy w czasie → wykres liniowy: plt.plot()
585
+ - Korelacje → scatter plot: plt.scatter()
586
+ - Top N → wykres słupkowy
587
+ - Statystyki grupowe → wykres słupkowy lub pudełkowy
588
+
589
+ PRZYKŁADY KODÓW Z WYKRESAMI:
590
+
591
+ # Histogram rozkładu
592
+ result = df['balance'].describe()
593
+ fig, ax = plt.subplots(figsize=(10, 6))
594
+ ax.hist(df['balance'], bins=30, alpha=0.7)
595
+ ax.set_title('Rozkład balansu klientów')
596
+ ax.set_xlabel('Balans')
597
+ ax.set_ylabel('Liczba klientów')
598
+
599
+ # Wykres słupkowy dla kategorii
600
+ result = df['job'].value_counts().head(10)
601
+ fig, ax = plt.subplots(figsize=(12, 6))
602
+ result.plot(kind='bar', ax=ax)
603
+ ax.set_title('Top 10 zawodów')
604
+ ax.set_xlabel('Zawód')
605
+ ax.set_ylabel('Liczba osób')
606
+ plt.xticks(rotation=45)
607
+
608
+ # Porównanie grup
609
+ result = df.groupby('job')['balance'].mean().head(10)
610
+ fig, ax = plt.subplots(figsize=(12, 6))
611
+ result.plot(kind='bar', ax=ax)
612
+ ax.set_title('Średni balans według zawodu')
613
+ ax.set_ylabel('Średni balans')
614
+ plt.xticks(rotation=45)
615
+
616
+ PRZYKŁADY BEZ WYKRESÓW (tylko liczby):
617
+ - Liczba wierszy: result = len(df)
618
+ - Średnia: result = df['kolumna'].mean()
619
+ - Filtrowanie: result = len(df[df['kolumna'] > 100])
620
+
621
+ Zwróć TYLKO kod Python (bez wyjaśnień):
622
+ """
623
+
624
+ # Zapytaj AI
625
+ with st.spinner("🤖 AI generuje kod..."):
626
+ st.info("🔄 Wysyłam zapytanie do DeepSeek API...")
627
+ ai_response = simple_query_ai(prompt, api_key)
628
+
629
+ if ai_response:
630
+ st.success("✅ Otrzymano odpowiedź od AI")
631
+ code = extract_python_code(ai_response)
632
+
633
+ st.subheader("🔧 Wygenerowany kod:")
634
+ st.code(code, language='python')
635
+
636
+ # Wykonaj z logowaniem
637
+ with st.spinner("⚡ Wykonuję analizę..."):
638
+ result, fig, error = safe_execute_code(code, df)
639
+
640
+ # Wyniki
641
+ if error:
642
+ st.error(f"❌ Błąd końcowy: {error}")
643
+ st.write("**Możliwe rozwiązania:**")
644
+ st.write("- Sprawdź nazwy kolumn")
645
+ st.write("- Upewnij się że kolumna zawiera dane liczbowe")
646
+ st.write("- Spróbuj prostsze pytanie")
647
+ else:
648
+ st.subheader("📊 WYNIK:")
649
+ if result is not None:
650
+ if isinstance(result, (int, float)):
651
+ st.metric("Wynik", f"{result:,}")
652
+ elif isinstance(result, str):
653
+ st.write(f"**{result}**")
654
+ elif isinstance(result, pd.DataFrame):
655
+ st.write("**Tabela wyników:**")
656
+ safe_display_data(result, max_rows=20)
657
+ elif isinstance(result, pd.Series):
658
+ st.write("**Serie danych:**")
659
+ safe_display_data(result.to_frame(), max_rows=20)
660
+ else:
661
+ st.write("**Wynik:**")
662
+ st.text(str(result)[:1000])
663
+
664
+ # WYKRESY - ulepszone wyświetlanie
665
+ if fig is not None:
666
+ st.subheader("📈 Wykres:")
667
+ try:
668
+ plt.tight_layout()
669
+ st.pyplot(fig, use_container_width=True)
670
+ st.success("✅ Wykres wyświetlony pomyślnie")
671
+ except Exception as plot_error:
672
+ st.error(f"❌ Błąd wyświetlania wykresu: {plot_error}")
673
+ finally:
674
+ plt.close(fig)
675
+ elif any(word in question.lower() for word in ['wykres', 'histogram', 'rozkład', 'porównaj', 'pokaż', 'wizualiz']):
676
+ st.info("💡 Zapytanie sugeruje wykres, ale AI go nie utworzyło. Spróbuj poprosić bezpośrednio o wykres.")
677
+
678
+ # INTERPRETACJA AI
679
+ st.subheader("🧠 Interpretacja AI:")
680
+ with st.spinner("🔮 AI interpretuje wyniki..."):
681
+ interpretation = generate_interpretation(question, result, code, api_key)
682
+ st.markdown(interpretation)
683
+
684
+ # Zapisz do historii
685
+ analysis_record = {
686
+ 'question': question,
687
+ 'code': code,
688
+ 'result': result,
689
+ 'interpretation': interpretation,
690
+ 'timestamp': pd.Timestamp.now(),
691
+ 'has_plot': fig is not None
692
+ }
693
+ st.session_state.analysis_history.append(analysis_record)
694
+
695
+ # Zachęć do kolejnych pytań
696
+ st.markdown("---")
697
+ st.success("✅ Analiza zakończona! Możesz zadać kolejne pytanie powyżej.")
698
+
699
+ # Sugestie kolejnych pytań na podstawie wyniku
700
+ if isinstance(result, pd.DataFrame) and len(result) > 1:
701
+ st.info("💡 Sugestia: Możesz zapytać o wykres dla tych wyników")
702
+ elif isinstance(result, (int, float)) and 'balans' in question.lower():
703
+ st.info("💡 Sugestia: Spróbuj 'Pokaż histogram balansu' lub 'Rozkład balansu klientów'")
704
+ elif 'top' in question.lower() or 'najwyższ' in question.lower():
705
+ st.info("💡 Sugestia: Możesz poprosić o wykres słupkowy dla tych wyników")
706
+
707
+ else:
708
+ st.error("❌ Nie udało się uzyskać odpowiedzi od AI")
709
+ st.write("**Możliwe przyczyny:**")
710
+ st.write("- Błąd klucza API")
711
+ st.write("- Problem z połączeniem internetowym")
712
+ st.write("- Przeciążenie serwera DeepSeek")
713
+
714
+ elif submit_button and not api_key:
715
+ st.warning("⚠️ Wprowadź klucz API DeepSeek w lewym panelu")
716
+ elif submit_button and not question.strip():
717
+ st.warning("⚠️ Wprowadź pytanie do analizy")
718
+
719
+ with col2:
720
+ st.header("📊 Informacje")
721
+
722
+ if st.session_state.df is not None:
723
+ df = st.session_state.df
724
+
725
+ # Podstawowe metryki
726
+ st.metric("📊 Wiersze", f"{len(df):,}")
727
+ st.metric("📋 Kolumny", len(df.columns))
728
+
729
+ # Informacje o pliku
730
+ if st.session_state.file_info:
731
+ st.subheader("📁 Format pliku")
732
+ info = st.session_state.file_info
733
+ st.write(f"**Separator:** `{info['separator']}`")
734
+ st.write(f"**Kodowanie:** {info['encoding']}")
735
+
736
+ # Typy danych
737
+ st.subheader("🏷️ Typy kolumn")
738
+ type_counts = df.dtypes.value_counts()
739
+ for dtype, count in type_counts.items():
740
+ st.write(f"**{str(dtype)}:** {count}")
741
+
742
+ # Braki danych
743
+ missing_data = df.isnull().sum()
744
+ missing_cols = missing_data[missing_data > 0]
745
+
746
+ if len(missing_cols) > 0:
747
+ st.subheader("⚠️ Braki danych")
748
+ for col, missing in missing_cols.items():
749
+ pct = (missing / len(df)) * 100
750
+ st.write(f"**{col}:** {missing:,} ({pct:.1f}%)")
751
+ else:
752
+ st.success("✅ Brak braków danych")
753
+
754
+ # Kolumny numeryczne - podstawowe statystyki
755
+ numeric_cols = df.select_dtypes(include=[np.number]).columns
756
+ if len(numeric_cols) > 0:
757
+ st.subheader("🔢 Statystyki numeryczne")
758
+ for col in numeric_cols[:5]:
759
+ try:
760
+ mean_val = df[col].mean()
761
+ min_val = df[col].min()
762
+ max_val = df[col].max()
763
+ st.write(f"**{col}:**")
764
+ st.write(f" Średnia: {mean_val:.2f}")
765
+ st.write(f" Zakres: {min_val:.2f} - {max_val:.2f}")
766
+ except:
767
+ st.write(f"**{col}:** błąd obliczeń")
768
+ else:
769
+ st.info("👆 Wczytaj plik CSV aby zobaczyć statystyki")
770
+
771
+ # Footer
772
+ st.markdown("---")
773
+ st.markdown("🔧 **Funkcje:**")
774
+ col_f1, col_f2 = st.columns(2)
775
+
776
+ with col_f1:
777
+ st.markdown("• **Automatyczne wykrywanie** formatu CSV")
778
+ st.markdown("• **Obsługa separatorów:** `;` `,` `tab` `|`")
779
+ st.markdown("• **Różne kodowania:** UTF-8, CP1250, Latin1")
780
+ st.markdown("• **Inteligentne wyświetlanie** (omija błędy)")
781
+ st.markdown("• **Szczegółowe logi** wykonania kodu")
782
+
783
+ with col_f2:
784
+ st.markdown("• **Interpretacja AI** wyników analizy")
785
+ st.markdown("• **Historia pytań** w sesji")
786
+ st.markdown("• **Automatyczne wykresy** (histogram, bar, scatter)")
787
+ st.markdown("• **Szybkie wykresy** jednym kliknięciem")
788
+ st.markdown("• **Ciągłość analizy** - kolejne pytania")
789
+
790
+ st.info("💡 **Tip:** Pytaj o 'histogram', 'wykres', 'rozkład', 'porównanie' - AI automatycznie utworzy odpowiedni wykres!")
791
+
792
+ # Debug info
793
+ if st.session_state.analysis_history:
794
+ plots_count = sum(1 for analysis in st.session_state.analysis_history if analysis.get('has_plot', False))
795
+ st.caption(f"🎯 W tej sesji: {len(st.session_state.analysis_history)} analiz, {plots_count} wykresów")