Emirhan Cerrah commited on
Commit
69dbe78
·
1 Parent(s): a434056

feat: Railway deployment with Firebase embedding cache system

Browse files
.firebaserc ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "projects": {
3
+ "default": "jira-duplicate-detection"
4
+ }
5
+ }
6
+
.gitignore CHANGED
@@ -104,3 +104,18 @@ tmp/
104
  package-lock.json
105
  yarn.lock
106
  node_modules/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  package-lock.json
105
  yarn.lock
106
  node_modules/
107
+
108
+ # Firebase
109
+ firebase-service-account.json
110
+ firebase-service-account-*.json
111
+ .firebase/
112
+
113
+ # User data and embeddings (will be on Railway volume or Firebase)
114
+ data/user_embeddings/
115
+ data/user_data/
116
+ data/user_datasets/
117
+ data/test_*.csv
118
+ data/embedding_outputs/
119
+
120
+ # Test files
121
+ test_*.py
.railwayignore ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Railway Ignore File
2
+ # Bu dosyalar Railway'e upload edilmeyecek
3
+
4
+ # Python cache
5
+ __pycache__/
6
+ *.pyc
7
+ *.pyo
8
+
9
+ # Virtual environment
10
+ venv/
11
+ env/
12
+
13
+ # Local development
14
+ .env
15
+ .env.local
16
+ *.log
17
+
18
+ # Firebase credentials (Railway environment variables'dan gelecek)
19
+ firebase-service-account*.json
20
+
21
+ # IDE
22
+ .vscode/
23
+ .idea/
24
+
25
+ # Git
26
+ .git/
27
+ .gitignore
28
+
29
+ # Test files
30
+ test_*.py
31
+ tests/
32
+
33
+ # Documentation (optional)
34
+ *.md
35
+ !README.md
36
+
37
+ # Notebooks
38
+ notebooks/
39
+ *.ipynb
40
+
41
+ # Large data files (bunlar user upload'la gelecek)
42
+ data/data_*.csv
43
+ data/*.xlsx
44
+ data/duplike*.xlsx
45
+
46
+ # Temporary
47
+ tmp/
48
+ temp/
49
+ *.tmp
50
+
EMBEDDING_SYSTEM.md ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔄 Dinamik Embedding Sistemi
2
+
3
+ ## Genel Bakış
4
+
5
+ Sistem artık **dinamik embedding güncellemesi** destekliyor! Yeni bir rapor eklendiğinde:
6
+
7
+ 1. ✅ **CSV dosyasına yazılır**
8
+ 2. ✅ **Embedding vektörü otomatik oluşturulur**
9
+ 3. ✅ **FAISS index'e eklenir**
10
+ 4. ✅ **Hemen arama sonuçlarında görünür!**
11
+
12
+ ## Nasıl Çalışır?
13
+
14
+ ### 1. Rapor Ekleme
15
+ Kullanıcı yeni bir rapor eklediğinde (`/api/create_report`):
16
+
17
+ ```python
18
+ # 1. Rapor CSV'ye yazılır
19
+ df.to_csv(csv_path, ...)
20
+
21
+ # 2. Embedding otomatik oluşturulur
22
+ update_embeddings_for_new_report(report_id)
23
+ ```
24
+
25
+ ### 2. Embedding Oluşturma
26
+ `update_embeddings_for_new_report()` fonksiyonu:
27
+
28
+ 1. **DataFrame'i yeniden yükler** (yeni raporu içeren)
29
+ 2. **Bi-encoder modeli ile embedding oluşturur**
30
+ ```python
31
+ new_embedding = bi_encoder.encode([combined_text])
32
+ ```
33
+ 3. **Mevcut embeddings.npy dosyasına ekler**
34
+ 4. **FAISS index'e ekler** (platform'a göre: android/ios/unknown)
35
+ 5. **Disk'e kaydeder** (kalıcılık için)
36
+
37
+ ### 3. Veri Yapısı
38
+
39
+ ```
40
+ data/
41
+ ├── embedding_outputs/
42
+ │ ├── embeddings.npy # Tüm embedding vektörleri (güncellenebilir)
43
+ │ ├── faiss_index_android.index # Android FAISS index (güncellenebilir)
44
+ │ ├── faiss_index_ios.index # iOS FAISS index (güncellenebilir)
45
+ │ └── faiss_index_unknown.index # Unknown FAISS index (güncellenebilir)
46
+ └── data_with_application.csv # Ana veri dosyası
47
+ ```
48
+
49
+ ## Default Data vs Custom Data
50
+
51
+ ### Default Data (Varsayılan Veri)
52
+ - ✅ **Embedding otomatik güncellenir**
53
+ - ✅ **FAISS index güncellenir**
54
+ - ✅ **Hızlı arama (hybrid search)**
55
+
56
+ ### Custom Data (Kullanıcı Verisi)
57
+ - ⚡ **On-the-fly arama** (text-based matching)
58
+ - 📝 CSV'ye kaydedilir
59
+ - 🔄 Embedding oluşturmaya gerek yok (text matching kullanır)
60
+
61
+ ## Performans
62
+
63
+ | İşlem | Süre |
64
+ |-------|------|
65
+ | Yeni rapor ekleme | ~1-2 saniye |
66
+ | Embedding oluşturma | ~0.5 saniye |
67
+ | FAISS index güncelleme | ~0.2 saniye |
68
+ | **Toplam** | **~2-3 saniye** |
69
+
70
+ ## Teknik Detaylar
71
+
72
+ ### Embedding Boyutu
73
+ - Model: `paraphrase-multilingual-MiniLM-L12-v2`
74
+ - Boyut: **384 dimensions**
75
+ - Format: `float32` (FAISS için optimize edilmiş)
76
+
77
+ ### FAISS Index
78
+ - Tip: **IndexFlatIP** (Inner Product - Cosine Similarity)
79
+ - Normalizasyon: Embeddings normalize edilir
80
+ - Platform bazlı: Android, iOS, Unknown için ayrı index'ler
81
+
82
+ ### Güncelleme Algoritması
83
+
84
+ ```python
85
+ # 1. Mevcut embeddings'i yükle
86
+ existing = np.load("embeddings.npy") # Shape: (N, 384)
87
+
88
+ # 2. Yeni embedding ekle
89
+ new = encoder.encode([text]) # Shape: (1, 384)
90
+ updated = np.vstack([existing, new]) # Shape: (N+1, 384)
91
+
92
+ # 3. Kaydet
93
+ np.save("embeddings.npy", updated)
94
+
95
+ # 4. FAISS'e ekle
96
+ normalized = new / np.linalg.norm(new)
97
+ index.add(normalized.astype('float32'))
98
+ faiss.write_index(index, "faiss_index.index")
99
+ ```
100
+
101
+ ## Kullanım Örnekleri
102
+
103
+ ### Backend'den Yeni Rapor Ekleme
104
+
105
+ ```python
106
+ # POST /api/create_report
107
+ {
108
+ "summary": "BiP mesaj gönderilirken crash oluyor",
109
+ "description": "Kullanıcı mesaj göndermeye çalıştığında...",
110
+ "component": "Android",
111
+ "priority": "High"
112
+ }
113
+
114
+ # Response
115
+ {
116
+ "success": true,
117
+ "report_id": 1234,
118
+ "embeddings_updated": true # ✅ Embedding güncellendi!
119
+ }
120
+ ```
121
+
122
+ ### Frontend Bildirimi
123
+
124
+ Rapor kaydedildiğinde kullanıcı görür:
125
+ ```
126
+ ✅ Rapor başarıyla kaydedildi!
127
+ Rapor ID: 1234
128
+ ✅ Embedding oluşturuldu - Rapor hemen arama sonuçlarında görünecek
129
+ ```
130
+
131
+ ## Hata Durumları
132
+
133
+ ### Embedding Oluşturulamazsa
134
+
135
+ ```python
136
+ ⚠️ Could not update embeddings: [error message]
137
+ ⚠️ New report will not appear in search until system restart!
138
+ ```
139
+
140
+ Bu durumda:
141
+ 1. Rapor CSV'ye kaydedilir ✅
142
+ 2. Ama arama sonuçlarında çıkmaz ❌
143
+ 3. Sistem yeniden başlatılınca embedding pipeline çalıştırılmalı
144
+
145
+ ### Çözüm
146
+
147
+ ```bash
148
+ # Tüm embeddings'i yeniden oluştur
149
+ python src/embedding_pipeline.py
150
+ ```
151
+
152
+ ## Sınırlamalar
153
+
154
+ 1. **Tek seferde 1 rapor**: Her rapor için ayrı embedding oluşturulur
155
+ 2. **Bellek kullanımı**: Her rapor ~1.5KB (384 float32)
156
+ 3. **Disk yazma**: Her ekleme için 2 dosya güncellenir (embeddings.npy + faiss_index.index)
157
+
158
+ ## Gelecek İyileştirmeler
159
+
160
+ - [ ] Batch embedding update (birden fazla rapor için)
161
+ - [ ] Asenkron embedding (kullanıcıyı bekletmeden)
162
+ - [ ] Embedding cache sistemi
163
+ - [ ] Incremental FAISS index (daha hızlı güncelleme)
164
+ - [ ] Custom data için de embedding desteği
165
+
166
+ ## Log Örnekleri
167
+
168
+ Başarılı ekleme:
169
+ ```
170
+ ✅ New report created: ID=1234, App=BiP, Summary=...
171
+ 🔄 Updating embeddings for new report...
172
+ 📥 Reloading data to include new report...
173
+ 🔄 Generating embedding for new report: 'BiP mesaj gönderilirken...'
174
+ 📊 Loaded existing embeddings: (1233, 384)
175
+ ✅ New embeddings shape: (1234, 384)
176
+ 💾 Saved updated embeddings to data/embedding_outputs/embeddings.npy
177
+ 🔄 Adding to FAISS index: android
178
+ 💾 Saved updated FAISS index to data/embedding_outputs/faiss_index_android.index
179
+ ✅ Successfully added new report to FAISS index (android)
180
+ ✅ Embeddings updated successfully!
181
+ ```
182
+
183
+ ## Test Senaryosu
184
+
185
+ 1. Yeni rapor ekle
186
+ 2. Hemen arama yap
187
+ 3. Yeni rapor sonuçlarda görünmeli ✅
188
+
189
+ ```bash
190
+ # 1. Rapor ekle
191
+ curl -X POST http://localhost:5001/api/create_report \
192
+ -H "Content-Type: application/json" \
193
+ -d '{"summary": "Test rapor", "description": "Test açıklaması"}'
194
+
195
+ # 2. Hemen ara
196
+ curl -X POST http://localhost:5001/api/search \
197
+ -H "Content-Type: application/json" \
198
+ -d '{"query": "Test rapor", "top_k": 5}'
199
+
200
+ # ✅ Yeni rapor sonuçlarda görünür!
201
+ ```
202
+
203
+ ## Özet
204
+
205
+ | Özellik | Durum |
206
+ |---------|-------|
207
+ | Dinamik embedding | ✅ Aktif |
208
+ | FAISS index güncelleme | ✅ Aktif |
209
+ | Hızlı arama | ✅ Aktif |
210
+ | Custom data desteği | ✅ Aktif (text-based) |
211
+ | Asenkron işlem | ⏳ Gelecek |
212
+ | Batch update | ⏳ Gelecek |
213
+
214
+ ---
215
+
216
+ **Not**: Bu sistem production-ready'dir ve aktif olarak kullanılabilir. Herhangi bir sorun durumunda sistem loglarını kontrol edin.
217
+
218
+
FIREBASE_AUTH_SETUP.md ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔥 Firebase Authentication Kurulum Kılavuzu
2
+
3
+ ## 📋 İçindekiler
4
+ 1. [Firebase Projesi Oluşturma](#1-firebase-projesi-oluşturma)
5
+ 2. [Firebase Authentication Aktifleştirme](#2-firebase-authentication-aktifleştirme)
6
+ 3. [Firebase Firestore Aktifleştirme](#3-firebase-firestore-aktifleştirme)
7
+ 4. [Proje Yapılandırması](#4-proje-yapılandırması)
8
+ 5. [Kullanım](#5-kullanım)
9
+ 6. [Sorun Giderme](#6-sorun-giderme)
10
+
11
+ ---
12
+
13
+ ## 1. Firebase Projesi Oluşturma
14
+
15
+ ### Adım 1: Firebase Console'a Giriş
16
+ 1. https://console.firebase.google.com/ adresine gidin
17
+ 2. Google hesabınızla giriş yapın
18
+ 3. **"Add project"** (Proje Ekle) butonuna tıklayın
19
+
20
+ ### Adım 2: Proje Ayarları
21
+ 1. **Proje Adı**: `bug-report-system` (veya istediğiniz isim)
22
+ 2. **Google Analytics**: İsteğe bağlı (kapatabilirsiniz)
23
+ 3. **Create Project** butonuna tıklayın
24
+ 4. Proje hazır olana kadar bekleyin (~1 dakika)
25
+
26
+ ### Adım 3: Web App Ekleme
27
+ 1. Firebase Console'da projenizi açın
28
+ 2. **"</>** (Web)" ikonuna tıklayın
29
+ 3. **App nickname**: `Bug Report Web App`
30
+ 4. **Firebase Hosting**: ✅ İşaretleyin (opsiyonel)
31
+ 5. **Register app** butonuna tıklayın
32
+
33
+ ### Adım 4: Firebase Config Bilgilerini Kopyalayın
34
+ ```javascript
35
+ // Bu bilgileri kaydedin - sonra kullanacağız!
36
+ const firebaseConfig = {
37
+ apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
38
+ authDomain: "bug-report-system.firebaseapp.com",
39
+ projectId: "bug-report-system",
40
+ storageBucket: "bug-report-system.appspot.com",
41
+ messagingSenderId: "123456789012",
42
+ appId: "1:123456789012:web:abcdef1234567890"
43
+ };
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 2. Firebase Authentication Aktifleştirme
49
+
50
+ ### Adım 1: Authentication'ı Aktifleştir
51
+ 1. Sol menüden **"Build"** > **"Authentication"** seçin
52
+ 2. **"Get started"** butonuna tıklayın
53
+
54
+ ### Adım 2: Email/Password Sign-in Metodunu Aktifleştir
55
+ 1. **"Sign-in method"** tab'ına gidin
56
+ 2. **"Email/Password"** seçeneğini bulun
57
+ 3. **"Enable"** toggle'ını açın
58
+ 4. **"Email link (passwordless sign-in)"** KAPALI bırakın
59
+ 5. **"Save"** butonuna tıklayın
60
+
61
+ ✅ Artık kullanıcılar e-posta ve şifre ile kayıt olabilir!
62
+
63
+ ---
64
+
65
+ ## 3. Firebase Firestore Aktifleştirme
66
+
67
+ ### Adım 1: Firestore Database Oluştur
68
+ 1. Sol menüden **"Build"** > **"Firestore Database"** seçin
69
+ 2. **"Create database"** butonuna tıklayın
70
+
71
+ ### Adım 2: Güvenlik Kurallarını Seç
72
+ **Test Mode** seçin (geliştirme için):
73
+ ```javascript
74
+ rules_version = '2';
75
+ service cloud.firestore {
76
+ match /databases/{database}/documents {
77
+ match /{document=**} {
78
+ allow read, write: if request.auth != null;
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Adım 3: Location Seç
85
+ - **eur3 (europe-west)** seçin (Türkiye'ye en yakın)
86
+ - **Enable** butonuna tıklayın
87
+
88
+ ### Adım 4: Firestore Güvenlik Kurallarını Güncelle (Önemli!)
89
+ ```javascript
90
+ rules_version = '2';
91
+ service cloud.firestore {
92
+ match /databases/{database}/documents {
93
+ // Users collection - sadece kendi verisini okuyabilir
94
+ match /users/{userId} {
95
+ allow read: if request.auth != null && request.auth.uid == userId;
96
+ allow create: if request.auth != null;
97
+ allow update: if request.auth != null && request.auth.uid == userId;
98
+ allow delete: if false; // Kullanıcılar kendi hesaplarını silemez
99
+ }
100
+
101
+ // Reports collection - tüm authenticated kullanıcılar okuyabilir
102
+ match /reports/{reportId} {
103
+ allow read: if request.auth != null;
104
+ allow create: if request.auth != null;
105
+ allow update: if request.auth != null;
106
+ allow delete: if request.auth != null;
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ **Publish** butonuna tıklayın.
113
+
114
+ ---
115
+
116
+ ## 4. Proje Yapılandırması
117
+
118
+ ### Adım 1: Firebase Config Dosyasını Güncelle
119
+
120
+ `web/firebase-config.js` dosyasını açın ve Firebase Console'dan aldığınız bilgilerle güncelleyin:
121
+
122
+ ```javascript
123
+ // Firebase Configuration
124
+ const firebaseConfig = {
125
+ apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // BURAYA KENDİ DEĞERLER
126
+ authDomain: "bug-report-system.firebaseapp.com",
127
+ projectId: "bug-report-system",
128
+ storageBucket: "bug-report-system.appspot.com",
129
+ messagingSenderId: "123456789012",
130
+ appId: "1:123456789012:web:abcdef1234567890"
131
+ };
132
+
133
+ // Initialize Firebase
134
+ firebase.initializeApp(firebaseConfig);
135
+
136
+ // Get Firebase services
137
+ const auth = firebase.auth();
138
+ const db = firebase.firestore();
139
+
140
+ console.log('🔥 Firebase initialized successfully!');
141
+ ```
142
+
143
+ ### Adım 2: Firebase Hosting (Opsiyonel)
144
+
145
+ Eğer Firebase Hosting kullanmak isterseniz:
146
+
147
+ ```bash
148
+ # Firebase CLI'yi yükleyin
149
+ npm install -g firebase-tools
150
+
151
+ # Firebase'e login olun
152
+ firebase login
153
+
154
+ # Projeyi başlatın
155
+ firebase init
156
+
157
+ # Seçenekler:
158
+ # - Hosting: Configure files for Firebase Hosting
159
+ # - What do you want to use as your public directory? -> web
160
+ # - Configure as a single-page app? -> Yes
161
+ # - Set up automatic builds and deploys with GitHub? -> No
162
+
163
+ # Deploy edin
164
+ firebase deploy
165
+ ```
166
+
167
+ ---
168
+
169
+ ## 5. Kullanım
170
+
171
+ ### Kullanıcı Kayıt Olma
172
+
173
+ 1. Tarayıcıda `register.html` sayfasını açın
174
+ 2. Formu doldurun:
175
+ - **Ad Soyad**: En az 3 karakter
176
+ - **E-posta**: Geçerli e-posta adresi
177
+ - **Şifre**: En az 6 karakter (büyük harf, küçük harf, rakam önerilir)
178
+ - **Rol**: Kullanıcı/Admin/Developer/Tester
179
+ 3. **"Kayıt Ol"** butonuna tıklayın
180
+ 4. Başarılı olursa otomatik olarak login sayfasına yönlendirileceksiniz
181
+
182
+ ### Kullanıcı Giriş Yapma
183
+
184
+ 1. Tarayıcıda `login.html` sayfasını açın
185
+ 2. E-posta ve şifrenizi girin
186
+ 3. **"Giriş Yap"** butonuna tıklayın
187
+ 4. Başarılı olursa `data_selection.html` sayfasına yönlendirileceksiniz
188
+
189
+ ### Şifre Sıfırlama
190
+
191
+ 1. Login sayfasında **"Şifremi unuttum?"** linkine tıklayın
192
+ 2. E-posta adresinizi girin
193
+ 3. E-posta kutunuzu kontrol edin
194
+ 4. Gelen linke tıklayarak yeni şifre oluşturun
195
+
196
+ ---
197
+
198
+ ## 6. Sorun Giderme
199
+
200
+ ### Hata: "Firebase yapılandırılmamış!"
201
+
202
+ **Neden**: `firebase-config.js` dosyasındaki API key'ler güncellenmemiş.
203
+
204
+ **Çözüm**:
205
+ 1. Firebase Console'dan config bilgilerini kopyalayın
206
+ 2. `web/firebase-config.js` dosyasını güncelleyin
207
+ 3. Sayfayı yenileyin
208
+
209
+ ---
210
+
211
+ ### Hata: "Email already in use"
212
+
213
+ **Neden**: Bu e-posta adresi ile zaten bir kullanıcı kayıtlı.
214
+
215
+ **Çözüm**:
216
+ - Farklı bir e-posta adresi kullanın
217
+ - Veya login yapın
218
+
219
+ ---
220
+
221
+ ### Hata: "CORS error" veya "Firebase is not defined"
222
+
223
+ **Neden**: Firebase SDK'ları yüklenmemiş.
224
+
225
+ **Çözüm**:
226
+ HTML dosyalarında bu satırları kontrol edin:
227
+ ```html
228
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
229
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js"></script>
230
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore-compat.js"></script>
231
+ <script src="firebase-config.js"></script>
232
+ ```
233
+
234
+ ---
235
+
236
+ ### Hata: "Permission denied" (Firestore)
237
+
238
+ **Neden**: Firestore güvenlik kuralları yanlış yapılandırılmış.
239
+
240
+ **Çözüm**:
241
+ 1. Firebase Console > Firestore Database > Rules
242
+ 2. Yukarıdaki güvenlik kurallarını kopyalayın
243
+ 3. **Publish** edin
244
+
245
+ ---
246
+
247
+ ### Demo Mod Kullanımı
248
+
249
+ Firebase yapılandırılmamışsa sistem otomatik olarak **Demo Mod**'a geçer:
250
+
251
+ **Demo Hesaplar**:
252
+ - E-posta: `[email protected]`
253
+ - Şifre: `demo123`
254
+
255
+ veya
256
+
257
+ - E-posta: `[email protected]`
258
+ - Şifre: `admin123`
259
+
260
+ ---
261
+
262
+ ## 📊 Firestore Veri Yapısı
263
+
264
+ ### Users Collection
265
+
266
+ ```javascript
267
+ /users/{userId}
268
+ {
269
+ uid: "firebase-user-id",
270
+ email: "[email protected]",
271
+ fullName: "Ahmet Yılmaz",
272
+ role: "admin", // user, admin, developer, tester
273
+ createdAt: Timestamp,
274
+ lastLogin: Timestamp
275
+ }
276
+ ```
277
+
278
+ ### Reports Collection (Gelecek için)
279
+
280
+ ```javascript
281
+ /reports/{reportId}
282
+ {
283
+ userId: "firebase-user-id",
284
+ summary: "Bug summary",
285
+ description: "Bug description",
286
+ application: "BiP",
287
+ platform: "android",
288
+ priority: "high",
289
+ createdAt: Timestamp,
290
+ updatedAt: Timestamp
291
+ }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## 🔐 Güvenlik Önerileri
297
+
298
+ 1. **API Key'leri Saklamayın**:
299
+ - `.gitignore` dosyasına `firebase-config.js` ekleyin
300
+ - Production'da environment variables kullanın
301
+
302
+ 2. **Güvenlik Kurallarını Sıkılaştırın**:
303
+ - Test mode'dan production mode'a geçin
304
+ - Sadece gerekli read/write izinleri verin
305
+
306
+ 3. **Email Verification Ekleyin**:
307
+ ```javascript
308
+ await user.sendEmailVerification();
309
+ ```
310
+
311
+ 4. **Rate Limiting**:
312
+ - Firebase App Check kullanın
313
+ - Çok fazla başarısız giriş denemelerini engelleyin
314
+
315
+ ---
316
+
317
+ ## ✅ Checklist
318
+
319
+ - [ ] Firebase projesi oluşturuldu
320
+ - [ ] Authentication aktifleştirildi
321
+ - [ ] Firestore aktifleştirildi
322
+ - [ ] `firebase-config.js` güncellendi
323
+ - [ ] Kayıt sayfası test edildi
324
+ - [ ] Login sayfası test edildi
325
+ - [ ] Şifre sıfırlama test edildi
326
+ - [ ] Firestore güvenlik kuralları güncellendi
327
+
328
+ ---
329
+
330
+ ## 📚 Ek Kaynaklar
331
+
332
+ - [Firebase Authentication Docs](https://firebase.google.com/docs/auth)
333
+ - [Firebase Firestore Docs](https://firebase.google.com/docs/firestore)
334
+ - [Firebase Security Rules](https://firebase.google.com/docs/rules)
335
+ - [Firebase Best Practices](https://firebase.google.com/docs/rules/rules-and-auth)
336
+
337
+ ---
338
+
339
+ **Yardıma mı ihtiyacınız var?** Firebase Console'da sağ altta bulunan "?" butonuna tıklayarak support alabilirsiniz.
340
+
341
+ 🎉 **Kurulum Tamamlandı!** Artık kullanıcılar Firebase ile kayıt olup giriş yapabilir!
342
+
FIREBASE_CACHE_QUICKSTART.md ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 Firebase Embedding Cache - Quick Start
2
+
3
+ ## ✨ Yeni Özellik: Embedding Cache Sistemi
4
+
5
+ Artık embedding'ler bir kere yapılıyor ve Firebase Storage'da kalıcı olarak saklanıyor!
6
+
7
+ ### 🎯 Öncesi vs Sonrası
8
+
9
+ | Önceki Durum | Yeni Durum |
10
+ |--------------|------------|
11
+ | Her login'de 10-15 saniye embedding ❌ | İlk seferde 10-15 saniye, sonra 2-3 saniye ✅ |
12
+ | Server restart → embedding kaybolur ❌ | Firebase'de kalıcı ✅ |
13
+ | Production'da kullanılamaz ❌ | Production-ready ✅ |
14
+
15
+ ---
16
+
17
+ ## 🔧 Local Development Setup
18
+
19
+ ### 1. Environment Variables
20
+
21
+ `env.example` dosyasını `.env` olarak kopyala:
22
+
23
+ ```bash
24
+ cp env.example .env
25
+ ```
26
+
27
+ ### 2. Firebase Service Account
28
+
29
+ 1. Firebase Console → Project Settings → Service Accounts
30
+ 2. Generate New Private Key → JSON indir
31
+ 3. `firebase-service-account.json` olarak kaydet
32
+ 4. `.env` dosyasında path'i ayarla:
33
+
34
+ ```bash
35
+ FIREBASE_SERVICE_ACCOUNT=./firebase-service-account.json
36
+ FIREBASE_STORAGE_BUCKET=jira-duplicate-detection.appspot.com
37
+ ```
38
+
39
+ ### 3. Dependencies Install
40
+
41
+ ```bash
42
+ pip install -r requirements.txt
43
+ ```
44
+
45
+ ### 4. Run Server
46
+
47
+ ```bash
48
+ python api_server.py
49
+ ```
50
+
51
+ Görmen gereken:
52
+ ```
53
+ 🔥 Firebase Configuration:
54
+ • Service Account: ./firebase-service-account.json
55
+ • Storage Bucket: jira-duplicate-detection.appspot.com
56
+ ✅ Firebase Storage initialized
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 📦 Nasıl Çalışıyor?
62
+
63
+ ### İlk Data Upload:
64
+
65
+ ```python
66
+ # 1. Kullanıcı veriyi upload ediyor
67
+ POST /api/upload_data
68
+
69
+ # 2. Backend embedding yapıyor (10-15 saniye)
70
+ UserEmbeddingPipeline.process()
71
+ ├─ embeddings.npy oluşturuluyor
72
+ ├─ faiss_index.bin oluşturuluyor
73
+ └─ metadata.json oluşturuluyor
74
+
75
+ # 3. Firebase Storage'a upload ediliyor
76
+ FirebaseStorageManager.upload_user_artifacts()
77
+ └─ user_embeddings/{userId}/
78
+ ├─ embeddings.npy
79
+ ├─ faiss_index.bin
80
+ ├─ metadata.json
81
+ └─ data.csv
82
+
83
+ # ✅ Kullanıcı search yapabiliyor
84
+ ```
85
+
86
+ ### Sonraki Login'ler:
87
+
88
+ ```python
89
+ # 1. Kullanıcı login yapıyor
90
+ # 2. Backend kontrol ediyor
91
+
92
+ if artifacts_exist_in_firebase:
93
+ # ✅ Firebase'den indir (2-3 saniye)
94
+ download_from_firebase()
95
+ return True # Embedding yapma!
96
+ else:
97
+ # ❌ Cache miss, yeni embedding yap
98
+ generate_embeddings()
99
+ upload_to_firebase()
100
+ ```
101
+
102
+ ---
103
+
104
+ ## 🧪 Test Senaryosu
105
+
106
+ ### Senaryo 1: Yeni Kullanıcı (Cache Miss)
107
+
108
+ ```bash
109
+ # 1. Yeni kullanıcı ile login yap
110
+ # 2. Data upload et
111
+ # 3. Log'lara bak:
112
+
113
+ 🔍 Checking Firebase Storage for cached embeddings...
114
+ 📝 No cached embeddings found, will generate new ones
115
+ 📊 Data shape: (15, 5)
116
+ 🧮 Creating embeddings...
117
+ ✅ Created embeddings: (15, 384)
118
+ 📤 Uploading embeddings to Firebase Storage...
119
+ ✅ Uploaded: embeddings.npy (45.6 KB)
120
+ ✅ Uploaded: faiss_index.bin (12.3 KB)
121
+ ✅ Embeddings cached to Firebase Storage!
122
+ ```
123
+
124
+ ### Senaryo 2: Aynı Kullanıcı (Cache Hit)
125
+
126
+ ```bash
127
+ # 1. Logout yap
128
+ # 2. Tekrar login yap
129
+ # 3. Arama yap
130
+ # 4. Log'lara bak:
131
+
132
+ 🔍 Checking Firebase Storage for cached embeddings...
133
+ ✅ Found cached embeddings in Firebase Storage!
134
+ 📥 Downloading artifacts for user xyz...
135
+ ✅ Downloaded: embeddings.npy (45.6 KB)
136
+ ✅ Downloaded: faiss_index.bin (12.3 KB)
137
+ ⏩ Skipping embedding generation - using cached version
138
+ ✅ Search completed in 0.5s (vs 15s without cache!)
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 🔒 Security
144
+
145
+ ### Firebase Storage Rules:
146
+
147
+ ```javascript
148
+ rules_version = '2';
149
+ service firebase.storage {
150
+ match /b/{bucket}/o {
151
+ match /user_embeddings/{userId}/{allPaths=**} {
152
+ // Her kullanıcı sadece kendi artifacts'ına erişebilir
153
+ allow read, write: if request.auth != null
154
+ && request.auth.uid == userId;
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## 🐛 Troubleshooting
163
+
164
+ ### "Firebase Storage not initialized"
165
+
166
+ `.env` dosyasını kontrol et:
167
+ ```bash
168
+ FIREBASE_SERVICE_ACCOUNT=./firebase-service-account.json
169
+ FIREBASE_STORAGE_BUCKET=your-project.appspot.com
170
+ ```
171
+
172
+ Service account JSON dosyası var mı?
173
+ ```bash
174
+ ls -la firebase-service-account.json
175
+ ```
176
+
177
+ ### Cache Kullanılmıyor
178
+
179
+ Logs'ta şunu gör:
180
+ ```
181
+ ⚠️ Firebase Storage not initialized, skipping download
182
+ ```
183
+
184
+ Çözüm:
185
+ ```bash
186
+ # .env dosyasını kontrol et
187
+ cat .env | grep FIREBASE
188
+
189
+ # Service account path doğru mu?
190
+ python -c "import os; print(os.path.exists('./firebase-service-account.json'))"
191
+ ```
192
+
193
+ ### Cache Devre Dışı Bırakma (Development)
194
+
195
+ ```bash
196
+ # .env dosyasında:
197
+ USE_FIREBASE_CACHE=False
198
+ ```
199
+
200
+ ---
201
+
202
+ ## 📊 Monitoring
203
+
204
+ ### Firebase Console:
205
+
206
+ Storage → `user_embeddings/` → Her kullanıcı için folder görünecek
207
+
208
+ ### Railway Logs:
209
+
210
+ ```
211
+ ✅ Embeddings cached to Firebase Storage! # Upload success
212
+ ⏩ Skipping embedding generation # Cache hit
213
+ 📥 Downloading artifacts # Cache download
214
+ ```
215
+
216
+ ---
217
+
218
+ ## 🚀 Production Deployment
219
+
220
+ Detaylı guide: [RAILWAY_DEPLOYMENT_GUIDE.md](./RAILWAY_DEPLOYMENT_GUIDE.md)
221
+
222
+ Kısaca:
223
+ 1. Railway projesi oluştur
224
+ 2. Environment variables ekle
225
+ 3. Service account upload et
226
+ 4. Deploy!
227
+
228
+ ---
229
+
230
+ ## 💡 Best Practices
231
+
232
+ ### 1. Cache Invalidation
233
+
234
+ Veri değiştiğinde cache otomatik güncellenir:
235
+ - Rapor eklendiğinde
236
+ - Rapor değiştirildiğinde
237
+ - Yeni data upload edildiğinde
238
+
239
+ ### 2. Storage Optimization
240
+
241
+ - Artifacts compress et (opsiyonel):
242
+ ```python
243
+ import gzip
244
+ with gzip.open('embeddings.npy.gz', 'wb') as f:
245
+ np.save(f, embeddings)
246
+ ```
247
+
248
+ ### 3. Cost Management
249
+
250
+ - Cache TTL ekle (örn: 30 gün sonra expire)
251
+ - Inactive user artifacts'ları temizle
252
+ - Compression kullan
253
+
254
+ ---
255
+
256
+ ## 📈 Performance Metrics
257
+
258
+ | Metrik | Öncesi | Sonrası | İyileşme |
259
+ |--------|--------|---------|----------|
260
+ | İlk embedding | 15s | 15s | - |
261
+ | Sonraki login'ler | 15s | 2-3s | **5x hızlı** |
262
+ | Server restart impact | Tüm cache kaybolur | Cache kalıcı | **∞x iyi** |
263
+ | User experience | Kötü | Mükemmel | ⭐⭐⭐⭐⭐ |
264
+
265
+ ---
266
+
267
+ ## ✅ Checklist
268
+
269
+ Aşağıdakileri kontrol et:
270
+
271
+ - [ ] `firebase-admin` ve `python-dotenv` kurulu
272
+ - [ ] `.env` dosyası oluşturuldu
273
+ - [ ] Firebase service account JSON indirildi
274
+ - [ ] Service account path `.env`'de doğru
275
+ - [ ] Storage bucket name doğru
276
+ - [ ] Firebase Storage Rules eklendi
277
+ - [ ] Local test: Cache miss → embedding yapılıyor
278
+ - [ ] Local test: Cache hit → embedding skip ediliyor
279
+ - [ ] Production deploy: Railway environment variables set edildi
280
+
281
+ ---
282
+
283
+ **🎉 Artık production-ready bir embedding cache sisteminiz var!**
284
+
285
+ Sorular için: GitHub Issues veya [RAILWAY_DEPLOYMENT_GUIDE.md](./RAILWAY_DEPLOYMENT_GUIDE.md)
286
+
FIREBASE_QUICK_START.md ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 Firebase Authentication - Hızlı Başlangıç
2
+
3
+ ## 5 Dakikada Firebase Kurulumu
4
+
5
+ ### 1️⃣ Firebase Projesi Oluştur (2 dakika)
6
+
7
+ 1. https://console.firebase.google.com/ → **Add Project**
8
+ 2. Proje adı: `bug-report-system`
9
+ 3. **Create Project**
10
+
11
+ ### 2️⃣ Authentication'ı Aktifleştir (1 dakika)
12
+
13
+ 1. Sol menü → **Authentication** → **Get Started**
14
+ 2. **Sign-in method** tab → **Email/Password** → **Enable** → **Save**
15
+
16
+ ### 3️⃣ Firestore'u Aktifleştir (1 dakika)
17
+
18
+ 1. Sol menü → **Firestore Database** → **Create database**
19
+ 2. **Test mode** seç → **eur3 (europe-west)** → **Enable**
20
+
21
+ ### 4️⃣ Config'i Kopyala (1 dakika)
22
+
23
+ 1. Proje ayarları (⚙️) → Scroll down → **Your apps** bölümünden **Web app** ekle
24
+ 2. Config kodunu kopyala:
25
+
26
+ ```javascript
27
+ const firebaseConfig = {
28
+ apiKey: "...",
29
+ authDomain: "...",
30
+ projectId: "...",
31
+ storageBucket: "...",
32
+ messagingSenderId: "...",
33
+ appId: "..."
34
+ };
35
+ ```
36
+
37
+ ### 5️⃣ Yapılandırmayı Güncelle (30 saniye)
38
+
39
+ `web/firebase-config.js` dosyasını aç ve config'i yapıştır:
40
+
41
+ ```javascript
42
+ const firebaseConfig = {
43
+ apiKey: "BURAYA_API_KEY", // ← Değiştir
44
+ authDomain: "BURAYA_AUTH_DOMAIN", // ← Değiştir
45
+ projectId: "BURAYA_PROJECT_ID", // ← Değiştir
46
+ storageBucket: "BURAYA_STORAGE", // ← Değiştir
47
+ messagingSenderId: "BURAYA_ID", // ← Değiştir
48
+ appId: "BURAYA_APP_ID" // ← Değiştir
49
+ };
50
+
51
+ firebase.initializeApp(firebaseConfig);
52
+ const auth = firebase.auth();
53
+ const db = firebase.firestore();
54
+ ```
55
+
56
+ ### ✅ Bitti!
57
+
58
+ Şimdi test et:
59
+ ```bash
60
+ # Web sayfalarını aç
61
+ open web/register.html # Kayıt sayfası
62
+ open web/login.html # Giriş sayfası
63
+ ```
64
+
65
+ ---
66
+
67
+ ## 🧪 Test
68
+
69
+ ### Yeni Kullanıcı Kaydı
70
+ 1. `register.html` aç
71
+ 2. Form doldur:
72
+ - Ad: `Test User`
73
+ - E-posta: `[email protected]`
74
+ - Şifre: `test123`
75
+ - Rol: `Admin`
76
+ 3. **Kayıt Ol** → Otomatik login'e yönlenecek
77
+
78
+ ### Giriş Yap
79
+ 1. `login.html` aç
80
+ 2. E-posta ve şifre gir
81
+ 3. **Giriş Yap** → Ana sayfaya yönlenecek
82
+
83
+ ---
84
+
85
+ ## 🔥 Firebase Console'da Kontrol
86
+
87
+ ### Kullanıcıları Görüntüle
88
+ 1. Firebase Console → **Authentication** → **Users** tab
89
+ 2. Kayıtlı kullanıcıları göreceksiniz
90
+
91
+ ### Firestore Verilerini Görüntüle
92
+ 1. Firebase Console → **Firestore Database** → **Data** tab
93
+ 2. `users` collection'ını açın
94
+ 3. Kullanıcı bilgilerini göreceksiniz
95
+
96
+ ---
97
+
98
+ ## ⚠️ Sorun mu Var?
99
+
100
+ ### "Firebase is not defined"
101
+ → Tarayıcı console'da bu hatayı görüyorsan:
102
+ - Sayfayı **hard refresh** yap (Ctrl+Shift+R veya Cmd+Shift+R)
103
+ - `firebase-config.js` dosyasının yüklendiğinden emin ol
104
+
105
+ ### "Permission denied" (Firestore)
106
+ → Firestore güvenlik kurallarını güncelle:
107
+ ```javascript
108
+ rules_version = '2';
109
+ service cloud.firestore {
110
+ match /databases/{database}/documents {
111
+ match /{document=**} {
112
+ allow read, write: if request.auth != null;
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### "Email already in use"
119
+ → Bu e-posta ile zaten kayıt olunmuş. Farklı e-posta dene veya login yap.
120
+
121
+ ---
122
+
123
+ ## 💡 Demo Mod
124
+
125
+ Firebase yapılandırmazsan **Demo Mod** otomatik çalışır:
126
+
127
+ **Demo Hesap**:
128
+ - E-posta: `[email protected]`
129
+ - Şifre: `demo123`
130
+
131
+ Demo mod'da:
132
+ - ✅ Giriş yapabilirsin
133
+ - ❌ Yeni kullanıcı kaydedemezsin
134
+ - ❌ Şifre sıfırlayamazsın
135
+
136
+ ---
137
+
138
+ ## 📁 Dosya Yapısı
139
+
140
+ ```
141
+ web/
142
+ ├── firebase-config.js ← Firebase ayarları (GÜNCELLE)
143
+ ├── register.html ← Kayıt sayfası
144
+ ├── login.html ← Giriş sayfası
145
+ └── ...
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 🎯 Sonraki Adımlar
151
+
152
+ 1. **Güvenlik Kurallarını Sıkılaştır**: Production'da test mode kullanma!
153
+ 2. **Email Verification Ekle**: Kullanıcılar e-postalarını doğrulasın
154
+ 3. **Admin Panel**: Kullanıcı yönetimi için admin paneli ekle
155
+ 4. **Roles & Permissions**: Farklı roller için yetkilendirme ekle
156
+
157
+ ---
158
+
159
+ ## 📚 Daha Fazla Bilgi
160
+
161
+ Detaylı kurulum için: **FIREBASE_AUTH_SETUP.md**
162
+
163
+ ---
164
+
165
+ **🎉 Başarılar!** Sorularınız için: Firebase Console → Support
166
+
Procfile ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ web: python api_server.py
2
+
RAILWAY_DEPLOYMENT_GUIDE.md ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚂 Railway + Firebase Deployment Guide
2
+
3
+ ## 📋 Overview
4
+
5
+ Bu sistem şu şekilde çalışır:
6
+ 1. **Railway** → Backend (Flask API) hosting
7
+ 2. **Firebase** → Authentication + Firestore + **Storage (Embedding Cache)**
8
+ 3. **Embedding Cache** → Her kullanıcı için embedding bir kere yapılır, Firebase Storage'da saklanır
9
+
10
+ ---
11
+
12
+ ## 🎯 Neden Bu Sistem?
13
+
14
+ ### ❌ Önceki Durum:
15
+ - Her login'de embedding yapılıyor (10-15 saniye) ❌
16
+ - Server restart olunca embedding'ler kayboluyordu ❌
17
+ - Production'da bu sürdürülebilir değil ❌
18
+
19
+ ### ✅ Yeni Sistem:
20
+ - Embedding **bir kere** yapılır (ilk data upload'da) ✅
21
+ - Firebase Storage'da saklanır (kalıcı) ✅
22
+ - Sonraki login'lerde Firebase'den indirilir (2-3 saniye) ✅
23
+ - Server restart olsa bile embedding'ler kaybolmaz ✅
24
+
25
+ ---
26
+
27
+ ## 🔥 Firebase Setup
28
+
29
+ ### 1. Firebase Storage Aktif Et
30
+
31
+ 1. Firebase Console → **Storage** → **Get Started**
32
+ 2. **Production mode** seç (security rules ekleyeceğiz)
33
+ 3. **Storage location** seç (örn: `europe-west3`)
34
+
35
+ ### 2. Storage Security Rules
36
+
37
+ Firebase Console → Storage → **Rules** → Aşağıdaki kuralları ekle:
38
+
39
+ ```javascript
40
+ rules_version = '2';
41
+ service firebase.storage {
42
+ match /b/{bucket}/o {
43
+ // User-specific embeddings
44
+ match /user_embeddings/{userId}/{allPaths=**} {
45
+ // Sadece kendi embedding'lerini okuyabilir/yazabilir
46
+ allow read, write: if request.auth != null && request.auth.uid == userId;
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### 3. Firebase Service Account
53
+
54
+ 1. Firebase Console → **Project Settings** → **Service Accounts**
55
+ 2. **Generate New Private Key** → JSON dosyasını indir
56
+ 3. Dosyayı `firebase-service-account.json` olarak kaydet
57
+ 4. **GİTİGNORE'A EKLE** (public etme!)
58
+
59
+ ---
60
+
61
+ ## 🚂 Railway Deployment
62
+
63
+ ### 1. Railway Projesi Oluştur
64
+
65
+ 1. [Railway.app](https://railway.app) → **New Project** → **Deploy from GitHub repo**
66
+ 2. Repository'yi seç: `bug-deduplication-github`
67
+ 3. Railway otomatik detect edecek (`requirements.txt` var)
68
+
69
+ ### 2. Environment Variables
70
+
71
+ Railway Dashboard → **Variables** → Şu değişkenleri ekle:
72
+
73
+ ```bash
74
+ # Firebase Configuration
75
+ FIREBASE_SERVICE_ACCOUNT=/app/firebase-service-account.json
76
+ FIREBASE_STORAGE_BUCKET=jira-duplicate-detection.appspot.com
77
+
78
+ # Flask Configuration
79
+ FLASK_ENV=production
80
+ FLASK_DEBUG=False
81
+
82
+ # CORS
83
+ ALLOWED_ORIGINS=https://jira-duplicate-detection.web.app,https://jira-duplicate-detection.firebaseapp.com
84
+
85
+ # Feature Flags
86
+ USE_FIREBASE_CACHE=True
87
+ ```
88
+
89
+ ### 3. Firebase Service Account Upload
90
+
91
+ Railway'de service account JSON dosyasını eklemek için 2 yöntem:
92
+
93
+ #### Yöntem 1: Base64 Encoding (Önerilen)
94
+ ```bash
95
+ # Local'de çalıştır:
96
+ cat firebase-service-account.json | base64 > firebase-service-account-base64.txt
97
+ ```
98
+
99
+ Railway Variables:
100
+ ```bash
101
+ FIREBASE_SERVICE_ACCOUNT_BASE64=<base64 string buraya yapıştır>
102
+ ```
103
+
104
+ `api_server.py`'ye ekle:
105
+ ```python
106
+ # Decode base64 and save
107
+ import base64
108
+ if os.getenv('FIREBASE_SERVICE_ACCOUNT_BASE64'):
109
+ with open('/tmp/firebase-service-account.json', 'w') as f:
110
+ decoded = base64.b64decode(os.getenv('FIREBASE_SERVICE_ACCOUNT_BASE64'))
111
+ f.write(decoded.decode())
112
+ os.environ['FIREBASE_SERVICE_ACCOUNT'] = '/tmp/firebase-service-account.json'
113
+ ```
114
+
115
+ #### Yöntem 2: Railway Volumes
116
+ 1. Railway → **Settings** → **Volumes**
117
+ 2. Mount Path: `/app/secrets`
118
+ 3. Service account'u `/app/secrets/firebase-service-account.json` olarak upload et
119
+
120
+ ### 4. Deploy
121
+
122
+ ```bash
123
+ git add .
124
+ git commit -m "Railway + Firebase deployment setup"
125
+ git push origin main
126
+ ```
127
+
128
+ Railway otomatik deploy edecek!
129
+
130
+ ---
131
+
132
+ ## 🌐 Frontend Configuration
133
+
134
+ ### 1. API Base URL Güncelle
135
+
136
+ `web/app.js` ve `web/create_report.js`:
137
+
138
+ ```javascript
139
+ // Production
140
+ const API_BASE_URL = 'https://your-app.railway.app/api';
141
+
142
+ // Veya environment detection:
143
+ const API_BASE_URL = window.location.hostname === 'localhost'
144
+ ? 'http://localhost:5001/api'
145
+ : 'https://your-app.railway.app/api';
146
+ ```
147
+
148
+ ### 2. Firebase Hosting Deploy
149
+
150
+ ```bash
151
+ firebase deploy --only hosting
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 🎯 Cache System Nasıl Çalışır?
157
+
158
+ ### İlk Data Upload (Kullanıcı veriyi ilk kez yüklüyor):
159
+
160
+ ```
161
+ 1. User uploads data.csv
162
+ 2. Backend embedding yapıyor (10-15 saniye)
163
+ ├─ embeddings.npy oluşturuluyor
164
+ ├─ faiss_index.bin oluşturuluyor
165
+ └─ metadata.json oluşturuluyor
166
+ 3. Firebase Storage'a upload ediliyor 📤
167
+ └─ user_embeddings/{userId}/
168
+ ├─ embeddings.npy
169
+ ├─ faiss_index.bin
170
+ ├─ metadata.json
171
+ └─ data.csv
172
+ 4. Kullanıcı search yapabiliyor ✅
173
+ ```
174
+
175
+ ### Sonraki Login'ler (Cache hit):
176
+
177
+ ```
178
+ 1. User login yapıyor
179
+ 2. Backend Firebase Storage'ı kontrol ediyor
180
+ 3. Artifacts bulunuyor! 📥
181
+ 4. Firebase'den indiriliy or (2-3 saniye)
182
+ 5. Local'e kaydediliyor
183
+ 6. Embedding yapılmıyor! ⏩ Cached version kullanılıyor
184
+ 7. Kullanıcı HEMEN search yapabiliyor ✅
185
+ ```
186
+
187
+ ### Veri Değiştiğinde:
188
+
189
+ ```
190
+ 1. User rapor ekliyor/değiştiriyor
191
+ 2. Backend embedding yeniliyor (10-15 saniye)
192
+ 3. Yeni artifacts Firebase'e upload ediliyor 📤
193
+ 4. Cache güncelleniyor ✅
194
+ ```
195
+
196
+ ---
197
+
198
+ ## 📊 Monitoring
199
+
200
+ ### Railway Logs
201
+
202
+ Railway Dashboard → **Logs** → Şunları göreceksin:
203
+
204
+ ```
205
+ ✅ Firebase Storage initialized
206
+ 🔍 Checking Firebase Storage for cached embeddings...
207
+ ✅ Found cached embeddings in Firebase Storage!
208
+ 📥 Downloading artifacts for user xyz...
209
+ ✅ Downloaded: embeddings.npy (1.2 MB)
210
+ ✅ Downloaded: faiss_index.bin (856 KB)
211
+ ⏩ Skipping embedding generation - using cached version
212
+ ```
213
+
214
+ ### Firebase Storage
215
+
216
+ Firebase Console → **Storage** → Göreceksin:
217
+
218
+ ```
219
+ user_embeddings/
220
+ ├─ P8VtJgvODiXSbjOtCMqUTecivHR2/
221
+ │ ├─ embeddings.npy (1.2 MB)
222
+ │ ├─ faiss_index.bin (856 KB)
223
+ │ ├─ metadata.json (2 KB)
224
+ │ └─ data.csv (45 KB)
225
+ └─ another-user-id/
226
+ └─ ...
227
+ ```
228
+
229
+ ---
230
+
231
+ ## 🐛 Troubleshooting
232
+
233
+ ### "Firebase Storage not initialized"
234
+
235
+ ```bash
236
+ # Railway Variables kontrol et:
237
+ FIREBASE_SERVICE_ACCOUNT=/app/firebase-service-account.json
238
+ FIREBASE_STORAGE_BUCKET=your-project.appspot.com
239
+ ```
240
+
241
+ ### "Permission denied" hatası
242
+
243
+ Firebase Storage Rules'u kontrol et:
244
+ ```javascript
245
+ allow read, write: if request.auth != null && request.auth.uid == userId;
246
+ ```
247
+
248
+ ### Embedding yapılmaya devam ediyor
249
+
250
+ Logs'a bak:
251
+ ```
252
+ 🔍 Checking Firebase Storage for cached embeddings...
253
+ 📝 No cached embeddings found, will generate new ones
254
+ ```
255
+
256
+ Firebase Storage'da artifacts var mı kontrol et.
257
+
258
+ ---
259
+
260
+ ## 💰 Cost Estimation
261
+
262
+ ### Railway:
263
+ - **Starter Plan**: $5/month (500 hours)
264
+ - **Hobby Plan**: Free (500 hours/month, enough for development)
265
+
266
+ ### Firebase Storage:
267
+ - **Spark Plan (Free)**: 1 GB storage, 1 GB/day download
268
+ - **Blaze Plan (Pay as you go)**:
269
+ - Storage: $0.026/GB/month
270
+ - Download: $0.12/GB
271
+
272
+ **Örnek:** 100 kullanıcı, her biri ~2MB embedding:
273
+ - Storage: 200 MB = ~$0.005/month
274
+ - Download (her kullanıcı ayda 10 login): 100 * 2MB * 10 = 2GB = ~$0.24/month
275
+
276
+ **TOPLAM: ~$5-10/month** 🎉
277
+
278
+ ---
279
+
280
+ ## ✅ Checklist
281
+
282
+ - [ ] Firebase Storage aktif
283
+ - [ ] Storage security rules eklendi
284
+ - [ ] Service account JSON indirildi
285
+ - [ ] Railway projesi oluşturuldu
286
+ - [ ] Environment variables eklendi
287
+ - [ ] Service account Railway'e upload edildi
288
+ - [ ] Frontend API URL güncellendi
289
+ - [ ] Firebase Hosting deploy edildi
290
+ - [ ] Test: Yeni kullanıcı ile embedding yapıldı ve cache'lendi
291
+ - [ ] Test: Aynı kullanıcı logout/login → cache'ten yüklendi
292
+
293
+ ---
294
+
295
+ ## 🚀 Next Steps
296
+
297
+ 1. **Incremental Updates**: Sadece değişen row'ların embedding'ini güncelle
298
+ 2. **Compression**: Artifacts'ları compress et (gzip)
299
+ 3. **CDN**: Firebase Storage CDN kullan
300
+ 4. **Monitoring**: Sentry/LogRocket ekle
301
+ 5. **Analytics**: Embedding cache hit rate track et
302
+
303
+ ---
304
+
305
+ ## 📞 Support
306
+
307
+ Sorular için: [GitHub Issues](https://github.com/your-repo/issues)
308
+
309
+ ---
310
+
311
+ **🎉 Artık production-ready bir sistem var!**
312
+
USER_BASED_EMBEDDING_SYSTEM.md ADDED
@@ -0,0 +1,484 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔐 Kullanıcı Bazlı Embedding Sistemi
2
+
3
+ ## 🎯 Sistem Mimarisi
4
+
5
+ ### Eski Sistem (❌ Artık Kullanılmıyor)
6
+ ```
7
+ Turkcell Genel Verileri
8
+
9
+ Tek Embedding Set
10
+
11
+ Tüm Kullanıcılar Aynı Veriyi Kullanıyor
12
+ ```
13
+
14
+ **Sorunlar:**
15
+ - ❌ Kullanıcılar birbirlerinin verilerini görebiliyordu
16
+ - ❌ Her kullanıcı için aynı sonuçlar
17
+ - ❌ Veri izolasyonu yok
18
+ - ❌ Privacy sorunu
19
+
20
+ ---
21
+
22
+ ### Yeni Sistem (✅ Aktif)
23
+ ```
24
+ Kullanıcı A → Kendi CSV'si → Kendi Embeddings → Kendi FAISS Index → İzole Arama
25
+ Kullanıcı B → Kendi CSV'si → Kendi Embeddings → Kendi FAISS Index → İzole Arama
26
+ Kullanıcı C → Kendi CSV'si → Kendi Embeddings → Kendi FAISS Index → İzole Arama
27
+ ```
28
+
29
+ **Avantajlar:**
30
+ - ✅ Tam veri izolasyonu
31
+ - ✅ Her kullanıcı sadece kendi verisini görür
32
+ - ✅ Privacy garantisi
33
+ - ✅ Özelleştirilebilir arama
34
+ - ✅ Hızlı hybrid search (FAISS + Cross-Encoder)
35
+
36
+ ---
37
+
38
+ ## 📊 Veri Yapısı
39
+
40
+ ### Dosya Sistemi
41
+ ```
42
+ data/
43
+ ├── user_embeddings/
44
+ │ ├── user_abc123/ # Firebase User ID
45
+ │ │ ├── embeddings.npy # Embedding vectors (N x 384)
46
+ │ │ ├── faiss_index.index # FAISS search index
47
+ │ │ └── metadata.json # Metadata (columns, config, etc.)
48
+ │ │
49
+ │ ├── user_xyz456/ # Başka bir kullanıcı
50
+ │ │ ├── embeddings.npy
51
+ │ │ ├── faiss_index.index
52
+ │ │ └── metadata.json
53
+ │ │
54
+ │ └── user_turkcell789/
55
+ │ ├── embeddings.npy
56
+ │ ├── faiss_index.index
57
+ │ └── metadata.json
58
+
59
+ └── user_data/
60
+ ├── user_abc123/
61
+ │ └── my_bugs.csv # User's original CSV
62
+ └── user_xyz456/
63
+ └── issues.csv
64
+ ```
65
+
66
+ ### Metadata Yapısı (`metadata.json`)
67
+ ```json
68
+ {
69
+ "user_id": "user_abc123",
70
+ "num_records": 1500,
71
+ "num_columns": 12,
72
+ "columns": ["Summary", "Description", "Priority", "Platform", ...],
73
+ "text_columns": ["Summary", "Description"],
74
+ "model_name": "paraphrase-multilingual-MiniLM-L12-v2",
75
+ "embedding_dim": 384,
76
+ "created_at": "2025-01-15T14:30:00",
77
+ "config": {
78
+ "fileName": "my_bugs.csv"
79
+ }
80
+ }
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 🔄 İşlem Akışı
86
+
87
+ ### 1. Veri Yükleme ve Embedding Oluşturma
88
+
89
+ ```
90
+ Kullanıcı CSV Yükler (web/data_upload.html)
91
+
92
+ Frontend: userId gönderilir
93
+
94
+ Backend: /api/upload_data endpoint
95
+
96
+ DataFrame oluşturulur
97
+
98
+ UserEmbeddingPipeline çalışır
99
+
100
+ 1. Text columns tespit edilir
101
+ 2. Bi-Encoder ile embeddings oluşturulur (384 dim)
102
+ 3. FAISS index oluşturulur (cosine similarity)
103
+ 4. Metadata kaydedilir
104
+
105
+ ✅ Kullanıcıya özel embedding seti hazır!
106
+ ```
107
+
108
+ **Log Örneği:**
109
+ ```
110
+ 📥 Upload request from user: user_abc123
111
+ 🔄 Creating embeddings for user: user_abc123
112
+ 📊 Data shape: (1500, 12)
113
+ 📝 Text columns: ['Summary', 'Description']
114
+ 🤖 Generating embeddings...
115
+ ✅ Embeddings created: (1500, 384)
116
+ 🔄 Creating FAISS index...
117
+ ✅ FAISS index created: 1500 vectors
118
+ 💾 Saved embeddings to: data/user_embeddings/user_abc123/embeddings.npy
119
+ ✅ Pipeline completed successfully!
120
+ ```
121
+
122
+ ---
123
+
124
+ ### 2. Arama (Hybrid Search)
125
+
126
+ ```
127
+ Kullanıcı Arama Yapar (web/index.html)
128
+
129
+ Backend: /api/search endpoint
130
+
131
+ custom_data_store'dan userId alınır
132
+
133
+ UserHybridSearch başlatılır
134
+
135
+ STAGE 1: FAISS Search
136
+ - Query encode edilir
137
+ - Top 50 candidate bulunur (hızlı)
138
+
139
+ STAGE 2: Cross-Encoder Re-ranking
140
+ - 50 candidate hassas skorlanır
141
+ - Final top 10 seçilir
142
+
143
+ ✅ Sonuçlar kullanıcıya döner
144
+ ```
145
+
146
+ **Log Örneği:**
147
+ ```
148
+ 🔍 Search request from user: user_abc123
149
+ 🚀 Using Hybrid Search with embeddings
150
+ 🔄 Encoding query...
151
+ 🔍 FAISS search (top 50 candidates)...
152
+ ✅ Found 50 candidates from FAISS
153
+ 🔄 Cross-Encoder re-ranking...
154
+ ✅ Re-ranking completed
155
+ ✅ Returning 10 results
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 🧪 Kullanım Örnekleri
161
+
162
+ ### Python'dan Kullanım
163
+
164
+ #### 1. Embedding Oluşturma
165
+ ```python
166
+ from src.user_embedding_pipeline import create_user_embeddings
167
+ import pandas as pd
168
+
169
+ # Veriyi yükle
170
+ df = pd.read_csv('my_data.csv')
171
+
172
+ # Embedding oluştur
173
+ success = create_user_embeddings(
174
+ user_id='user_abc123',
175
+ df=df,
176
+ text_columns=['Summary', 'Description'], # Optional
177
+ config={'fileName': 'my_data.csv'}
178
+ )
179
+
180
+ if success:
181
+ print("✅ Embeddings created!")
182
+ ```
183
+
184
+ #### 2. Arama Yapma
185
+ ```python
186
+ from src.user_hybrid_search import search_user_data
187
+ import pandas as pd
188
+
189
+ # Veriyi yükle
190
+ df = pd.read_csv('my_data.csv')
191
+
192
+ # Arama yap
193
+ results = search_user_data(
194
+ user_id='user_abc123',
195
+ query='Uygulama çöküyor',
196
+ df=df,
197
+ top_k=10
198
+ )
199
+
200
+ for i, result in enumerate(results, 1):
201
+ print(f"{i}. {result['summary']} (Score: {result['final_score']:.4f})")
202
+ ```
203
+
204
+ ---
205
+
206
+ ### Frontend'den Kullanım
207
+
208
+ #### 1. Veri Yükleme
209
+ ```javascript
210
+ // web/data_upload.html
211
+
212
+ const userSession = JSON.parse(localStorage.getItem('userSession'));
213
+ const userId = userSession.uid || userSession.username;
214
+
215
+ const response = await fetch('http://localhost:5001/api/upload_data', {
216
+ method: 'POST',
217
+ headers: { 'Content-Type': 'application/json' },
218
+ body: JSON.stringify({
219
+ data: csvData, // Array of objects
220
+ fileName: 'my_bugs.csv',
221
+ userId: userId, // ⚠️ CRITICAL!
222
+ textColumns: ['Summary', 'Description'] // Optional
223
+ })
224
+ });
225
+
226
+ const result = await response.json();
227
+ console.log('Embeddings created:', result.info.embeddingsCreated);
228
+ ```
229
+
230
+ #### 2. Arama Yapma
231
+ ```javascript
232
+ // web/index.html (app.js)
233
+
234
+ const response = await fetch('http://localhost:5001/api/search', {
235
+ method: 'POST',
236
+ headers: { 'Content-Type': 'application/json' },
237
+ body: JSON.stringify({
238
+ query: 'Uygulama çöküyor',
239
+ top_k: 10
240
+ })
241
+ });
242
+
243
+ const data = await response.json();
244
+ // Kullanıcının kendi verisi içinde arama yapıldı!
245
+ ```
246
+
247
+ ---
248
+
249
+ ## 🔐 Güvenlik ve İzolasyon
250
+
251
+ ### Veri İzolasyonu
252
+ ```python
253
+ # Kullanıcı A'nın verileri
254
+ /data/user_embeddings/user_abc123/
255
+
256
+ # Kullanıcı B'nin verileri
257
+ /data/user_embeddings/user_xyz456/
258
+
259
+ # ❌ Kullanıcı A, Kullanıcı B'nin verilerini GÖREMEZ!
260
+ ```
261
+
262
+ ### Firebase Authentication
263
+ ```javascript
264
+ // Frontend'de user ID Firebase'den alınır
265
+ const user = firebase.auth().currentUser;
266
+ const userId = user.uid; // Unique ve güvenli
267
+ ```
268
+
269
+ ### Backend Validation
270
+ ```python
271
+ # Her request'te userId kontrol edilir
272
+ user_id = custom_data_store.get('userId', 'anonymous')
273
+
274
+ # Sadece o kullanıcının embeddings'i yüklenir
275
+ user_dir = Path(f"data/user_embeddings/{user_id}")
276
+ ```
277
+
278
+ ---
279
+
280
+ ## ⚙️ Konfigürasyon
281
+
282
+ ### Embedding Modeli
283
+ ```python
284
+ # src/user_embedding_pipeline.py
285
+
286
+ model_name = 'paraphrase-multilingual-MiniLM-L12-v2'
287
+ # - 384 dimensions
288
+ # - Multilingual (Turkish support)
289
+ # - Fast (50ms per text)
290
+ ```
291
+
292
+ ### FAISS Index
293
+ ```python
294
+ # Cosine Similarity için normalize edilmiş Inner Product
295
+ index = faiss.IndexFlatIP(embedding_dim)
296
+
297
+ # Normalize edilmiş embeddings
298
+ normalized = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
299
+ index.add(normalized.astype('float32'))
300
+ ```
301
+
302
+ ### Cross-Encoder
303
+ ```python
304
+ model_name = 'cross-encoder/mmarco-mMiniLMv2-L12-H384-v1'
305
+ # - Multilingual
306
+ # - High accuracy
307
+ # - Slower (200ms per pair, but only for top-K)
308
+ ```
309
+
310
+ ---
311
+
312
+ ## 📈 Performans
313
+
314
+ ### Embedding Oluşturma
315
+ | Veri Boyutu | Süre | Disk Kullanımı |
316
+ |-------------|------|----------------|
317
+ | 1,000 satır | ~30 saniye | ~1.5 MB |
318
+ | 5,000 satır | ~2 dakika | ~7.5 MB |
319
+ | 10,000 satır | ~4 dakika | ~15 MB |
320
+
321
+ ### Arama Hızı
322
+ | Aşama | Süre |
323
+ |-------|------|
324
+ | Query encoding | ~50ms |
325
+ | FAISS search (top 50) | ~5ms |
326
+ | Cross-encoder (50 → 10) | ~200ms |
327
+ | **Total** | **~250ms** |
328
+
329
+ ---
330
+
331
+ ## 🚀 Deployment
332
+
333
+ ### Gereksinimler
334
+ ```txt
335
+ # requirements.txt
336
+ sentence-transformers>=2.2.0
337
+ faiss-cpu>=1.7.4 # veya faiss-gpu
338
+ pandas>=1.5.0
339
+ numpy>=1.24.0
340
+ ```
341
+
342
+ ### Kurulum
343
+ ```bash
344
+ # Dependencies yükle
345
+ pip install -r requirements.txt
346
+
347
+ # Backend'i başlat
348
+ python api_server.py
349
+ ```
350
+
351
+ ---
352
+
353
+ ## 🧪 Test
354
+
355
+ ### Unit Test
356
+ ```bash
357
+ # Embedding pipeline testi
358
+ python src/user_embedding_pipeline.py
359
+ ```
360
+
361
+ ### Integration Test
362
+ ```bash
363
+ # 1. Test verisi yükle
364
+ curl -X POST http://localhost:5001/api/upload_data \
365
+ -H "Content-Type: application/json" \
366
+ -d @test_data.json
367
+
368
+ # 2. Arama yap
369
+ curl -X POST http://localhost:5001/api/search \
370
+ -H "Content-Type: application/json" \
371
+ -d '{"query": "test bug", "top_k": 5}'
372
+ ```
373
+
374
+ ---
375
+
376
+ ## 🐛 Sorun Giderme
377
+
378
+ ### Embedding Oluşturulamıyor
379
+ ```bash
380
+ # Log'ları kontrol et
381
+ ❌ Embedding creation error: ...
382
+
383
+ # Çözüm 1: Model yükleme sorunu
384
+ pip install --upgrade sentence-transformers
385
+
386
+ # Çözüm 2: Bellek yetersiz
387
+ # Batch size'ı küçült (pipeline.py → batch_size=16)
388
+ ```
389
+
390
+ ### Arama Sonuç Döndürmüyor
391
+ ```bash
392
+ # User embeddings var mı kontrol et
393
+ ls -la data/user_embeddings/USER_ID/
394
+
395
+ # Olmalı:
396
+ # - embeddings.npy
397
+ # - faiss_index.index
398
+ # - metadata.json
399
+ ```
400
+
401
+ ### "User embeddings not found" Hatası
402
+ ```python
403
+ # Çözüm: Embeddings'i yeniden oluştur
404
+ from src.user_embedding_pipeline import create_user_embeddings
405
+ create_user_embeddings(user_id, df)
406
+ ```
407
+
408
+ ---
409
+
410
+ ## 📚 API Referansı
411
+
412
+ ### POST `/api/upload_data`
413
+ **Request:**
414
+ ```json
415
+ {
416
+ "data": [...],
417
+ "fileName": "bugs.csv",
418
+ "userId": "user_abc123", // REQUIRED
419
+ "textColumns": ["Summary", "Description"] // Optional
420
+ }
421
+ ```
422
+
423
+ **Response:**
424
+ ```json
425
+ {
426
+ "success": true,
427
+ "message": "Data uploaded and embeddings created",
428
+ "info": {
429
+ "fileName": "bugs.csv",
430
+ "rowCount": 1500,
431
+ "embeddingsCreated": true,
432
+ "embeddingsPath": "data/user_embeddings/user_abc123"
433
+ }
434
+ }
435
+ ```
436
+
437
+ ### POST `/api/search`
438
+ **Request:**
439
+ ```json
440
+ {
441
+ "query": "Uygulama çöküyor",
442
+ "top_k": 10
443
+ }
444
+ ```
445
+
446
+ **Response:**
447
+ ```json
448
+ {
449
+ "success": true,
450
+ "results": [
451
+ {
452
+ "index": 42,
453
+ "summary": "App crashes on startup",
454
+ "final_score": 8.4523,
455
+ "cross_encoder_score": 8.4523,
456
+ "faiss_score": 0.9234,
457
+ ...
458
+ }
459
+ ],
460
+ "count": 10,
461
+ "search_time": 0.25
462
+ }
463
+ ```
464
+
465
+ ---
466
+
467
+ ## ✅ Özet
468
+
469
+ | Özellik | Durum |
470
+ |---------|-------|
471
+ | Kullanıcı izolasyonu | ✅ Aktif |
472
+ | Embedding oluşturma | ✅ Otomatik |
473
+ | Hybrid search | ✅ FAISS + Cross-Encoder |
474
+ | Privacy | ✅ Garantili |
475
+ | Performans | ✅ <300ms |
476
+ | Firebase entegrasyonu | ✅ Tam |
477
+ | Multi-user desteği | ✅ Sınırsız |
478
+
479
+ ---
480
+
481
+ **Son Güncelleme**: 2025-01-16
482
+ **Versiyon**: 2.0.0
483
+ **Durum**: ✅ Production Ready
484
+
WEB_AUTH_FEATURES.md ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔐 Kullanıcı Kimlik Doğrulama Sistemi
2
+
3
+ ## ✨ Özellikler
4
+
5
+ ### 🎯 Temel Özellikler
6
+ - ✅ **Firebase Authentication** ile güvenli kimlik doğrulama
7
+ - ✅ **Kullanıcı Kayıt** sistemi (register.html)
8
+ - ✅ **Kullanıcı Girişi** (login.html)
9
+ - ✅ **Şifre Sıfırlama** özelliği
10
+ - ✅ **Şifre Gücü Göstergesi** (kayıt sırasında)
11
+ - ✅ **Demo Mod** (Firebase yapılandırılmamışsa)
12
+ - ✅ **Firestore** ile kullanıcı verilerini saklama
13
+ - ✅ **Rol Bazlı Yetkilendirme** (Admin, User, Developer, Tester)
14
+
15
+ ---
16
+
17
+ ## 📱 Sayfalar
18
+
19
+ ### 1. register.html - Kayıt Sayfası
20
+ **Özellikler**:
21
+ - 👤 Ad Soyad girişi
22
+ - 📧 E-posta doğrulama
23
+ - 🔒 Güvenli şifre oluşturma
24
+ - 📊 Şifre gücü göstergesi (Zayıf/Orta/Güçlü)
25
+ - 🎭 Rol seçimi
26
+ - ✅ Gerçek zamanlı form doğrulama
27
+ - 🎨 Modern animasyonlu UI
28
+
29
+ **Validasyonlar**:
30
+ - Ad Soyad: Minimum 3 karakter
31
+ - E-posta: Geçerli format (@turkcell.com.tr)
32
+ - Şifre: Minimum 6 karakter
33
+ - Şifre eşleşmesi kontrolü
34
+ - Rol seçimi zorunlu
35
+
36
+ **Kullanım**:
37
+ ```
38
+ 1. Ad Soyad gir
39
+ 2. E-posta gir
40
+ 3. Şifre belirle (güç göstergesi yeşil olmalı)
41
+ 4. Şifreyi tekrar gir
42
+ 5. Rol seç
43
+ 6. "Kayıt Ol" butonuna tıkla
44
+ 7. ✅ Otomatik login sayfasına yönlendirilir
45
+ ```
46
+
47
+ ---
48
+
49
+ ### 2. login.html - Giriş Sayfası
50
+ **Özellikler**:
51
+ - 📧 E-posta ile giriş
52
+ - 🔒 Şifre ile kimlik doğrulama
53
+ - 🔄 Şifremi unuttum linki
54
+ - 💡 Demo mod desteği
55
+ - 🎨 Modern animasyonlu arka plan
56
+
57
+ **Kullanım**:
58
+ ```
59
+ 1. E-posta gir
60
+ 2. Şifre gir
61
+ 3. "Giriş Yap" butonuna tıkla
62
+ 4. ✅ Ana sayfaya yönlendirilir
63
+ ```
64
+
65
+ **Demo Hesaplar** (Firebase yapılandırılmamışsa):
66
+ - `[email protected]` / `demo123`
67
+ - `[email protected]` / `admin123`
68
+
69
+ ---
70
+
71
+ ## 🔥 Firebase Entegrasyonu
72
+
73
+ ### Kullanılan Firebase Servisleri
74
+
75
+ #### 1. Firebase Authentication
76
+ ```javascript
77
+ // Kullanıcı kaydı
78
+ await auth.createUserWithEmailAndPassword(email, password);
79
+
80
+ // Kullanıcı girişi
81
+ await auth.signInWithEmailAndPassword(email, password);
82
+
83
+ // Şifre sıfırlama
84
+ await auth.sendPasswordResetEmail(email);
85
+
86
+ // Çıkış
87
+ await auth.signOut();
88
+ ```
89
+
90
+ #### 2. Firebase Firestore
91
+ ```javascript
92
+ // Kullanıcı verisi kaydetme
93
+ await db.collection('users').doc(userId).set({
94
+ uid: userId,
95
+ email: email,
96
+ fullName: name,
97
+ role: role,
98
+ createdAt: serverTimestamp(),
99
+ lastLogin: serverTimestamp()
100
+ });
101
+
102
+ // Kullanıcı verisi okuma
103
+ const userDoc = await db.collection('users').doc(userId).get();
104
+ const userData = userDoc.data();
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 🗄️ Veri Yapısı
110
+
111
+ ### LocalStorage
112
+ ```javascript
113
+ {
114
+ "userSession": {
115
+ "uid": "firebase-user-id",
116
+ "email": "[email protected]",
117
+ "username": "[email protected]",
118
+ "name": "Ahmet Yılmaz",
119
+ "role": "admin",
120
+ "loginTime": "2025-01-15T10:30:00.000Z",
121
+ "authProvider": "firebase" // veya "demo"
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Firestore - users collection
127
+ ```javascript
128
+ /users/{userId}
129
+ {
130
+ uid: "firebase-user-id",
131
+ email: "[email protected]",
132
+ fullName: "Ahmet Yılmaz",
133
+ role: "admin", // user, admin, developer, tester
134
+ createdAt: Timestamp(2025-01-15 10:30:00),
135
+ lastLogin: Timestamp(2025-01-15 10:30:00)
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 🎭 Roller ve Yetkiler
142
+
143
+ ### Kullanılabilir Roller
144
+ 1. **👤 User** (Kullanıcı)
145
+ - Rapor oluşturabilir
146
+ - Arama yapabilir
147
+ - Kendi raporlarını görebilir
148
+
149
+ 2. **👨‍💼 Admin** (Yönetici)
150
+ - Tüm User yetkileri
151
+ - Tüm raporları görebilir
152
+ - Sistem ayarlarını değiştirebilir
153
+ - Kullanıcı yönetimi (gelecekte)
154
+
155
+ 3. **👨‍💻 Developer** (Geliştirici)
156
+ - Tüm User yetkileri
157
+ - Teknik detayları görebilir
158
+ - Debug modunu aktifleştirebilir
159
+
160
+ 4. **🧪 Tester** (Test Uzmanı)
161
+ - Tüm User yetkileri
162
+ - Test raporları oluşturabilir
163
+ - Test verileri yükleyebilir
164
+
165
+ ---
166
+
167
+ ## 🔒 Güvenlik
168
+
169
+ ### Şifre Gereksinimleri
170
+ - ✅ Minimum 6 karakter
171
+ - 🟢 Önerilen: 10+ karakter
172
+ - 🟢 Önerilen: Büyük + küçük harf
173
+ - 🟢 Önerilen: En az 1 rakam
174
+ - 🟢 Önerilen: Özel karakter
175
+
176
+ ### Şifre Gücü Göstergesi
177
+ ```
178
+ 🔴 Zayıf : < 3 kriter
179
+ 🟡 Orta : 3-4 kriter
180
+ 🟢 Güçlü : 5+ kriter
181
+ ```
182
+
183
+ ### Firebase Security Rules
184
+ ```javascript
185
+ rules_version = '2';
186
+ service cloud.firestore {
187
+ match /databases/{database}/documents {
188
+ match /users/{userId} {
189
+ allow read: if request.auth.uid == userId;
190
+ allow create: if request.auth != null;
191
+ allow update: if request.auth.uid == userId;
192
+ }
193
+ }
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## 🎨 UI/UX Özellikleri
200
+
201
+ ### Animasyonlar
202
+ - 🌟 Animasyonlu arka plan (250 kutucuk)
203
+ - ✨ Hover efektleri (kutucuklar sarı olur)
204
+ - 🎯 Smooth scroll
205
+ - 💫 Gradient animasyonları
206
+
207
+ ### Responsive Design
208
+ - 📱 Mobil uyumlu (600px)
209
+ - 💻 Tablet uyumlu (900px)
210
+ - 🖥️ Desktop optimized
211
+
212
+ ### Form Validasyonu
213
+ - ⚡ Gerçek zamanlı validasyon
214
+ - 🎨 Görsel geri bildirim (kırmızı border)
215
+ - 📝 Hata mesajları (Türkçe)
216
+ - ✅ Başarı mesajları
217
+
218
+ ---
219
+
220
+ ## 🔄 Akış Diyagramları
221
+
222
+ ### Kayıt Akışı
223
+ ```
224
+ Kullanıcı register.html açar
225
+
226
+ Form doldurulur
227
+
228
+ Validasyon kontrolü
229
+
230
+ Firebase'e kayıt
231
+
232
+ Firestore'a kullanıcı verisi
233
+
234
+ ✅ login.html'e yönlendirme
235
+ ```
236
+
237
+ ### Giriş Akışı
238
+ ```
239
+ Kullanıcı login.html açar
240
+
241
+ E-posta ve şifre girer
242
+
243
+ Firebase Authentication
244
+
245
+ Firestore'dan kullanıcı verisi
246
+
247
+ LocalStorage'a session kaydet
248
+
249
+ ✅ data_selection.html'e yönlendirme
250
+ ```
251
+
252
+ ### Şifre Sıfırlama Akışı
253
+ ```
254
+ "Şifremi unuttum?" tıkla
255
+
256
+ E-posta gir
257
+
258
+ Firebase reset email gönder
259
+
260
+ E-posta kutusunu kontrol et
261
+
262
+ Reset linkine tıkla
263
+
264
+ Yeni şifre belirle
265
+
266
+ ✅ Giriş yap
267
+ ```
268
+
269
+ ---
270
+
271
+ ## 📊 Hata Yönetimi
272
+
273
+ ### Firebase Hataları
274
+ | Hata Kodu | Mesaj | Açıklama |
275
+ |-----------|-------|----------|
276
+ | `auth/email-already-in-use` | "Bu e-posta zaten kullanımda!" | Kayıt sırasında |
277
+ | `auth/invalid-email` | "Geçersiz e-posta adresi!" | E-posta formatı hatalı |
278
+ | `auth/user-not-found` | "Kullanıcı bulunamadı!" | Giriş sırasında |
279
+ | `auth/wrong-password` | "Hatalı şifre!" | Şifre yanlış |
280
+ | `auth/weak-password` | "Şifre çok zayıf!" | Min. 6 karakter |
281
+ | `auth/too-many-requests` | "Çok fazla deneme!" | Rate limit |
282
+
283
+ ### Kullanıcı Dostu Mesajlar
284
+ ```javascript
285
+ ✅ Başarı: "Kayıt başarılı! Giriş sayfasına yönlendiriliyorsunuz..."
286
+ ⚠️ Uyarı: "Şifreler eşleşmiyor!"
287
+ ❌ Hata: "E-posta adresi zaten kullanımda!"
288
+ ```
289
+
290
+ ---
291
+
292
+ ## 🚀 Gelecek Geliştirmeler
293
+
294
+ ### Planlanan Özellikler
295
+ - [ ] Email verification (e-posta doğrulama)
296
+ - [ ] Social login (Google, Microsoft)
297
+ - [ ] 2FA (Two-factor authentication)
298
+ - [ ] User profile sayfası
299
+ - [ ] Admin panel
300
+ - [ ] Password strength meter iyileştirmesi
301
+ - [ ] Remember me özelliği
302
+ - [ ] Session timeout
303
+ - [ ] Login history
304
+
305
+ ---
306
+
307
+ ## 📝 Kod Örnekleri
308
+
309
+ ### Kullanıcı Session Kontrolü
310
+ ```javascript
311
+ // Her sayfada kullanıcı kontrolü
312
+ const userSession = JSON.parse(localStorage.getItem('userSession'));
313
+ if (!userSession) {
314
+ window.location.href = 'login.html';
315
+ }
316
+ ```
317
+
318
+ ### Firebase ile Logout
319
+ ```javascript
320
+ async function logout() {
321
+ if (confirm('Çıkış yapmak istediğinizden emin misiniz?')) {
322
+ try {
323
+ await auth.signOut();
324
+ localStorage.removeItem('userSession');
325
+ window.location.href = 'login.html';
326
+ } catch (error) {
327
+ console.error('Logout error:', error);
328
+ }
329
+ }
330
+ }
331
+ ```
332
+
333
+ ### Rol Bazlı Erişim Kontrolü
334
+ ```javascript
335
+ function checkAdminAccess() {
336
+ const session = JSON.parse(localStorage.getItem('userSession'));
337
+ if (session.role !== 'admin') {
338
+ alert('Bu sayfaya erişim yetkiniz yok!');
339
+ window.location.href = 'index.html';
340
+ }
341
+ }
342
+ ```
343
+
344
+ ---
345
+
346
+ ## 🎓 Eğitim Videoları (Gelecekte)
347
+
348
+ - [ ] Firebase projesi oluşturma
349
+ - [ ] Kayıt sistemi kullanımı
350
+ - [ ] Admin paneli kullanımı
351
+ - [ ] Güvenlik kuralları yapılandırması
352
+
353
+ ---
354
+
355
+ ## 📞 Destek
356
+
357
+ **Sorun mu yaşıyorsunuz?**
358
+ 1. `FIREBASE_QUICK_START.md` dosyasını okuyun
359
+ 2. `FIREBASE_AUTH_SETUP.md` detaylı kurulum kılavuzu
360
+ 3. Firebase Console → Support
361
+ 4. GitHub Issues
362
+
363
+ ---
364
+
365
+ **Son Güncelleme**: 2025-01-15
366
+ **Versiyon**: 1.0.0
367
+ **Durum**: ✅ Production Ready
368
+
api_server.py CHANGED
@@ -11,6 +11,7 @@ from hybrid_search import HybridSearch
11
  import logging
12
  from typing import Dict, List, Optional
13
  import time
 
14
 
15
  # Configure logging
16
  logging.basicConfig(
@@ -26,17 +27,86 @@ CORS(app) # Enable CORS for frontend requests
26
  # Initialize search system (singleton)
27
  search_system = None
28
 
29
- # Global variable to store custom uploaded data
30
- custom_data_store = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  'data': None,
32
  'fileName': None,
33
  'rowCount': 0,
34
  'columns': [],
35
- 'selectedColumns': [], # Columns to use for cross-encoder search
36
- 'metadataColumns': [], # Columns to show in form for comparison
37
  'uploadedAt': None,
38
- 'loaded': False
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  def get_search_system():
42
  """Get or initialize the search system"""
@@ -48,19 +118,136 @@ def get_search_system():
48
  return search_system
49
 
50
 
51
- def search_custom_data(query, df, top_k=10, selected_columns=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  """
53
- Simple text-based search on custom data
54
- Returns results in the same format as hybrid search
55
 
56
  Args:
57
  query: Search query
58
  df: Custom DataFrame
59
  top_k: Number of results to return
60
  selected_columns: List of columns selected by user for search (priority)
 
61
  """
62
  import pandas as pd
63
  from difflib import SequenceMatcher
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  results = []
66
  query_lower = query.lower()
@@ -209,43 +396,56 @@ def search_reports():
209
  if not isinstance(top_k, int) or top_k < 1 or top_k > 50:
210
  top_k = 10
211
 
212
- logger.info(f"🔍 Search request: query='{query[:50]}...', app={application}, platform={platform}, columns={selected_columns}")
 
213
 
214
- # Check if custom data is loaded
215
- global custom_data_store
216
 
217
- if custom_data_store['loaded'] and custom_data_store['data'] is not None:
218
- # Use custom data for search (simple text matching for now)
219
- # Prefer selectedColumns from request, fallback to stored config
220
- cols_to_use = selected_columns if selected_columns != ['Summary', 'Description'] else custom_data_store.get('selectedColumns', selected_columns)
221
- logger.info(f"📤 Using custom uploaded data: {custom_data_store['fileName']}")
 
 
 
 
222
  logger.info(f"🎯 Selected columns for search: {cols_to_use}")
 
223
  start_time = time.time()
224
  results = search_custom_data(
225
  query,
226
- custom_data_store['data'],
227
  top_k,
228
- selected_columns=cols_to_use
 
229
  )
230
  search_time = time.time() - start_time
231
  logger.info(f"✅ Found {len(results)} results in custom data in {search_time:.2f}s")
232
  else:
233
- # Use default hybrid search system
234
- logger.info(f"📁 Using default data")
235
- logger.info(f"🎯 Selected columns for search: {selected_columns}")
236
- start_time = time.time()
237
- search = get_search_system()
238
- results = search.search(
239
- query=query,
240
- application=application,
241
- platform=platform,
242
- version=version,
243
- language=language,
244
- top_k=top_k,
245
- selected_columns=selected_columns # Pass selected columns
246
- )
247
- search_time = time.time() - start_time
248
- logger.info(f"✅ Found {len(results)} results in {search_time:.2f}s")
 
 
 
 
 
 
 
249
 
250
  # Return results
251
  return jsonify({
@@ -257,8 +457,8 @@ def search_reports():
257
  'version': version,
258
  'language': language
259
  },
260
- 'results': results,
261
- 'count': len(results),
262
  'search_time': round(search_time, 2)
263
  })
264
 
@@ -285,10 +485,15 @@ def get_stats():
285
  }
286
  """
287
  try:
288
- # Check if custom data is loaded
289
- if custom_data_store['loaded'] and custom_data_store['data'] is not None:
290
- df = custom_data_store['data']
291
- logger.info(f"📊 Getting stats from custom data: {len(df)} rows")
 
 
 
 
 
292
 
293
  # Calculate statistics from custom data
294
  stats = {
@@ -313,27 +518,23 @@ def get_stats():
313
  'success': True,
314
  'stats': stats,
315
  'customDataLoaded': True,
316
- 'fileName': custom_data_store['fileName']
 
317
  })
318
  else:
319
- # Use default search system
320
- search = get_search_system()
321
-
322
- # Calculate statistics
323
- stats = {
324
- 'total_reports': len(search.df),
325
- 'platforms': {
326
- 'android': int((search.df['Platform'] == 'android').sum()),
327
- 'ios': int((search.df['Platform'] == 'ios').sum()),
328
- 'unknown': int((search.df['Platform'] == 'unknown').sum())
329
- },
330
- 'applications': search.df['Application'].value_counts().to_dict() if 'Application' in search.df.columns else {}
331
- }
332
 
333
  return jsonify({
334
  'success': True,
335
- 'stats': stats,
336
- 'customDataLoaded': False
 
 
 
 
 
 
337
  })
338
 
339
  except Exception as e:
@@ -347,6 +548,38 @@ def get_stats():
347
  @app.route('/api/applications', methods=['GET'])
348
  def get_applications():
349
  """Get list of available applications"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  try:
351
  search = get_search_system()
352
 
@@ -396,17 +629,36 @@ def create_report():
396
  # Get request data
397
  data = request.get_json()
398
 
399
- logger.info(f"📥 Received create_report request with data keys: {list(data.keys()) if data else 'None'}")
 
 
 
 
 
400
  if data and 'replace_report' in data:
401
- logger.info(f"🔍 replace_report parameter found: {data.get('replace_report')}")
402
- logger.info(f"🔍 old_report_summary parameter: {data.get('old_report_summary')}")
 
 
 
 
 
 
 
403
 
404
- if not data or 'summary' not in data or 'description' not in data:
 
405
  return jsonify({
406
  'success': False,
407
- 'error': 'Missing required fields: summary, description'
408
  }), 400
409
 
 
 
 
 
 
 
410
  # Check if we're replacing an old report
411
  replace_report = data.get('replace_report', False)
412
  old_report_summary = data.get('old_report_summary', '')
@@ -414,6 +666,12 @@ def create_report():
414
 
415
  logger.info(f"🎯 replace_report={replace_report}, old_report_summary='{old_report_summary}'")
416
 
 
 
 
 
 
 
417
  # Detect application from summary
418
  summary_lower = data['summary'].lower()
419
  application = 'Unknown'
@@ -457,20 +715,18 @@ def create_report():
457
  'Application': application
458
  }
459
 
460
- # Check if custom data is loaded
461
- global custom_data_store
462
-
463
- if custom_data_store['loaded'] and custom_data_store['data'] is not None:
464
- # Append to custom data
465
- logger.info(f"📤 Appending to custom data: {custom_data_store['fileName']}")
466
 
467
  # Create new row with custom data columns
468
- custom_row = {col: '' for col in custom_data_store['data'].columns}
469
 
470
  # Map ALL form data fields to custom columns dynamically
471
  for key, value in data.items():
472
  # Try to find matching column (exact match or partial match)
473
- for col in custom_data_store['data'].columns:
474
  col_lower = col.lower()
475
  key_lower = key.lower()
476
 
@@ -480,7 +736,7 @@ def create_report():
480
  break
481
 
482
  # Also try common mappings
483
- for col in custom_data_store['data'].columns:
484
  col_lower = col.lower()
485
  if 'summary' in col_lower or 'özet' in col_lower:
486
  if not custom_row[col]: # Only if not already set
@@ -501,12 +757,12 @@ def create_report():
501
  # If replacing an old report, delete it first
502
  if replace_report and old_report_summary:
503
  logger.info(f"🔄 Replacing old report: '{old_report_summary}'")
504
- logger.info(f"📊 Current DataFrame shape: {custom_data_store['data'].shape}")
505
- logger.info(f"📋 Available columns: {custom_data_store['data'].columns.tolist()}")
506
 
507
  # Find the summary column (case-insensitive)
508
  summary_col = None
509
- for col in custom_data_store['data'].columns:
510
  if 'summary' in col.lower() or 'özet' in col.lower():
511
  summary_col = col
512
  logger.info(f"✓ Found summary column: '{summary_col}'")
@@ -514,38 +770,84 @@ def create_report():
514
 
515
  if summary_col:
516
  # Find and remove rows with matching summary
517
- mask = custom_data_store['data'][summary_col].astype(str).str.lower().str.contains(
518
  old_report_summary.lower(),
519
  na=False,
520
  regex=False
521
  )
522
- rows_before = len(custom_data_store['data'])
523
- matching_rows = custom_data_store['data'][mask]
524
  logger.info(f"🔍 Found {len(matching_rows)} matching row(s):")
525
  for idx, row in matching_rows.iterrows():
526
  logger.info(f" Row {idx}: {row[summary_col][:80]}")
527
 
528
- custom_data_store['data'] = custom_data_store['data'][~mask]
529
- rows_after = len(custom_data_store['data'])
530
  logger.info(f"🗑️ Deleted {rows_before - rows_after} old report(s)")
531
  else:
532
- logger.warning(f"⚠️ Could not find summary column in: {custom_data_store['data'].columns.tolist()}")
533
 
534
  # Append to DataFrame
535
- custom_data_store['data'] = pd.concat([
536
- custom_data_store['data'],
537
  pd.DataFrame([custom_row])
538
  ], ignore_index=True)
539
 
540
- custom_data_store['rowCount'] = len(custom_data_store['data'])
541
- report_id = custom_data_store['rowCount']
542
 
543
- # CRITICAL: Save to CSV file!
544
- csv_path = f"data/user_data/{custom_data_store.get('fileName', 'custom_data.csv')}"
545
  os.makedirs(os.path.dirname(csv_path), exist_ok=True)
546
- custom_data_store['data'].to_csv(csv_path, index=False, encoding='utf-8')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
 
548
- logger.info(f"✅ Report added to custom data and saved to {csv_path}. New count: {report_id}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
  else:
550
  # Use default CSV path
551
  csv_path = 'data/data_with_application.csv'
@@ -590,11 +892,16 @@ def create_report():
590
 
591
  logger.info(f"✅ New report created: ID={report_id}, App={application}, Summary={data['summary'][:50]}...")
592
 
 
 
 
593
  return jsonify({
594
  'success': True,
595
  'message': 'Report created successfully',
596
  'report_id': report_id,
597
- 'application': application
 
 
598
  })
599
 
600
  except Exception as e:
@@ -618,7 +925,9 @@ def upload_data():
618
  "data": [...], # Array of objects
619
  "fileName": "data.csv",
620
  "columns": ["col1", "col2", ...],
621
- "rowCount": 100
 
 
622
  }
623
  """
624
  try:
@@ -626,8 +935,11 @@ def upload_data():
626
  from datetime import datetime
627
  import os
628
  import json
 
629
 
630
- global custom_data_store
 
 
631
 
632
  # Get request data
633
  data = request.get_json()
@@ -638,13 +950,26 @@ def upload_data():
638
  'error': 'Missing data field'
639
  }), 400
640
 
641
- # Store custom data
642
- custom_data_store['data'] = pd.DataFrame(data['data'])
643
- custom_data_store['fileName'] = data.get('fileName', 'uploaded_data.csv')
644
- custom_data_store['rowCount'] = len(data['data'])
645
- custom_data_store['columns'] = data.get('columns', list(data['data'][0].keys()) if data['data'] else [])
646
- custom_data_store['uploadedAt'] = datetime.now().isoformat()
647
- custom_data_store['loaded'] = True
 
 
 
 
 
 
 
 
 
 
 
 
 
648
 
649
  # Save to user-specific datasets list
650
  username = data.get('username', 'demo')
@@ -662,12 +987,12 @@ def upload_data():
662
 
663
  # Add new dataset to user's list
664
  dataset_info = {
665
- 'name': custom_data_store['fileName'].replace('.csv', '').replace('_', ' ').title(),
666
- 'fileName': custom_data_store['fileName'],
667
- 'filePath': f'user_data/{username}/{custom_data_store["fileName"]}',
668
- 'rowCount': custom_data_store['rowCount'],
669
- 'columns': custom_data_store['columns'],
670
- 'columnCount': len(custom_data_store['columns']),
671
  'fileSize': f"{len(str(data['data'])) / (1024*1024):.2f} MB",
672
  'lastModified': datetime.now().strftime('%Y-%m-%d %H:%M'),
673
  'type': 'user',
@@ -677,7 +1002,7 @@ def upload_data():
677
  # Check if already exists, update or append
678
  exists = False
679
  for i, ds in enumerate(user_datasets):
680
- if ds.get('fileName') == custom_data_store['fileName']:
681
  user_datasets[i] = dataset_info
682
  exists = True
683
  break
@@ -689,16 +1014,75 @@ def upload_data():
689
  with open(user_datasets_file, 'w') as f:
690
  json.dump(user_datasets, f, indent=2)
691
 
692
- logger.info(f"✅ Custom data uploaded and saved: {custom_data_store['fileName']}, {custom_data_store['rowCount']} rows, {len(custom_data_store['columns'])} columns")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
 
694
  return jsonify({
695
  'success': True,
696
- 'message': 'Data uploaded successfully',
697
  'info': {
698
- 'fileName': custom_data_store['fileName'],
699
- 'rowCount': custom_data_store['rowCount'],
700
- 'columns': custom_data_store['columns'],
701
- 'uploadedAt': custom_data_store['uploadedAt']
 
 
 
702
  }
703
  })
704
 
@@ -720,13 +1104,12 @@ def update_selected_columns():
720
 
721
  Request Body:
722
  {
 
723
  "selectedColumns": ["Summary", "Description", ...], # For cross-encoder
724
  "metadataColumns": ["Platform", "App Version", ...] # For form display
725
  }
726
  """
727
  try:
728
- global custom_data_store
729
-
730
  data = request.get_json()
731
 
732
  if not data or 'selectedColumns' not in data:
@@ -735,18 +1118,40 @@ def update_selected_columns():
735
  'error': 'Missing selectedColumns field'
736
  }), 400
737
 
738
- # Update selected columns
739
- custom_data_store['selectedColumns'] = data['selectedColumns']
740
- custom_data_store['metadataColumns'] = data.get('metadataColumns', [])
741
 
742
- logger.info(f"✅ Cross-encoder columns updated: {custom_data_store['selectedColumns']}")
743
- logger.info(f"✅ Metadata columns updated: {custom_data_store['metadataColumns']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
 
745
  return jsonify({
746
  'success': True,
747
  'message': 'Selected columns updated',
748
- 'selectedColumns': custom_data_store['selectedColumns'],
749
- 'metadataColumns': custom_data_store['metadataColumns']
 
750
  })
751
 
752
  except Exception as e:
@@ -791,35 +1196,29 @@ def clear_custom_data():
791
 
792
  @app.route('/api/data_status', methods=['GET'])
793
  def data_status():
794
- """Get current data status (custom or default)"""
795
  try:
796
- global custom_data_store
 
 
 
797
 
798
- if custom_data_store['loaded']:
799
  return jsonify({
800
  'success': True,
801
  'custom_data_loaded': True,
802
- 'custom_data_columns': custom_data_store['columns'],
803
- 'fileName': custom_data_store['fileName'],
804
- 'rowCount': custom_data_store['rowCount']
 
805
  })
806
  else:
807
- # Get default data columns from HybridSearch system
808
- try:
809
- search = get_search_system()
810
- default_columns = list(search.df.columns)
811
- except Exception as e:
812
- logger.warning(f"Could not load default data info: {e}")
813
- default_columns = [
814
- 'Summary', 'Description', 'Affects Version', 'Component',
815
- 'Priority', 'Custom field (Severity)', 'Custom field (Problem Type)',
816
- 'Custom field (Frequency)', 'Application', 'App Version'
817
- ]
818
-
819
  return jsonify({
820
  'success': True,
821
  'custom_data_loaded': False,
822
- 'default_data_columns': default_columns
 
823
  })
824
 
825
  except Exception as e:
@@ -835,14 +1234,19 @@ def data_status():
835
  def get_column_values(column_name):
836
  """Get unique values for a specific column"""
837
  try:
838
- global custom_data_store
 
 
839
 
840
- # Use custom data if loaded, otherwise use default
841
- if custom_data_store['loaded'] and custom_data_store['data'] is not None:
842
- df = custom_data_store['data']
843
- else:
844
- search = get_search_system()
845
- df = search.df
 
 
 
846
 
847
  # Get unique values for the column
848
  if column_name in df.columns:
@@ -854,12 +1258,14 @@ def get_column_values(column_name):
854
  'success': True,
855
  'column': column_name,
856
  'values': unique_values,
857
- 'count': len(unique_values)
 
858
  })
859
  else:
860
  return jsonify({
861
  'success': False,
862
- 'error': f'Column {column_name} not found'
 
863
  }), 404
864
 
865
  except Exception as e:
@@ -885,42 +1291,10 @@ def get_available_datasets():
885
  # Get username from query params (for user-specific datasets)
886
  username = request.args.get('username', 'default')
887
 
888
- # List of default CSV files
889
- csv_files = [
890
- 'data_with_application.csv',
891
- 'data_with_enhanced_app_version.csv',
892
- 'data_with_app_version.csv',
893
- 'data_cleaned.csv'
894
- ]
895
-
896
- # Add default datasets
897
- for csv_file in csv_files:
898
- file_path = os.path.join(data_dir, csv_file)
899
- if os.path.exists(file_path):
900
- try:
901
- # Read first few rows to get info (with semicolon delimiter)
902
- df = pd.read_csv(file_path, nrows=5, encoding='utf-8', sep=';', on_bad_lines='skip')
903
- file_stats = os.stat(file_path)
904
-
905
- # Get full row count (faster method using line count)
906
- with open(file_path, 'r', encoding='utf-8') as f:
907
- row_count = sum(1 for line in f) - 1 # -1 for header
908
-
909
- datasets.append({
910
- 'name': csv_file.replace('.csv', '').replace('_', ' ').title(),
911
- 'fileName': csv_file,
912
- 'filePath': file_path,
913
- 'rowCount': row_count,
914
- 'columns': df.columns.tolist(),
915
- 'columnCount': len(df.columns),
916
- 'fileSize': f"{file_stats.st_size / (1024*1024):.2f} MB",
917
- 'lastModified': datetime.fromtimestamp(file_stats.st_mtime).strftime('%Y-%m-%d %H:%M'),
918
- 'type': 'default',
919
- 'owner': 'system'
920
- })
921
- except Exception as e:
922
- logger.error(f"Error reading {csv_file}: {e}")
923
- continue
924
 
925
  # Add user-specific datasets from localStorage data
926
  user_datasets_file = os.path.join(data_dir, 'user_datasets', f'{username}.json')
@@ -1032,22 +1406,45 @@ def internal_error(error):
1032
 
1033
  def main():
1034
  """Main function to run the server"""
 
 
 
 
1035
  print("\n" + "=" * 80)
1036
  print("🚀 BUG REPORT DUPLICATE DETECTION API SERVER")
1037
  print("=" * 80)
1038
- print("\nInitializing...")
 
 
 
 
 
 
 
 
1039
 
1040
- # Pre-initialize search system
1041
- get_search_system()
 
 
 
 
 
 
1042
 
1043
  print("\n" + "=" * 80)
1044
  print("✅ SERVER READY!")
1045
  print("=" * 80)
 
 
 
 
 
1046
  print("\n📍 Endpoints:")
1047
- print(" • http://localhost:5000/api/health - Health check")
1048
- print(" • http://localhost:5000/api/search - Search similar reports (POST)")
1049
- print(" • http://localhost:5000/api/stats - Get system statistics")
1050
- print(" • http://localhost:5000/api/applications - Get available applications")
1051
  print("\n🌐 Frontend:")
1052
  print(" • Open web/index.html in your browser")
1053
  print("\n💡 Usage:")
@@ -1059,8 +1456,8 @@ def main():
1059
  # Run Flask server
1060
  app.run(
1061
  host='0.0.0.0',
1062
- port=5001, # Using 5001 because macOS AirPlay uses 5000
1063
- debug=False, # Set to True for development
1064
  threaded=True
1065
  )
1066
 
 
11
  import logging
12
  from typing import Dict, List, Optional
13
  import time
14
+ from pathlib import Path
15
 
16
  # Configure logging
17
  logging.basicConfig(
 
27
  # Initialize search system (singleton)
28
  search_system = None
29
 
30
+ # Global variable to store custom uploaded data PER USER
31
+ # Structure: user_data_stores[user_id] = { data, fileName, rowCount, ... }
32
+ user_data_stores = {}
33
+
34
+ def get_user_data_store(user_id: str) -> dict:
35
+ """Get data store for a specific user - loads from disk if exists"""
36
+ if user_id not in user_data_stores:
37
+ # Check if user has data on disk
38
+ user_embeddings_dir = Path('data/user_embeddings') / user_id
39
+ metadata_file = user_embeddings_dir / 'metadata.json'
40
+
41
+ if metadata_file.exists():
42
+ # Load from disk
43
+ try:
44
+ import json
45
+ import pandas as pd
46
+
47
+ with open(metadata_file, 'r') as f:
48
+ metadata = json.load(f)
49
+
50
+ # Load the original data if available
51
+ data_file = user_embeddings_dir / 'data.csv'
52
+ df = None
53
+ if data_file.exists():
54
+ df = pd.read_csv(data_file)
55
+ logger.info(f"📂 Loaded user data from disk for user: {user_id} ({len(df)} rows)")
56
+
57
+ # Reconstruct user_data_store from metadata
58
+ user_data_stores[user_id] = {
59
+ 'data': df,
60
+ 'fileName': metadata.get('fileName', 'uploaded_data.csv'),
61
+ 'rowCount': metadata.get('recordCount', len(df) if df is not None else 0),
62
+ 'columns': metadata.get('textColumns', []),
63
+ 'selectedColumns': metadata.get('textColumns', []),
64
+ 'metadataColumns': metadata.get('metadataColumns', []),
65
+ 'uploadedAt': metadata.get('createdAt'),
66
+ 'loaded': df is not None,
67
+ 'userId': user_id,
68
+ 'embeddingsReady': True
69
+ }
70
+ logger.info(f"✅ Restored user data store from disk for user: {user_id}")
71
+ except Exception as e:
72
+ logger.error(f"❌ Error loading user data from disk: {e}")
73
+ # Fall through to create empty store
74
+ user_data_stores[user_id] = {
75
  'data': None,
76
  'fileName': None,
77
  'rowCount': 0,
78
  'columns': [],
79
+ 'selectedColumns': [],
80
+ 'metadataColumns': [],
81
  'uploadedAt': None,
82
+ 'loaded': False,
83
+ 'userId': user_id
84
+ }
85
+ else:
86
+ # Create empty store
87
+ user_data_stores[user_id] = {
88
+ 'data': None,
89
+ 'fileName': None,
90
+ 'rowCount': 0,
91
+ 'columns': [],
92
+ 'selectedColumns': [],
93
+ 'metadataColumns': [],
94
+ 'uploadedAt': None,
95
+ 'loaded': False,
96
+ 'userId': user_id
97
+ }
98
+ return user_data_stores[user_id]
99
+
100
+ def set_user_data_store(user_id: str, data_store: dict):
101
+ """Set data store for a specific user"""
102
+ data_store['userId'] = user_id
103
+ user_data_stores[user_id] = data_store
104
+
105
+ def clear_user_data_store(user_id: str):
106
+ """Clear data store for a specific user"""
107
+ if user_id in user_data_stores:
108
+ del user_data_stores[user_id]
109
+ logger.info(f"✅ Custom data cleared for user: {user_id}")
110
 
111
  def get_search_system():
112
  """Get or initialize the search system"""
 
118
  return search_system
119
 
120
 
121
+ def update_embeddings_for_new_report(new_row_index):
122
+ """
123
+ Update embeddings and FAISS indices for a newly added report
124
+
125
+ Args:
126
+ new_row_index: Index of the new row in the DataFrame (0-based)
127
+ """
128
+ global search_system
129
+
130
+ if search_system is None:
131
+ logger.warning("⚠️ Search system not initialized, cannot update embeddings")
132
+ return
133
+
134
+ try:
135
+ import numpy as np
136
+ import faiss
137
+ from pathlib import Path
138
+
139
+ # Reload DataFrame to get the new report
140
+ logger.info(f"📥 Reloading data to include new report...")
141
+ search_system.load_data()
142
+
143
+ # Get the new row
144
+ if new_row_index >= len(search_system.df):
145
+ logger.error(f"❌ Invalid row index: {new_row_index}, DataFrame has {len(search_system.df)} rows")
146
+ return
147
+
148
+ new_row = search_system.df.iloc[new_row_index]
149
+
150
+ # Generate embedding for the new report
151
+ logger.info(f"🔄 Generating embedding for new report: '{new_row.get('Summary', '')[:50]}...'")
152
+ summary = str(new_row.get('Summary', ''))
153
+ description = str(new_row.get('Description', ''))
154
+ combined_text = f"{summary}. {description}".strip().lower()
155
+
156
+ new_embedding = search_system.bi_encoder.encode(
157
+ [combined_text],
158
+ batch_size=1,
159
+ show_progress_bar=False,
160
+ convert_to_numpy=True
161
+ )
162
+
163
+ # Load existing embeddings
164
+ embeddings_path = Path(search_system.embeddings_dir) / "embeddings.npy"
165
+ if embeddings_path.exists():
166
+ existing_embeddings = np.load(embeddings_path)
167
+ logger.info(f"📊 Loaded existing embeddings: {existing_embeddings.shape}")
168
+
169
+ # Append new embedding
170
+ updated_embeddings = np.vstack([existing_embeddings, new_embedding])
171
+ logger.info(f"✅ New embeddings shape: {updated_embeddings.shape}")
172
+
173
+ # Save updated embeddings
174
+ np.save(embeddings_path, updated_embeddings)
175
+ logger.info(f"💾 Saved updated embeddings to {embeddings_path}")
176
+
177
+ # Update in-memory embeddings
178
+ search_system.embeddings = updated_embeddings
179
+
180
+ # Add to FAISS index
181
+ platform = str(new_row.get('Platform', 'unknown')).lower()
182
+ if platform not in ['android', 'ios', 'unknown']:
183
+ platform = 'unknown'
184
+
185
+ logger.info(f"🔄 Adding to FAISS index: {platform}")
186
+
187
+ if platform in search_system.faiss_indices:
188
+ # Normalize the embedding (FAISS uses cosine similarity with normalized vectors)
189
+ normalized_embedding = new_embedding / np.linalg.norm(new_embedding)
190
+
191
+ # Add to FAISS index
192
+ search_system.faiss_indices[platform].add(normalized_embedding.astype('float32'))
193
+
194
+ # Save updated FAISS index
195
+ index_path = Path(search_system.embeddings_dir) / f"faiss_index_{platform}.index"
196
+ faiss.write_index(search_system.faiss_indices[platform], str(index_path))
197
+ logger.info(f"💾 Saved updated FAISS index to {index_path}")
198
+
199
+ logger.info(f"✅ Successfully added new report to FAISS index ({platform})")
200
+ else:
201
+ logger.warning(f"⚠️ FAISS index not found for platform: {platform}")
202
+ else:
203
+ logger.warning(f"⚠️ Embeddings file not found: {embeddings_path}")
204
+ logger.warning("⚠️ Please run embedding pipeline to generate embeddings")
205
+
206
+ except Exception as e:
207
+ logger.error(f"❌ Error updating embeddings: {e}")
208
+ import traceback
209
+ traceback.print_exc()
210
+ raise
211
+
212
+
213
+ def search_custom_data(query, df, top_k=10, selected_columns=None, user_id=None):
214
  """
215
+ Hybrid search on custom data using user's embeddings
216
+ Falls back to text-based search if embeddings not available
217
 
218
  Args:
219
  query: Search query
220
  df: Custom DataFrame
221
  top_k: Number of results to return
222
  selected_columns: List of columns selected by user for search (priority)
223
+ user_id: User ID (for loading embeddings)
224
  """
225
  import pandas as pd
226
  from difflib import SequenceMatcher
227
+ import sys
228
+ import os
229
+
230
+ # Try hybrid search with embeddings first
231
+ if user_id:
232
+ try:
233
+ # Add src to path
234
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
235
+ from user_hybrid_search import search_user_data
236
+
237
+ logger.info(f"🚀 Using Hybrid Search with embeddings for user: {user_id}")
238
+ results = search_user_data(user_id, query, df, top_k)
239
+
240
+ if results and len(results) > 0:
241
+ logger.info(f"✅ Hybrid search returned {len(results)} results")
242
+ return results
243
+ else:
244
+ logger.warning(f"⚠️ Hybrid search returned no results, falling back to text search")
245
+
246
+ except Exception as e:
247
+ logger.warning(f"⚠️ Hybrid search failed: {e}, falling back to text search")
248
+
249
+ # Fallback: Simple text-based search
250
+ logger.info(f"🔍 Using fallback text-based search")
251
 
252
  results = []
253
  query_lower = query.lower()
 
396
  if not isinstance(top_k, int) or top_k < 1 or top_k > 50:
397
  top_k = 10
398
 
399
+ # Get user_id from request (required for user-specific search)
400
+ user_id = data.get('user_id', 'anonymous')
401
 
402
+ logger.info(f"🔍 Search request: query='{query[:50]}...', app={application}, platform={platform}, user_id={user_id}")
 
403
 
404
+ # Get user-specific data store
405
+ user_store = get_user_data_store(user_id)
406
+
407
+ if user_store['loaded'] and user_store['data'] is not None:
408
+ # Use custom data for search (hybrid search with user embeddings)
409
+ cols_to_use = selected_columns if selected_columns != ['Summary', 'Description'] else user_store.get('selectedColumns', selected_columns)
410
+
411
+ logger.info(f"📤 Using custom uploaded data: {user_store['fileName']}")
412
+ logger.info(f"👤 User ID: {user_id}")
413
  logger.info(f"🎯 Selected columns for search: {cols_to_use}")
414
+
415
  start_time = time.time()
416
  results = search_custom_data(
417
  query,
418
+ user_store['data'],
419
  top_k,
420
+ selected_columns=cols_to_use,
421
+ user_id=user_id # Pass user_id for embedding-based search
422
  )
423
  search_time = time.time() - start_time
424
  logger.info(f"✅ Found {len(results)} results in custom data in {search_time:.2f}s")
425
  else:
426
+ # No data loaded - user must upload their own data
427
+ logger.warning(f"⚠️ No data uploaded for user: {user_id}")
428
+ return jsonify({
429
+ 'success': False,
430
+ 'error': 'No data uploaded. Please upload your data first.',
431
+ 'message': 'You must upload your data before searching. Go to Data Upload page.',
432
+ 'userId': user_id
433
+ }), 400
434
+
435
+ # Clean NaN values from results (JSON doesn't support NaN)
436
+ import math
437
+ def clean_nan(obj):
438
+ """Recursively replace NaN values with None or 0"""
439
+ if isinstance(obj, dict):
440
+ return {k: clean_nan(v) for k, v in obj.items()}
441
+ elif isinstance(obj, list):
442
+ return [clean_nan(item) for item in obj]
443
+ elif isinstance(obj, float) and (math.isnan(obj) or math.isinf(obj)):
444
+ return 0.0 # Replace NaN/Inf with 0
445
+ else:
446
+ return obj
447
+
448
+ cleaned_results = clean_nan(results)
449
 
450
  # Return results
451
  return jsonify({
 
457
  'version': version,
458
  'language': language
459
  },
460
+ 'results': cleaned_results,
461
+ 'count': len(cleaned_results),
462
  'search_time': round(search_time, 2)
463
  })
464
 
 
485
  }
486
  """
487
  try:
488
+ # Get user_id from query parameter
489
+ user_id = request.args.get('user_id', 'anonymous')
490
+
491
+ # Get user-specific data store
492
+ user_store = get_user_data_store(user_id)
493
+
494
+ if user_store['loaded'] and user_store['data'] is not None:
495
+ df = user_store['data']
496
+ logger.info(f"📊 Getting stats from custom data for user {user_id}: {len(df)} rows")
497
 
498
  # Calculate statistics from custom data
499
  stats = {
 
518
  'success': True,
519
  'stats': stats,
520
  'customDataLoaded': True,
521
+ 'fileName': user_store['fileName'],
522
+ 'userId': user_id
523
  })
524
  else:
525
+ # No data loaded for this user
526
+ logger.info(f"⚠️ No data loaded for user: {user_id}")
 
 
 
 
 
 
 
 
 
 
 
527
 
528
  return jsonify({
529
  'success': True,
530
+ 'stats': {
531
+ 'total_reports': 0,
532
+ 'platforms': {},
533
+ 'applications': {}
534
+ },
535
+ 'customDataLoaded': False,
536
+ 'message': 'No data uploaded. Please upload your data first.',
537
+ 'userId': user_id
538
  })
539
 
540
  except Exception as e:
 
548
  @app.route('/api/applications', methods=['GET'])
549
  def get_applications():
550
  """Get list of available applications"""
551
+ try:
552
+ # Check if custom data is loaded
553
+ if custom_data_store['loaded'] and custom_data_store['data'] is not None:
554
+ df = custom_data_store['data']
555
+
556
+ if 'Application' not in df.columns:
557
+ applications = []
558
+ else:
559
+ applications = sorted(df['Application'].unique().tolist())
560
+
561
+ return jsonify({
562
+ 'success': True,
563
+ 'applications': applications
564
+ })
565
+ else:
566
+ # No data loaded
567
+ return jsonify({
568
+ 'success': True,
569
+ 'applications': []
570
+ })
571
+
572
+ except Exception as e:
573
+ logger.error(f"❌ Applications error: {e}")
574
+ return jsonify({
575
+ 'success': False,
576
+ 'error': str(e)
577
+ }), 500
578
+
579
+
580
+ @app.route('/api/applications_OLD', methods=['GET'])
581
+ def get_applications_old():
582
+ """Get list of available applications - OLD VERSION"""
583
  try:
584
  search = get_search_system()
585
 
 
629
  # Get request data
630
  data = request.get_json()
631
 
632
+ import uuid
633
+ request_id = str(uuid.uuid4())[:8]
634
+
635
+ logger.info(f"[{request_id}] 📥 Received create_report request with data keys: {list(data.keys()) if data else 'None'}")
636
+ logger.info(f"[{request_id}] 📝 Full data: {data}")
637
+
638
  if data and 'replace_report' in data:
639
+ logger.info(f"[{request_id}] 🔍 replace_report parameter found: {data.get('replace_report')}")
640
+ logger.info(f"[{request_id}] 🔍 old_report_summary parameter: {data.get('old_report_summary')}")
641
+
642
+ if not data:
643
+ logger.error(f"[{request_id}] ❌ VALIDATION FAILED: No data received!")
644
+ return jsonify({
645
+ 'success': False,
646
+ 'error': 'No data received'
647
+ }), 400
648
 
649
+ if 'summary' not in data:
650
+ logger.error(f"[{request_id}] ❌ VALIDATION FAILED: Missing summary field. Data keys: {list(data.keys())}")
651
  return jsonify({
652
  'success': False,
653
+ 'error': 'Missing required field: summary'
654
  }), 400
655
 
656
+ logger.info(f"[{request_id}] ✅ Validation passed: summary found = '{data['summary']}'")
657
+
658
+ # Get user_id from request
659
+ user_id = data.get('userId') or data.get('user_id', 'anonymous')
660
+ logger.info(f"👤 Create report request from user: {user_id}")
661
+
662
  # Check if we're replacing an old report
663
  replace_report = data.get('replace_report', False)
664
  old_report_summary = data.get('old_report_summary', '')
 
666
 
667
  logger.info(f"🎯 replace_report={replace_report}, old_report_summary='{old_report_summary}'")
668
 
669
+ # DEBUG: Check user store status
670
+ user_store = get_user_data_store(user_id)
671
+ logger.info(f"🔍 DEBUG user_store: loaded={user_store.get('loaded')}, data_is_none={user_store.get('data') is None}")
672
+ if user_store.get('data') is not None:
673
+ logger.info(f"🔍 DEBUG user_store data shape: {user_store['data'].shape}")
674
+
675
  # Detect application from summary
676
  summary_lower = data['summary'].lower()
677
  application = 'Unknown'
 
715
  'Application': application
716
  }
717
 
718
+ # User store already retrieved above (line 640)
719
+ if user_store['loaded'] and user_store['data'] is not None:
720
+ # Append to user's custom data
721
+ logger.info(f"📤 Appending to user {user_id}'s data: {user_store['fileName']}")
 
 
722
 
723
  # Create new row with custom data columns
724
+ custom_row = {col: '' for col in user_store['data'].columns}
725
 
726
  # Map ALL form data fields to custom columns dynamically
727
  for key, value in data.items():
728
  # Try to find matching column (exact match or partial match)
729
+ for col in user_store['data'].columns:
730
  col_lower = col.lower()
731
  key_lower = key.lower()
732
 
 
736
  break
737
 
738
  # Also try common mappings
739
+ for col in user_store['data'].columns:
740
  col_lower = col.lower()
741
  if 'summary' in col_lower or 'özet' in col_lower:
742
  if not custom_row[col]: # Only if not already set
 
757
  # If replacing an old report, delete it first
758
  if replace_report and old_report_summary:
759
  logger.info(f"🔄 Replacing old report: '{old_report_summary}'")
760
+ logger.info(f"📊 Current DataFrame shape: {user_store['data'].shape}")
761
+ logger.info(f"📋 Available columns: {user_store['data'].columns.tolist()}")
762
 
763
  # Find the summary column (case-insensitive)
764
  summary_col = None
765
+ for col in user_store['data'].columns:
766
  if 'summary' in col.lower() or 'özet' in col.lower():
767
  summary_col = col
768
  logger.info(f"✓ Found summary column: '{summary_col}'")
 
770
 
771
  if summary_col:
772
  # Find and remove rows with matching summary
773
+ mask = user_store['data'][summary_col].astype(str).str.lower().str.contains(
774
  old_report_summary.lower(),
775
  na=False,
776
  regex=False
777
  )
778
+ rows_before = len(user_store['data'])
779
+ matching_rows = user_store['data'][mask]
780
  logger.info(f"🔍 Found {len(matching_rows)} matching row(s):")
781
  for idx, row in matching_rows.iterrows():
782
  logger.info(f" Row {idx}: {row[summary_col][:80]}")
783
 
784
+ user_store['data'] = user_store['data'][~mask]
785
+ rows_after = len(user_store['data'])
786
  logger.info(f"🗑️ Deleted {rows_before - rows_after} old report(s)")
787
  else:
788
+ logger.warning(f"⚠️ Could not find summary column in: {user_store['data'].columns.tolist()}")
789
 
790
  # Append to DataFrame
791
+ user_store['data'] = pd.concat([
792
+ user_store['data'],
793
  pd.DataFrame([custom_row])
794
  ], ignore_index=True)
795
 
796
+ user_store['rowCount'] = len(user_store['data'])
797
+ report_id = user_store['rowCount']
798
 
799
+ # Save to user's CSV file (both in user_data and user_embeddings)
800
+ csv_path = f"data/user_data/{user_store.get('fileName', 'custom_data.csv')}"
801
  os.makedirs(os.path.dirname(csv_path), exist_ok=True)
802
+ user_store['data'].to_csv(csv_path, index=False, encoding='utf-8')
803
+
804
+ # Also save to user_embeddings directory for persistence
805
+ user_embeddings_dir = Path('data/user_embeddings') / user_id
806
+ user_embeddings_dir.mkdir(parents=True, exist_ok=True)
807
+ user_data_file = user_embeddings_dir / 'data.csv'
808
+ user_store['data'].to_csv(user_data_file, index=False)
809
+
810
+ # CRITICAL: Reload data from disk to ensure consistency
811
+ # This ensures subsequent searches use the updated data
812
+ user_store['data'] = pd.read_csv(user_data_file)
813
+ user_store['rowCount'] = len(user_store['data'])
814
+
815
+ # Update user store
816
+ set_user_data_store(user_id, user_store)
817
 
818
+ logger.info(f"✅ Report added to user {user_id}'s data and saved. New count: {report_id}")
819
+ logger.info(f"🔄 Data reloaded from disk to ensure consistency")
820
+
821
+ # CRITICAL: Regenerate embeddings for updated data
822
+ # Do this SYNCHRONOUSLY to ensure search results are correct
823
+ logger.info(f"🔄 Regenerating embeddings for user {user_id}...")
824
+
825
+ try:
826
+ from src.user_embedding_pipeline import create_user_embeddings
827
+
828
+ # Get text columns from metadata
829
+ text_columns = user_store.get('textColumns', ['Summary', 'Description'])
830
+
831
+ logger.info(f"📝 Using text columns: {text_columns}")
832
+
833
+ # Regenerate embeddings SYNCHRONOUSLY
834
+ success = create_user_embeddings(
835
+ user_id=user_id,
836
+ df=user_store['data'],
837
+ text_columns=text_columns
838
+ )
839
+
840
+ if success:
841
+ logger.info(f"✅ Embeddings regenerated successfully for user {user_id}")
842
+ embeddings_updated = True
843
+ else:
844
+ logger.warning(f"⚠️ Embedding regeneration failed for user {user_id}")
845
+ embeddings_updated = False
846
+
847
+ except Exception as e:
848
+ logger.error(f"❌ Error regenerating embeddings: {e}")
849
+ logger.exception(e)
850
+ embeddings_updated = False
851
  else:
852
  # Use default CSV path
853
  csv_path = 'data/data_with_application.csv'
 
892
 
893
  logger.info(f"✅ New report created: ID={report_id}, App={application}, Summary={data['summary'][:50]}...")
894
 
895
+ # NOTE: Embeddings should NOT be updated here for user-specific mode
896
+ # Users should re-upload their data or we should implement incremental embedding updates
897
+
898
  return jsonify({
899
  'success': True,
900
  'message': 'Report created successfully',
901
  'report_id': report_id,
902
+ 'application': application,
903
+ 'userId': user_id,
904
+ 'embeddings_updated': False # Embeddings not auto-updated in user-specific mode
905
  })
906
 
907
  except Exception as e:
 
925
  "data": [...], # Array of objects
926
  "fileName": "data.csv",
927
  "columns": ["col1", "col2", ...],
928
+ "rowCount": 100,
929
+ "userId": "firebase-user-id", # REQUIRED
930
+ "textColumns": ["Summary", "Description"] # Optional
931
  }
932
  """
933
  try:
 
935
  from datetime import datetime
936
  import os
937
  import json
938
+ import sys
939
 
940
+ # Add src to path for imports
941
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
942
+ from user_embedding_pipeline import create_user_embeddings
943
 
944
  # Get request data
945
  data = request.get_json()
 
950
  'error': 'Missing data field'
951
  }), 400
952
 
953
+ # Get user ID (REQUIRED)
954
+ user_id = data.get('userId') or data.get('username', 'anonymous')
955
+ logger.info(f"📥 Upload request from user: {user_id}")
956
+
957
+ # Store custom data for THIS USER
958
+ df = pd.DataFrame(data['data'])
959
+ user_data_store = {
960
+ 'data': df,
961
+ 'fileName': data.get('fileName', 'uploaded_data.csv'),
962
+ 'rowCount': len(data['data']),
963
+ 'columns': data.get('columns', list(data['data'][0].keys()) if data['data'] else []),
964
+ 'uploadedAt': datetime.now().isoformat(),
965
+ 'loaded': True,
966
+ 'userId': user_id,
967
+ 'selectedColumns': [],
968
+ 'metadataColumns': []
969
+ }
970
+
971
+ # Save to user-specific store
972
+ set_user_data_store(user_id, user_data_store)
973
 
974
  # Save to user-specific datasets list
975
  username = data.get('username', 'demo')
 
987
 
988
  # Add new dataset to user's list
989
  dataset_info = {
990
+ 'name': user_data_store['fileName'].replace('.csv', '').replace('_', ' ').title(),
991
+ 'fileName': user_data_store['fileName'],
992
+ 'filePath': f'user_data/{username}/{user_data_store["fileName"]}',
993
+ 'rowCount': user_data_store['rowCount'],
994
+ 'columns': user_data_store['columns'],
995
+ 'columnCount': len(user_data_store['columns']),
996
  'fileSize': f"{len(str(data['data'])) / (1024*1024):.2f} MB",
997
  'lastModified': datetime.now().strftime('%Y-%m-%d %H:%M'),
998
  'type': 'user',
 
1002
  # Check if already exists, update or append
1003
  exists = False
1004
  for i, ds in enumerate(user_datasets):
1005
+ if ds.get('fileName') == user_data_store['fileName']:
1006
  user_datasets[i] = dataset_info
1007
  exists = True
1008
  break
 
1014
  with open(user_datasets_file, 'w') as f:
1015
  json.dump(user_datasets, f, indent=2)
1016
 
1017
+ logger.info(f"✅ Custom data uploaded and saved for user {user_id}: {user_data_store['fileName']}, {user_data_store['rowCount']} rows, {len(user_data_store['columns'])} columns")
1018
+
1019
+ # 🔥 CRITICAL: Create embeddings for this user's data!
1020
+ try:
1021
+ logger.info(f"🔄 Creating embeddings for user: {user_id}")
1022
+
1023
+ # Get text columns (from request or auto-detect)
1024
+ text_columns = data.get('textColumns')
1025
+ if text_columns:
1026
+ logger.info(f"📝 Using provided text columns: {text_columns}")
1027
+ else:
1028
+ logger.info(f"🔍 Auto-detecting text columns...")
1029
+
1030
+ # Run embedding pipeline
1031
+ success = create_user_embeddings(
1032
+ user_id=user_id,
1033
+ df=df,
1034
+ text_columns=text_columns,
1035
+ config={
1036
+ 'fileName': user_data_store['fileName'],
1037
+ 'metadataColumns': user_data_store.get('metadataColumns', [])
1038
+ }
1039
+ )
1040
+
1041
+ if success:
1042
+ logger.info(f"✅ Embeddings created successfully for user: {user_id}")
1043
+
1044
+ # Save original data to disk for persistence
1045
+ user_embeddings_dir = Path('data/user_embeddings') / user_id
1046
+ data_file = user_embeddings_dir / 'data.csv'
1047
+ try:
1048
+ df.to_csv(data_file, index=False)
1049
+ logger.info(f"💾 Saved user data to disk: {data_file}")
1050
+ except Exception as e:
1051
+ logger.error(f"❌ Error saving user data to disk: {e}")
1052
+
1053
+ # Update user store with embedding info
1054
+ user_store = get_user_data_store(user_id)
1055
+ user_store['embeddingsCreated'] = True
1056
+ user_store['embeddingsPath'] = f"data/user_embeddings/{user_id}"
1057
+ set_user_data_store(user_id, user_store)
1058
+ else:
1059
+ logger.warning(f"⚠️ Embedding creation failed for user: {user_id}")
1060
+ user_store = get_user_data_store(user_id)
1061
+ user_store['embeddingsCreated'] = False
1062
+ set_user_data_store(user_id, user_store)
1063
+
1064
+ except Exception as e:
1065
+ logger.error(f"❌ Embedding creation error for user {user_id}: {e}")
1066
+ import traceback
1067
+ traceback.print_exc()
1068
+ user_store = get_user_data_store(user_id)
1069
+ user_store['embeddingsCreated'] = False
1070
+ set_user_data_store(user_id, user_store)
1071
+
1072
+ # Get final user store for response
1073
+ final_user_store = get_user_data_store(user_id)
1074
 
1075
  return jsonify({
1076
  'success': True,
1077
+ 'message': 'Data uploaded and embeddings created successfully',
1078
  'info': {
1079
+ 'fileName': final_user_store['fileName'],
1080
+ 'rowCount': final_user_store['rowCount'],
1081
+ 'columns': final_user_store['columns'],
1082
+ 'uploadedAt': final_user_store['uploadedAt'],
1083
+ 'embeddingsCreated': final_user_store.get('embeddingsCreated', False),
1084
+ 'embeddingsPath': final_user_store.get('embeddingsPath', None),
1085
+ 'userId': user_id
1086
  }
1087
  })
1088
 
 
1104
 
1105
  Request Body:
1106
  {
1107
+ "userId": "user123", # User ID
1108
  "selectedColumns": ["Summary", "Description", ...], # For cross-encoder
1109
  "metadataColumns": ["Platform", "App Version", ...] # For form display
1110
  }
1111
  """
1112
  try:
 
 
1113
  data = request.get_json()
1114
 
1115
  if not data or 'selectedColumns' not in data:
 
1118
  'error': 'Missing selectedColumns field'
1119
  }), 400
1120
 
1121
+ # Get user ID
1122
+ user_id = data.get('userId') or data.get('username', 'anonymous')
1123
+ user_store = get_user_data_store(user_id)
1124
 
1125
+ # Update selected columns
1126
+ user_store['selectedColumns'] = data['selectedColumns']
1127
+ user_store['metadataColumns'] = data.get('metadataColumns', [])
1128
+ set_user_data_store(user_id, user_store)
1129
+
1130
+ # Also update metadata.json for persistence
1131
+ try:
1132
+ user_embeddings_dir = Path('data/user_embeddings') / user_id
1133
+ metadata_file = user_embeddings_dir / 'metadata.json'
1134
+ if metadata_file.exists():
1135
+ import json
1136
+ with open(metadata_file, 'r') as f:
1137
+ metadata = json.load(f)
1138
+ metadata['textColumns'] = data['selectedColumns']
1139
+ metadata['metadataColumns'] = data.get('metadataColumns', [])
1140
+ with open(metadata_file, 'w') as f:
1141
+ json.dump(metadata, f, indent=2)
1142
+ logger.info(f"💾 Updated metadata.json for user {user_id}")
1143
+ except Exception as e:
1144
+ logger.error(f"❌ Error updating metadata.json: {e}")
1145
+
1146
+ logger.info(f"✅ Cross-encoder columns updated for user {user_id}: {user_store['selectedColumns']}")
1147
+ logger.info(f"✅ Metadata columns updated for user {user_id}: {user_store['metadataColumns']}")
1148
 
1149
  return jsonify({
1150
  'success': True,
1151
  'message': 'Selected columns updated',
1152
+ 'selectedColumns': user_store['selectedColumns'],
1153
+ 'metadataColumns': user_store['metadataColumns'],
1154
+ 'userId': user_id
1155
  })
1156
 
1157
  except Exception as e:
 
1196
 
1197
  @app.route('/api/data_status', methods=['GET'])
1198
  def data_status():
1199
+ """Get current data status for a specific user"""
1200
  try:
1201
+ # Get user_id from query parameter
1202
+ user_id = request.args.get('user_id', 'anonymous')
1203
+
1204
+ user_store = get_user_data_store(user_id)
1205
 
1206
+ if user_store['loaded']:
1207
  return jsonify({
1208
  'success': True,
1209
  'custom_data_loaded': True,
1210
+ 'custom_data_columns': user_store['columns'],
1211
+ 'fileName': user_store['fileName'],
1212
+ 'rowCount': user_store['rowCount'],
1213
+ 'userId': user_id
1214
  })
1215
  else:
1216
+ # No data loaded for this user
 
 
 
 
 
 
 
 
 
 
 
1217
  return jsonify({
1218
  'success': True,
1219
  'custom_data_loaded': False,
1220
+ 'message': 'No data uploaded for this user',
1221
+ 'userId': user_id
1222
  })
1223
 
1224
  except Exception as e:
 
1234
  def get_column_values(column_name):
1235
  """Get unique values for a specific column"""
1236
  try:
1237
+ # Get user ID from query params
1238
+ user_id = request.args.get('user_id', 'anonymous')
1239
+ user_store = get_user_data_store(user_id)
1240
 
1241
+ # Check if user has data
1242
+ if not user_store['loaded'] or user_store['data'] is None:
1243
+ return jsonify({
1244
+ 'success': False,
1245
+ 'error': 'No data uploaded. Please upload your data first.',
1246
+ 'userId': user_id
1247
+ }), 400
1248
+
1249
+ df = user_store['data']
1250
 
1251
  # Get unique values for the column
1252
  if column_name in df.columns:
 
1258
  'success': True,
1259
  'column': column_name,
1260
  'values': unique_values,
1261
+ 'count': len(unique_values),
1262
+ 'userId': user_id
1263
  })
1264
  else:
1265
  return jsonify({
1266
  'success': False,
1267
+ 'error': f'Column {column_name} not found',
1268
+ 'userId': user_id
1269
  }), 404
1270
 
1271
  except Exception as e:
 
1291
  # Get username from query params (for user-specific datasets)
1292
  username = request.args.get('username', 'default')
1293
 
1294
+ # NO DEFAULT DATASETS - Users must upload their own data
1295
+ # Only show user-specific datasets
1296
+
1297
+ logger.info(f"📋 Getting datasets for user: {username}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1298
 
1299
  # Add user-specific datasets from localStorage data
1300
  user_datasets_file = os.path.join(data_dir, 'user_datasets', f'{username}.json')
 
1406
 
1407
  def main():
1408
  """Main function to run the server"""
1409
+ # Load environment variables
1410
+ from dotenv import load_dotenv
1411
+ load_dotenv()
1412
+
1413
  print("\n" + "=" * 80)
1414
  print("🚀 BUG REPORT DUPLICATE DETECTION API SERVER")
1415
  print("=" * 80)
1416
+ print("\n⚠️ USER-SPECIFIC MODE:")
1417
+ print(" • No default data loaded")
1418
+ print(" • Each user must upload their own data")
1419
+ print(" • Users can only see their own data")
1420
+ print(" • Embeddings are created per user")
1421
+ print(" • Firebase Storage caching enabled")
1422
+
1423
+ # DO NOT pre-initialize search system - users will upload their own data
1424
+ # get_search_system() # REMOVED: No default Turkcell data
1425
 
1426
+ # Check Firebase configuration
1427
+ firebase_enabled = os.getenv('USE_FIREBASE_CACHE', 'True').lower() == 'true'
1428
+ if firebase_enabled:
1429
+ print("\n🔥 Firebase Configuration:")
1430
+ service_account = os.getenv('FIREBASE_SERVICE_ACCOUNT', 'Not set')
1431
+ storage_bucket = os.getenv('FIREBASE_STORAGE_BUCKET', 'Not set')
1432
+ print(f" • Service Account: {service_account}")
1433
+ print(f" • Storage Bucket: {storage_bucket}")
1434
 
1435
  print("\n" + "=" * 80)
1436
  print("✅ SERVER READY!")
1437
  print("=" * 80)
1438
+
1439
+ # Get port from environment (Railway sets this)
1440
+ port = int(os.getenv('PORT', 5001))
1441
+
1442
+ print(f"\n📍 Server will start on port: {port}")
1443
  print("\n📍 Endpoints:")
1444
+ print(f" • http://localhost:{port}/api/health - Health check")
1445
+ print(f" • http://localhost:{port}/api/search - Search similar reports (POST)")
1446
+ print(f" • http://localhost:{port}/api/stats - Get system statistics")
1447
+ print(f" • http://localhost:{port}/api/applications - Get available applications")
1448
  print("\n🌐 Frontend:")
1449
  print(" • Open web/index.html in your browser")
1450
  print("\n💡 Usage:")
 
1456
  # Run Flask server
1457
  app.run(
1458
  host='0.0.0.0',
1459
+ port=port,
1460
+ debug=os.getenv('FLASK_DEBUG', 'False').lower() == 'true',
1461
  threaded=True
1462
  )
1463
 
env.example ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ===================================
2
+ # JIRA DUPLICATE DETECTION - ENV CONFIG
3
+ # ===================================
4
+
5
+ # Firebase Configuration
6
+ # -----------------------
7
+ # Firebase Service Account JSON dosyasının yolu
8
+ FIREBASE_SERVICE_ACCOUNT=./firebase-service-account.json
9
+
10
+ # Firebase Storage bucket name
11
+ # Format: your-project.appspot.com
12
+ FIREBASE_STORAGE_BUCKET=jira-duplicate-detection.appspot.com
13
+
14
+ # Flask Configuration
15
+ # -------------------
16
+ FLASK_ENV=production
17
+ FLASK_DEBUG=False
18
+ FLASK_PORT=5001
19
+
20
+ # Railway Configuration
21
+ # ---------------------
22
+ # Railway otomatik olarak PORT environment variable set eder
23
+ # PORT=${PORT}
24
+
25
+ # CORS Configuration
26
+ # ------------------
27
+ # Frontend domain (production)
28
+ ALLOWED_ORIGINS=https://jira-duplicate-detection.web.app,https://jira-duplicate-detection.firebaseapp.com
29
+
30
+ # Development mode
31
+ # ----------------
32
+ # Development'ta Firebase yerine local storage kullanmak için:
33
+ # USE_FIREBASE_CACHE=False
34
+ USE_FIREBASE_CACHE=True
35
+
36
+ # Logging
37
+ # -------
38
+ LOG_LEVEL=INFO
39
+
firebase.json ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "hosting": {
3
+ "public": "web",
4
+ "ignore": [
5
+ "firebase.json",
6
+ "**/.*",
7
+ "**/node_modules/**"
8
+ ],
9
+ "rewrites": [
10
+ {
11
+ "source": "/",
12
+ "destination": "/login.html"
13
+ }
14
+ ],
15
+ "redirects": [
16
+ {
17
+ "source": "/signup",
18
+ "destination": "/register.html",
19
+ "type": 301
20
+ },
21
+ {
22
+ "source": "/login",
23
+ "destination": "/login.html",
24
+ "type": 301
25
+ }
26
+ ],
27
+ "headers": [
28
+ {
29
+ "source": "**/*.@(jpg|jpeg|gif|png|svg|webp|js|css)",
30
+ "headers": [
31
+ {
32
+ "key": "Cache-Control",
33
+ "value": "max-age=31536000"
34
+ }
35
+ ]
36
+ },
37
+ {
38
+ "source": "**/*.@(html)",
39
+ "headers": [
40
+ {
41
+ "key": "Cache-Control",
42
+ "value": "no-cache"
43
+ }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ }
49
+
firebase_setup_guide.md ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔥 Firebase Hosting Setup Rehberi
2
+
3
+ ## 1️⃣ Firebase Login
4
+
5
+ Terminal'de şu komutu çalıştır:
6
+
7
+ ```bash
8
+ firebase login
9
+ ```
10
+
11
+ Tarayıcı açılacak:
12
+ - **[email protected]** ile giriş yap
13
+ - "Allow Firebase CLI" tıkla
14
+ - Terminal'e dön, başarılı mesajı göreceksin
15
+
16
+ ---
17
+
18
+ ## 2️⃣ Firebase Projesi Oluştur
19
+
20
+ ### Seçenek A: Console'dan (Önerilen)
21
+
22
+ 1. **https://console.firebase.google.com** aç
23
+ 2. **[email protected]** ile giriş yap
24
+ 3. **"Add project"** tıkla
25
+ 4. Proje adı: **jira-duplicate-detection**
26
+ 5. Google Analytics: **Disable** (veya enable, tercihinize kalmış)
27
+ 6. **"Create project"** tıkla
28
+ 7. Proje ID'yi not al (örn: `jira-duplicate-detection-xxxxx`)
29
+
30
+ ### Seçenek B: CLI'dan
31
+
32
+ ```bash
33
+ firebase projects:create jira-duplicate-detection
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 3️⃣ Firebase Init (Bu Klasörde)
39
+
40
+ Terminal'de:
41
+
42
+ ```bash
43
+ cd /Users/cemirhans/Downloads/JIRA_DUPLICATE_DETECTION-main-2/bug-deduplication-github
44
+ firebase init
45
+ ```
46
+
47
+ ### Sorulacak Sorular ve Cevaplar:
48
+
49
+ **1. Which Firebase features?**
50
+ ```
51
+ ❯ ◯ Hosting: Configure files for Firebase Hosting
52
+ ```
53
+ → Space ile seç, Enter
54
+
55
+ **2. Project Setup**
56
+ ```
57
+ ❯ Use an existing project
58
+ ```
59
+ → Enter
60
+
61
+ **3. Select a default Firebase project**
62
+ ```
63
+ ❯ jira-duplicate-detection (jira-duplicate-detection-xxxxx)
64
+ ```
65
+ → Enter
66
+
67
+ **4. What do you want to use as your public directory?**
68
+ ```
69
+ ? web
70
+ ```
71
+ → `web` yaz, Enter
72
+
73
+ **5. Configure as a single-page app?**
74
+ ```
75
+ ? Yes
76
+ ```
77
+ → `y` Enter
78
+
79
+ **6. Set up automatic builds and deploys with GitHub?**
80
+ ```
81
+ ? No
82
+ ```
83
+ → `N` Enter
84
+
85
+ **7. File web/index.html already exists. Overwrite?**
86
+ ```
87
+ ? No
88
+ ```
89
+ → `N` Enter (önemli! mevcut dosyayı koruyoruz)
90
+
91
+ ---
92
+
93
+ ## 4️⃣ Firebase Deploy
94
+
95
+ ```bash
96
+ firebase deploy
97
+ ```
98
+
99
+ Çıktı:
100
+ ```
101
+ ✔ Deploy complete!
102
+
103
+ Project Console: https://console.firebase.google.com/project/jira-duplicate-detection-xxxxx/overview
104
+ Hosting URL: https://jira-duplicate-detection-xxxxx.web.app
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 5️⃣ Test Et
110
+
111
+ Tarayıcıda aç:
112
+ - **https://jira-duplicate-detection-xxxxx.web.app**
113
+
114
+ ---
115
+
116
+ ## ⚠️ Önemli Notlar
117
+
118
+ 1. **Backend Bağlantısı**:
119
+ - Şu anda frontend yayında ama backend lokal
120
+ - Backend'i de deploy etmek için Railway kullanacağız (sonraki adım)
121
+
122
+ 2. **API URL Güncellemesi**:
123
+ - Backend deploy olduktan sonra:
124
+ - `web/app.js` ve `web/create_report.js` içinde
125
+ - `API_BASE_URL` değiştir
126
+ - `firebase deploy` tekrar çalıştır
127
+
128
+ ---
129
+
130
+ ## 🎯 Özet Komutlar
131
+
132
+ ```bash
133
+ # 1. Login
134
+ firebase login
135
+
136
+ # 2. Init
137
+ cd /Users/cemirhans/Downloads/JIRA_DUPLICATE_DETECTION-main-2/bug-deduplication-github
138
+ firebase init
139
+
140
+ # 3. Deploy
141
+ firebase deploy
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 📋 Sonraki Adım: Backend Deploy
147
+
148
+ Backend için Railway kullanacağız:
149
+ 1. https://railway.app → GitHub ile giriş
150
+ 2. "New Project" → "Deploy from GitHub repo"
151
+ 3. esraacevik/JIRA_DUPLICATE_DETECTION seç
152
+ 4. Deploy
153
+
154
+ ---
155
+
156
+ ## 🆘 Sorun Çözme
157
+
158
+ **"Firebase command not found"**
159
+ ```bash
160
+ npm install -g firebase-tools
161
+ ```
162
+
163
+ **"Login failed"**
164
+ ```bash
165
+ firebase logout
166
+ firebase login --reauth
167
+ ```
168
+
169
+ **"Project not found"**
170
+ - Console'dan proje oluştur
171
+ - Proje ID'yi doğru yaz
172
+
173
+ ---
174
+
175
+ ## ✅ Başarı Kontrol
176
+
177
+ Şunları görmelisin:
178
+ ```
179
+ ✔ Firebase initialization complete!
180
+ ✔ Deploy complete!
181
+
182
+ Hosting URL: https://jira-duplicate-detection-xxxxx.web.app
183
+ ```
184
+
185
+ ---
186
+
187
+ 🎉 **İyi Şanslar!**
188
+
railway.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://railway.app/railway.schema.json",
3
+ "build": {
4
+ "builder": "NIXPACKS",
5
+ "buildCommand": "pip install -r requirements.txt"
6
+ },
7
+ "deploy": {
8
+ "startCommand": "python api_server.py",
9
+ "restartPolicyType": "ON_FAILURE",
10
+ "restartPolicyMaxRetries": 10,
11
+ "volumeMounts": [
12
+ {
13
+ "mountPath": "/app/data/user_embeddings",
14
+ "volumeName": "user-embeddings-storage"
15
+ }
16
+ ]
17
+ }
18
+ }
19
+
requirements.txt CHANGED
@@ -15,3 +15,5 @@ numpy>=1.24.0
15
  tqdm>=4.65.0
16
  flask>=2.3.0
17
  flask-cors>=4.0.0
 
 
 
15
  tqdm>=4.65.0
16
  flask>=2.3.0
17
  flask-cors>=4.0.0
18
+ firebase-admin>=6.0.0
19
+ python-dotenv>=1.0.0
src/firebase_storage_manager.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Firebase Storage Manager for Embedding Artifacts
3
+ Handles upload/download of user embeddings to Firebase Storage
4
+ """
5
+
6
+ import os
7
+ import logging
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Optional, Dict, Any
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class FirebaseStorageManager:
15
+ """Manage embedding artifacts in Firebase Storage"""
16
+
17
+ def __init__(self):
18
+ """Initialize Firebase Admin SDK"""
19
+ self.initialized = False
20
+ self.bucket = None
21
+
22
+ try:
23
+ import firebase_admin
24
+ from firebase_admin import credentials, storage
25
+
26
+ # Check if already initialized
27
+ if not firebase_admin._apps:
28
+ # Try to get credentials from environment or service account file
29
+ cred_path = os.getenv('FIREBASE_SERVICE_ACCOUNT')
30
+
31
+ if cred_path and os.path.exists(cred_path):
32
+ logger.info(f"🔥 Initializing Firebase with service account: {cred_path}")
33
+ cred = credentials.Certificate(cred_path)
34
+ else:
35
+ logger.info("🔥 Initializing Firebase with default credentials")
36
+ cred = credentials.ApplicationDefault()
37
+
38
+ # Get storage bucket from environment
39
+ bucket_name = os.getenv('FIREBASE_STORAGE_BUCKET')
40
+ if not bucket_name:
41
+ logger.warning("⚠️ FIREBASE_STORAGE_BUCKET not set, using default")
42
+ bucket_name = None
43
+
44
+ firebase_admin.initialize_app(cred, {
45
+ 'storageBucket': bucket_name
46
+ })
47
+
48
+ # Get storage bucket
49
+ self.bucket = storage.bucket()
50
+ self.initialized = True
51
+ logger.info(f"✅ Firebase Storage initialized: {self.bucket.name}")
52
+
53
+ except Exception as e:
54
+ logger.warning(f"⚠️ Firebase Storage not available: {e}")
55
+ logger.warning("📝 Running in local-only mode")
56
+ self.initialized = False
57
+
58
+ def get_user_artifacts_path(self, user_id: str) -> str:
59
+ """Get the Firebase Storage path for user artifacts"""
60
+ return f"user_embeddings/{user_id}"
61
+
62
+ def upload_user_artifacts(self, user_id: str, local_dir: Path) -> bool:
63
+ """
64
+ Upload user embedding artifacts to Firebase Storage
65
+
66
+ Args:
67
+ user_id: User ID
68
+ local_dir: Local directory containing artifacts
69
+
70
+ Returns:
71
+ True if successful, False otherwise
72
+ """
73
+ if not self.initialized:
74
+ logger.warning("⚠️ Firebase Storage not initialized, skipping upload")
75
+ return False
76
+
77
+ try:
78
+ logger.info(f"📤 Uploading artifacts for user {user_id}...")
79
+
80
+ # Files to upload
81
+ files_to_upload = [
82
+ 'embeddings.npy',
83
+ 'faiss_index.bin',
84
+ 'metadata.json',
85
+ 'data.csv'
86
+ ]
87
+
88
+ remote_path = self.get_user_artifacts_path(user_id)
89
+ uploaded_count = 0
90
+
91
+ for filename in files_to_upload:
92
+ local_file = local_dir / filename
93
+
94
+ if not local_file.exists():
95
+ logger.warning(f"⚠️ File not found: {local_file}")
96
+ continue
97
+
98
+ # Upload to Firebase Storage
99
+ blob = self.bucket.blob(f"{remote_path}/{filename}")
100
+ blob.upload_from_filename(str(local_file))
101
+
102
+ logger.info(f"✅ Uploaded: {filename} ({local_file.stat().st_size} bytes)")
103
+ uploaded_count += 1
104
+
105
+ logger.info(f"✅ Uploaded {uploaded_count}/{len(files_to_upload)} files for user {user_id}")
106
+ return uploaded_count > 0
107
+
108
+ except Exception as e:
109
+ logger.error(f"❌ Error uploading artifacts: {e}")
110
+ return False
111
+
112
+ def download_user_artifacts(self, user_id: str, local_dir: Path) -> bool:
113
+ """
114
+ Download user embedding artifacts from Firebase Storage
115
+
116
+ Args:
117
+ user_id: User ID
118
+ local_dir: Local directory to save artifacts
119
+
120
+ Returns:
121
+ True if successful, False otherwise
122
+ """
123
+ if not self.initialized:
124
+ logger.warning("⚠️ Firebase Storage not initialized, skipping download")
125
+ return False
126
+
127
+ try:
128
+ logger.info(f"📥 Downloading artifacts for user {user_id}...")
129
+
130
+ # Create local directory
131
+ local_dir.mkdir(parents=True, exist_ok=True)
132
+
133
+ # Files to download
134
+ files_to_download = [
135
+ 'embeddings.npy',
136
+ 'faiss_index.bin',
137
+ 'metadata.json',
138
+ 'data.csv'
139
+ ]
140
+
141
+ remote_path = self.get_user_artifacts_path(user_id)
142
+ downloaded_count = 0
143
+
144
+ for filename in files_to_download:
145
+ local_file = local_dir / filename
146
+ blob = self.bucket.blob(f"{remote_path}/{filename}")
147
+
148
+ # Check if file exists
149
+ if not blob.exists():
150
+ logger.warning(f"⚠️ Remote file not found: {remote_path}/{filename}")
151
+ continue
152
+
153
+ # Download from Firebase Storage
154
+ blob.download_to_filename(str(local_file))
155
+
156
+ logger.info(f"✅ Downloaded: {filename} ({local_file.stat().st_size} bytes)")
157
+ downloaded_count += 1
158
+
159
+ if downloaded_count == len(files_to_download):
160
+ logger.info(f"✅ Downloaded all {downloaded_count} files for user {user_id}")
161
+ return True
162
+ elif downloaded_count > 0:
163
+ logger.warning(f"⚠️ Partial download: {downloaded_count}/{len(files_to_download)} files")
164
+ return False
165
+ else:
166
+ logger.warning(f"⚠️ No artifacts found for user {user_id}")
167
+ return False
168
+
169
+ except Exception as e:
170
+ logger.error(f"❌ Error downloading artifacts: {e}")
171
+ return False
172
+
173
+ def check_artifacts_exist(self, user_id: str) -> bool:
174
+ """
175
+ Check if embedding artifacts exist in Firebase Storage
176
+
177
+ Args:
178
+ user_id: User ID
179
+
180
+ Returns:
181
+ True if artifacts exist, False otherwise
182
+ """
183
+ if not self.initialized:
184
+ return False
185
+
186
+ try:
187
+ remote_path = self.get_user_artifacts_path(user_id)
188
+
189
+ # Check for essential files
190
+ essential_files = ['embeddings.npy', 'faiss_index.bin', 'metadata.json']
191
+
192
+ for filename in essential_files:
193
+ blob = self.bucket.blob(f"{remote_path}/{filename}")
194
+ if not blob.exists():
195
+ return False
196
+
197
+ logger.info(f"✅ Artifacts exist for user {user_id}")
198
+ return True
199
+
200
+ except Exception as e:
201
+ logger.error(f"❌ Error checking artifacts: {e}")
202
+ return False
203
+
204
+ def get_artifact_metadata(self, user_id: str) -> Optional[Dict[str, Any]]:
205
+ """
206
+ Get metadata for user's artifacts
207
+
208
+ Args:
209
+ user_id: User ID
210
+
211
+ Returns:
212
+ Metadata dict or None
213
+ """
214
+ if not self.initialized:
215
+ return None
216
+
217
+ try:
218
+ remote_path = self.get_user_artifacts_path(user_id)
219
+ blob = self.bucket.blob(f"{remote_path}/metadata.json")
220
+
221
+ if not blob.exists():
222
+ return None
223
+
224
+ # Download and parse metadata
225
+ metadata_content = blob.download_as_string()
226
+ metadata = json.loads(metadata_content)
227
+
228
+ return metadata
229
+
230
+ except Exception as e:
231
+ logger.error(f"❌ Error getting metadata: {e}")
232
+ return None
233
+
234
+ def delete_user_artifacts(self, user_id: str) -> bool:
235
+ """
236
+ Delete user's artifacts from Firebase Storage
237
+
238
+ Args:
239
+ user_id: User ID
240
+
241
+ Returns:
242
+ True if successful, False otherwise
243
+ """
244
+ if not self.initialized:
245
+ logger.warning("⚠️ Firebase Storage not initialized, skipping delete")
246
+ return False
247
+
248
+ try:
249
+ logger.info(f"🗑️ Deleting artifacts for user {user_id}...")
250
+
251
+ remote_path = self.get_user_artifacts_path(user_id)
252
+
253
+ # List all blobs with this prefix
254
+ blobs = self.bucket.list_blobs(prefix=remote_path)
255
+
256
+ deleted_count = 0
257
+ for blob in blobs:
258
+ blob.delete()
259
+ deleted_count += 1
260
+
261
+ logger.info(f"✅ Deleted {deleted_count} files for user {user_id}")
262
+ return True
263
+
264
+ except Exception as e:
265
+ logger.error(f"❌ Error deleting artifacts: {e}")
266
+ return False
267
+
268
+
269
+ # Global instance
270
+ _storage_manager = None
271
+
272
+ def get_storage_manager() -> FirebaseStorageManager:
273
+ """Get or create Firebase Storage Manager singleton"""
274
+ global _storage_manager
275
+ if _storage_manager is None:
276
+ _storage_manager = FirebaseStorageManager()
277
+ return _storage_manager
278
+
src/user_embedding_pipeline.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ User-Specific Embedding Pipeline
4
+ ==================================
5
+ Her kullanıcının kendi verisi için embedding oluşturur.
6
+ Kullanıcılar birbirlerinin verilerini göremez.
7
+ """
8
+
9
+ import pandas as pd
10
+ import numpy as np
11
+ import faiss
12
+ import os
13
+ import json
14
+ from pathlib import Path
15
+ from sentence_transformers import SentenceTransformer
16
+ import logging
17
+
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class UserEmbeddingPipeline:
23
+ """Kullanıcıya özel embedding pipeline"""
24
+
25
+ def __init__(self, user_id, model_name='paraphrase-multilingual-MiniLM-L12-v2', use_firebase_cache=True):
26
+ """
27
+ Args:
28
+ user_id: Firebase user ID veya unique user identifier
29
+ model_name: Embedding model adı
30
+ use_firebase_cache: Firebase Storage cache kullanılsın mı?
31
+ """
32
+ self.user_id = user_id
33
+ self.model_name = model_name
34
+ self.model = None
35
+ self.use_firebase_cache = use_firebase_cache
36
+
37
+ # Kullanıcı için özel klasör
38
+ self.user_dir = Path(f"data/user_embeddings/{user_id}")
39
+ self.user_dir.mkdir(parents=True, exist_ok=True)
40
+
41
+ # Firebase Storage Manager
42
+ self.storage_manager = None
43
+ if use_firebase_cache:
44
+ try:
45
+ from firebase_storage_manager import get_storage_manager
46
+ self.storage_manager = get_storage_manager()
47
+ except Exception as e:
48
+ logger.warning(f"⚠️ Firebase Storage not available: {e}")
49
+
50
+ logger.info(f"🔧 User Embedding Pipeline initialized for user: {user_id}")
51
+
52
+ def load_model(self):
53
+ """Embedding modelini yükle"""
54
+ if self.model is None:
55
+ logger.info(f"📥 Loading embedding model: {self.model_name}")
56
+ self.model = SentenceTransformer(self.model_name)
57
+ logger.info("✅ Model loaded successfully")
58
+ return self.model
59
+
60
+ def create_embeddings(self, df, text_columns=['Summary', 'Description']):
61
+ """
62
+ DataFrame'den embedding oluştur
63
+
64
+ Args:
65
+ df: Pandas DataFrame (kullanıcının verisi)
66
+ text_columns: Embedding için kullanılacak text sütunları
67
+
68
+ Returns:
69
+ embeddings: numpy array (N x embedding_dim)
70
+ """
71
+ logger.info(f"🔄 Creating embeddings for {len(df)} records...")
72
+ logger.info(f"📝 Using columns: {text_columns}")
73
+
74
+ # Model'i yükle
75
+ model = self.load_model()
76
+
77
+ # Text'leri birleştir
78
+ texts = []
79
+ for _, row in df.iterrows():
80
+ text_parts = []
81
+ for col in text_columns:
82
+ if col in df.columns and pd.notna(row[col]):
83
+ text_parts.append(str(row[col]))
84
+
85
+ combined_text = '. '.join(text_parts).strip().lower()
86
+ texts.append(combined_text if combined_text else 'empty')
87
+
88
+ # Embeddings oluştur
89
+ logger.info("🤖 Generating embeddings...")
90
+ embeddings = model.encode(
91
+ texts,
92
+ batch_size=32,
93
+ show_progress_bar=True,
94
+ convert_to_numpy=True
95
+ )
96
+
97
+ logger.info(f"✅ Embeddings created: {embeddings.shape}")
98
+
99
+ # Kaydet
100
+ embeddings_path = self.user_dir / "embeddings.npy"
101
+ np.save(embeddings_path, embeddings)
102
+ logger.info(f"💾 Saved embeddings to: {embeddings_path}")
103
+
104
+ return embeddings
105
+
106
+ def create_faiss_index(self, embeddings):
107
+ """
108
+ FAISS index oluştur
109
+
110
+ Args:
111
+ embeddings: numpy array
112
+
113
+ Returns:
114
+ index: FAISS index
115
+ """
116
+ logger.info("🔄 Creating FAISS index...")
117
+
118
+ # Normalize embeddings (cosine similarity için)
119
+ normalized_embeddings = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
120
+
121
+ # FAISS index oluştur (Inner Product - cosine similarity)
122
+ embedding_dim = embeddings.shape[1]
123
+ index = faiss.IndexFlatIP(embedding_dim)
124
+
125
+ # Embeddings'leri ekle
126
+ index.add(normalized_embeddings.astype('float32'))
127
+
128
+ logger.info(f"✅ FAISS index created: {index.ntotal} vectors")
129
+
130
+ # Kaydet
131
+ index_path = self.user_dir / "faiss_index.index"
132
+ faiss.write_index(index, str(index_path))
133
+ logger.info(f"💾 Saved FAISS index to: {index_path}")
134
+
135
+ return index
136
+
137
+ def save_metadata(self, df, text_columns, config=None):
138
+ """
139
+ Metadata kaydet
140
+
141
+ Args:
142
+ df: DataFrame
143
+ text_columns: Kullanılan text sütunları
144
+ config: Ek konfigürasyon (opsiyonel)
145
+ """
146
+ current_time = pd.Timestamp.now().isoformat()
147
+ metadata = {
148
+ 'user_id': self.user_id,
149
+ 'num_records': len(df),
150
+ 'recordCount': len(df), # For compatibility
151
+ 'num_columns': len(df.columns),
152
+ 'columns': df.columns.tolist(),
153
+ 'textColumns': text_columns, # For compatibility
154
+ 'text_columns': text_columns,
155
+ 'metadataColumns': (config or {}).get('metadataColumns', []),
156
+ 'fileName': (config or {}).get('fileName', 'uploaded_data.csv'),
157
+ 'model_name': self.model_name,
158
+ 'embedding_dim': self.model.get_sentence_embedding_dimension() if self.model else None,
159
+ 'created_at': current_time,
160
+ 'createdAt': current_time, # For compatibility
161
+ 'config': config or {}
162
+ }
163
+
164
+ metadata_path = self.user_dir / "metadata.json"
165
+ with open(metadata_path, 'w') as f:
166
+ json.dump(metadata, f, indent=2)
167
+
168
+ logger.info(f"💾 Saved metadata to: {metadata_path}")
169
+
170
+ def process(self, df, text_columns=None, config=None):
171
+ """
172
+ Tam pipeline: embedding + FAISS index oluştur
173
+
174
+ FIREBASE CACHE SYSTEM:
175
+ 1. Firebase Storage'da artifacts varsa indir ve kullan
176
+ 2. Yoksa embedding yap ve Firebase'e upload et
177
+
178
+ Args:
179
+ df: Pandas DataFrame
180
+ text_columns: Text sütunları (None ise otomatik tespit)
181
+ config: Ek konfigürasyon
182
+
183
+ Returns:
184
+ success: bool
185
+ """
186
+ try:
187
+ logger.info(f"\n{'='*60}")
188
+ logger.info(f"🚀 Starting User Embedding Pipeline for: {self.user_id}")
189
+ logger.info(f"{'='*60}\n")
190
+
191
+ # ===== FIREBASE CACHE CHECK =====
192
+ if self.use_firebase_cache and self.storage_manager and self.storage_manager.initialized:
193
+ logger.info("🔍 Checking Firebase Storage for cached embeddings...")
194
+
195
+ # Check if artifacts exist in Firebase
196
+ artifacts_exist = self.storage_manager.check_artifacts_exist(self.user_id)
197
+
198
+ if artifacts_exist:
199
+ logger.info("✅ Found cached embeddings in Firebase Storage!")
200
+
201
+ # Download artifacts from Firebase
202
+ download_success = self.storage_manager.download_user_artifacts(
203
+ self.user_id,
204
+ self.user_dir
205
+ )
206
+
207
+ if download_success:
208
+ logger.info("✅ Successfully downloaded embeddings from Firebase!")
209
+ logger.info("⏩ Skipping embedding generation - using cached version")
210
+ return True
211
+ else:
212
+ logger.warning("⚠️ Download failed, will regenerate embeddings")
213
+ else:
214
+ logger.info("📝 No cached embeddings found, will generate new ones")
215
+
216
+ # ===== GENERATE NEW EMBEDDINGS =====
217
+ # Text sütunlarını tespit et
218
+ if text_columns is None:
219
+ text_columns = self._detect_text_columns(df)
220
+
221
+ logger.info(f"📊 Data shape: {df.shape}")
222
+ logger.info(f"📝 Text columns: {text_columns}")
223
+
224
+ # 1. Embeddings oluştur
225
+ embeddings = self.create_embeddings(df, text_columns)
226
+
227
+ # 2. FAISS index oluştur
228
+ self.create_faiss_index(embeddings)
229
+
230
+ # 3. Metadata kaydet
231
+ self.save_metadata(df, text_columns, config)
232
+
233
+ # 4. Upload to Firebase Storage (if enabled)
234
+ if self.use_firebase_cache and self.storage_manager and self.storage_manager.initialized:
235
+ logger.info("📤 Uploading embeddings to Firebase Storage...")
236
+
237
+ upload_success = self.storage_manager.upload_user_artifacts(
238
+ self.user_id,
239
+ self.user_dir
240
+ )
241
+
242
+ if upload_success:
243
+ logger.info("✅ Embeddings cached to Firebase Storage!")
244
+ else:
245
+ logger.warning("⚠️ Firebase upload failed, but local artifacts are saved")
246
+
247
+ logger.info(f"\n{'='*60}")
248
+ logger.info(f"✅ Pipeline completed successfully!")
249
+ logger.info(f"📁 Output directory: {self.user_dir}")
250
+ logger.info(f"{'='*60}\n")
251
+
252
+ return True
253
+
254
+ except Exception as e:
255
+ logger.error(f"❌ Pipeline error: {e}")
256
+ import traceback
257
+ traceback.print_exc()
258
+ return False
259
+
260
+ def _detect_text_columns(self, df):
261
+ """Otomatik text sütunlarını tespit et"""
262
+ text_keywords = ['summary', 'description', 'title', 'özet', 'açıklama', 'başlık', 'content']
263
+ text_columns = []
264
+
265
+ for col in df.columns:
266
+ col_lower = col.lower()
267
+ if any(keyword in col_lower for keyword in text_keywords):
268
+ text_columns.append(col)
269
+
270
+ # En az 1 sütun bulunmalı
271
+ if not text_columns:
272
+ # İlk string sütunu kullan
273
+ for col in df.columns:
274
+ if df[col].dtype == 'object':
275
+ text_columns.append(col)
276
+ if len(text_columns) >= 2:
277
+ break
278
+
279
+ return text_columns if text_columns else [df.columns[0]]
280
+
281
+
282
+ def create_user_embeddings(user_id, df, text_columns=None, config=None):
283
+ """
284
+ Kullanıcı için embedding oluştur (helper function)
285
+
286
+ Args:
287
+ user_id: User ID
288
+ df: DataFrame
289
+ text_columns: Text sütunları (None ise otomatik)
290
+ config: Konfigürasyon
291
+
292
+ Returns:
293
+ success: bool
294
+ """
295
+ pipeline = UserEmbeddingPipeline(user_id)
296
+ return pipeline.process(df, text_columns, config)
297
+
298
+
299
+ if __name__ == "__main__":
300
+ # Test için örnek kullanım
301
+ print("🧪 Testing User Embedding Pipeline...")
302
+
303
+ # Örnek veri
304
+ test_data = pd.DataFrame({
305
+ 'Summary': ['Test bug 1', 'Test bug 2', 'Test bug 3'],
306
+ 'Description': ['Desc 1', 'Desc 2', 'Desc 3'],
307
+ 'Priority': ['High', 'Medium', 'Low']
308
+ })
309
+
310
+ # Test kullanıcısı için pipeline
311
+ success = create_user_embeddings(
312
+ user_id='test_user_123',
313
+ df=test_data,
314
+ text_columns=['Summary', 'Description']
315
+ )
316
+
317
+ if success:
318
+ print("✅ Test successful!")
319
+ else:
320
+ print("❌ Test failed!")
321
+
src/user_hybrid_search.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ User-Specific Hybrid Search
4
+ ============================
5
+ Kullanıcıya özel embeddings kullanarak hybrid search
6
+ """
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ import faiss
11
+ import json
12
+ from pathlib import Path
13
+ from sentence_transformers import SentenceTransformer, CrossEncoder
14
+ import logging
15
+
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class UserHybridSearch:
21
+ """Kullanıcıya özel hybrid search sistemi"""
22
+
23
+ def __init__(self, user_id):
24
+ """
25
+ Args:
26
+ user_id: Firebase user ID veya unique user identifier
27
+ """
28
+ self.user_id = user_id
29
+ self.user_dir = Path(f"data/user_embeddings/{user_id}")
30
+
31
+ self.bi_encoder = None
32
+ self.cross_encoder = None
33
+ self.faiss_index = None
34
+ self.embeddings = None
35
+ self.metadata = None
36
+ self.df = None
37
+
38
+ logger.info(f"🔍 User Hybrid Search initialized for: {user_id}")
39
+
40
+ def load_models(self):
41
+ """Load models"""
42
+ if self.bi_encoder is None:
43
+ logger.info("📥 Loading bi-encoder...")
44
+ self.bi_encoder = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
45
+
46
+ if self.cross_encoder is None:
47
+ logger.info("📥 Loading cross-encoder...")
48
+ self.cross_encoder = CrossEncoder('cross-encoder/mmarco-mMiniLMv2-L12-H384-v1')
49
+
50
+ def load_user_data(self):
51
+ """Load user's embeddings, FAISS index, and metadata"""
52
+ try:
53
+ # Load metadata
54
+ metadata_path = self.user_dir / "metadata.json"
55
+ if not metadata_path.exists():
56
+ raise FileNotFoundError(f"Metadata not found for user: {self.user_id}")
57
+
58
+ with open(metadata_path, 'r') as f:
59
+ self.metadata = json.load(f)
60
+
61
+ logger.info(f"✅ Loaded metadata: {self.metadata['num_records']} records")
62
+
63
+ # Load embeddings
64
+ embeddings_path = self.user_dir / "embeddings.npy"
65
+ self.embeddings = np.load(embeddings_path)
66
+ logger.info(f"✅ Loaded embeddings: {self.embeddings.shape}")
67
+
68
+ # Load FAISS index
69
+ index_path = self.user_dir / "faiss_index.index"
70
+ self.faiss_index = faiss.read_index(str(index_path))
71
+ logger.info(f"✅ Loaded FAISS index: {self.faiss_index.ntotal} vectors")
72
+
73
+ return True
74
+
75
+ except Exception as e:
76
+ logger.error(f"❌ Error loading user data: {e}")
77
+ return False
78
+
79
+ def search(self, query, df=None, top_k=10, rerank_k=50):
80
+ """
81
+ Hybrid search: FAISS + Cross-Encoder
82
+
83
+ Args:
84
+ query: Search query string
85
+ df: DataFrame (kullanıcının verisi)
86
+ top_k: Final sonuç sayısı
87
+ rerank_k: FAISS'ten kaç candidate alınacak
88
+
89
+ Returns:
90
+ results: List of dicts
91
+ """
92
+ try:
93
+ logger.info(f"\n{'='*60}")
94
+ logger.info(f"🔍 User Hybrid Search: {self.user_id}")
95
+ logger.info(f"📝 Query: {query[:50]}...")
96
+ logger.info(f"{'='*60}\n")
97
+
98
+ # Load models
99
+ self.load_models()
100
+
101
+ # Load user data
102
+ if not self.load_user_data():
103
+ return []
104
+
105
+ # Encode query
106
+ logger.info("🔄 Encoding query...")
107
+ query_embedding = self.bi_encoder.encode([query.lower()], convert_to_numpy=True)
108
+
109
+ # Normalize for cosine similarity
110
+ query_embedding = query_embedding / np.linalg.norm(query_embedding)
111
+
112
+ # FAISS search (Stage 1)
113
+ logger.info(f"🔍 FAISS search (top {rerank_k} candidates)...")
114
+ scores, indices = self.faiss_index.search(query_embedding.astype('float32'), rerank_k)
115
+
116
+ candidates = []
117
+ for score, idx in zip(scores[0], indices[0]):
118
+ if idx < len(df):
119
+ candidates.append({
120
+ 'index': int(idx),
121
+ 'faiss_score': float(score),
122
+ 'row': df.iloc[idx].to_dict()
123
+ })
124
+
125
+ logger.info(f"✅ Found {len(candidates)} candidates from FAISS")
126
+
127
+ # Cross-Encoder re-ranking (Stage 2)
128
+ if len(candidates) > 0:
129
+ logger.info("🔄 Cross-Encoder re-ranking...")
130
+
131
+ # Prepare pairs for cross-encoder
132
+ text_col = self.metadata['text_columns'][0] if self.metadata['text_columns'] else df.columns[0]
133
+ pairs = []
134
+ for candidate in candidates:
135
+ row_text = str(candidate['row'].get(text_col, ''))
136
+ pairs.append([query, row_text])
137
+
138
+ # Get cross-encoder scores
139
+ ce_scores = self.cross_encoder.predict(pairs)
140
+
141
+ # Add scores to candidates
142
+ for i, candidate in enumerate(candidates):
143
+ candidate['cross_encoder_score'] = float(ce_scores[i])
144
+ candidate['final_score'] = float(ce_scores[i]) # Can be combined with FAISS score
145
+
146
+ # Sort by final score
147
+ candidates.sort(key=lambda x: x['final_score'], reverse=True)
148
+
149
+ logger.info(f"✅ Re-ranking completed")
150
+
151
+ # Format results
152
+ results = []
153
+ for candidate in candidates[:top_k]:
154
+ result = {
155
+ 'index': candidate['index'],
156
+ 'final_score': candidate['final_score'],
157
+ 'cross_encoder_score': candidate['cross_encoder_score'],
158
+ 'faiss_score': candidate['faiss_score'],
159
+ 'version_similarity': 1.0,
160
+ 'platform_match': True,
161
+ 'language_match': True
162
+ }
163
+
164
+ # Add all row data
165
+ result.update(candidate['row'])
166
+
167
+ results.append(result)
168
+
169
+ logger.info(f"✅ Returning {len(results)} results\n")
170
+
171
+ return results
172
+
173
+ except Exception as e:
174
+ logger.error(f"❌ Search error: {e}")
175
+ import traceback
176
+ traceback.print_exc()
177
+ return []
178
+
179
+
180
+ def search_user_data(user_id, query, df, top_k=10):
181
+ """
182
+ Helper function for user hybrid search
183
+
184
+ Args:
185
+ user_id: User ID
186
+ query: Search query
187
+ df: User's DataFrame
188
+ top_k: Number of results
189
+
190
+ Returns:
191
+ results: List of dicts
192
+ """
193
+ searcher = UserHybridSearch(user_id)
194
+ return searcher.search(query, df, top_k)
195
+
web/app.js CHANGED
@@ -65,6 +65,10 @@ async function performSearch(showLoading = true) {
65
  const systemConfig = JSON.parse(localStorage.getItem('systemConfig') || '{}');
66
  const selectedColumns = systemConfig.selectedColumns || ['Summary', 'Description'];
67
 
 
 
 
 
68
  // Prepare request data
69
  const requestData = {
70
  query: summary,
@@ -72,9 +76,12 @@ async function performSearch(showLoading = true) {
72
  platform: elements.platformSelect.value || null,
73
  version: elements.versionInput.value || null,
74
  top_k: 10,
75
- selected_columns: selectedColumns // Send selected columns to backend
 
76
  };
77
 
 
 
78
  try {
79
  const startTime = performance.now();
80
 
@@ -161,6 +168,30 @@ function createResultCard(result, rank) {
161
  // Determine match quality
162
  const { quality, emoji } = getMatchQuality(result.final_score);
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  // Build HTML
165
  card.innerHTML = `
166
  <div class="result-header">
@@ -173,15 +204,32 @@ function createResultCard(result, rank) {
173
  <span class="score-badge">Score: ${result.final_score.toFixed(4)}</span>
174
  </div>
175
 
176
- <div class="result-summary">${escapeHtml(result.summary)}</div>
177
 
178
- <div class="result-description">${escapeHtml(result.description)}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  <div class="result-meta">
181
- ${result.application ? `<span class="meta-tag"> <strong>${result.application}</strong></span>` : ''}
182
- ${result.platform ? `<span class="meta-tag"> <strong>${result.platform}</strong></span>` : ''}
183
- ${result.app_version && result.app_version !== 'N/A' ? `<span class="meta-tag"> <strong>${result.app_version}</strong></span>` : ''}
184
- ${result.priority ? `<span class="meta-tag"> ${result.priority}</span>` : ''}
185
  </div>
186
 
187
  <div class="result-scores">
@@ -217,6 +265,28 @@ function createResultCard(result, rank) {
217
  return card;
218
  }
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  // =============================================
221
  // Helper Functions
222
  // =============================================
@@ -266,9 +336,9 @@ function displayMockResults() {
266
  description: 'Kullanc mesaj gndermeye altnda uygulama aniden kapanyor. Bu durum zellikle uzun mesajlarda grlyor...',
267
  application: 'BiP',
268
  platform: 'android',
269
- app_version: '3.70.19',
270
- language: null,
271
  priority: 'high',
 
 
272
  cross_encoder_score: 5.2145,
273
  version_similarity: 1.0,
274
  platform_similarity: 1.0,
@@ -281,9 +351,9 @@ function displayMockResults() {
281
  description: 'Mesaj gnderme ilemi srasnda uygulama donuyor ve kapanyor...',
282
  application: 'BiP',
283
  platform: 'android',
284
- app_version: '3.70.18',
285
- language: null,
286
  priority: 'medium',
 
 
287
  cross_encoder_score: 4.8521,
288
  version_similarity: 0.9,
289
  platform_similarity: 1.0,
@@ -296,9 +366,9 @@ function displayMockResults() {
296
  description: 'BiP uygulamasnda mesaj yazp gnder butonuna bastmda uygulama kapanyor...',
297
  application: 'BiP',
298
  platform: 'android',
299
- app_version: '3.69.12',
300
- language: null,
301
  priority: 'high',
 
 
302
  cross_encoder_score: 4.5123,
303
  version_similarity: 0.7,
304
  platform_similarity: 1.0,
@@ -532,8 +602,12 @@ async function loadCategoricalOptions(allColumns) {
532
  if (!selectEl) continue;
533
 
534
  try {
 
 
 
 
535
  // Fetch unique values from backend
536
- const response = await fetch(`${API_BASE_URL}/column_values/${encodeURIComponent(columnName)}`);
537
  const data = await response.json();
538
 
539
  if (data.success && data.values) {
 
65
  const systemConfig = JSON.parse(localStorage.getItem('systemConfig') || '{}');
66
  const selectedColumns = systemConfig.selectedColumns || ['Summary', 'Description'];
67
 
68
+ // Get userId from session
69
+ const session = JSON.parse(localStorage.getItem('userSession'));
70
+ const userId = session?.uid || session?.username || 'anonymous';
71
+
72
  // Prepare request data
73
  const requestData = {
74
  query: summary,
 
76
  platform: elements.platformSelect.value || null,
77
  version: elements.versionInput.value || null,
78
  top_k: 10,
79
+ selected_columns: selectedColumns, // Send selected columns to backend
80
+ user_id: userId // Add user_id for user-specific search
81
  };
82
 
83
+ console.log('🔍 Searching with user_id:', userId);
84
+
85
  try {
86
  const startTime = performance.now();
87
 
 
168
  // Determine match quality
169
  const { quality, emoji } = getMatchQuality(result.final_score);
170
 
171
+ // Build metadata tags - show ALL properties except internal ones
172
+ const excludedKeys = ['final_score', 'cross_encoder_score', 'version_similarity',
173
+ 'platform_similarity', 'language_similarity', 'index',
174
+ 'summary', 'Summary', 'description', 'Description', 'desc'];
175
+
176
+ let metaTagsHTML = '';
177
+ for (const [key, value] of Object.entries(result)) {
178
+ if (!excludedKeys.includes(key) && value !== null && value !== undefined && value !== '' && value !== 'N/A') {
179
+ const displayKey = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
180
+ metaTagsHTML += `<span class="meta-tag" title="${displayKey}"><strong>${displayKey}:</strong> ${escapeHtml(String(value))}</span>`;
181
+ }
182
+ }
183
+
184
+ // Prepare description with "Read More" functionality
185
+ // Handle case-insensitive field names (Description vs description)
186
+ const descValue = result.description || result.Description || result.desc || '';
187
+ const summaryValue = result.summary || result.Summary || '';
188
+
189
+ const fullDescription = escapeHtml(descValue);
190
+ const shortDescription = descValue.length > 200
191
+ ? escapeHtml(descValue.substring(0, 200)) + '...'
192
+ : fullDescription;
193
+ const needsReadMore = descValue.length > 200;
194
+
195
  // Build HTML
196
  card.innerHTML = `
197
  <div class="result-header">
 
204
  <span class="score-badge">Score: ${result.final_score.toFixed(4)}</span>
205
  </div>
206
 
207
+ <div class="result-summary">${escapeHtml(summaryValue)}</div>
208
 
209
+ <div class="result-description-container">
210
+ <div class="result-description" data-full="${needsReadMore ? 'false' : 'true'}">
211
+ <span class="description-text">${shortDescription}</span>
212
+ <span class="description-full" style="display: none;">${fullDescription}</span>
213
+ </div>
214
+ ${needsReadMore ? `
215
+ <button class="read-more-btn" onclick="toggleDescription(this)" style="
216
+ background: none;
217
+ border: none;
218
+ color: var(--primary-color);
219
+ font-weight: 600;
220
+ cursor: pointer;
221
+ padding: 4px 0;
222
+ margin-top: 4px;
223
+ font-size: 0.9rem;
224
+ text-decoration: underline;
225
+ ">
226
+ Devamını Oku
227
+ </button>
228
+ ` : ''}
229
+ </div>
230
 
231
  <div class="result-meta">
232
+ ${metaTagsHTML}
 
 
 
233
  </div>
234
 
235
  <div class="result-scores">
 
265
  return card;
266
  }
267
 
268
+ // Toggle description visibility
269
+ function toggleDescription(button) {
270
+ const container = button.previousElementSibling;
271
+ const shortText = container.querySelector('.description-text');
272
+ const fullText = container.querySelector('.description-full');
273
+ const isFull = container.getAttribute('data-full') === 'true';
274
+
275
+ if (isFull) {
276
+ // Show short version
277
+ shortText.style.display = 'inline';
278
+ fullText.style.display = 'none';
279
+ button.innerHTML = ' Devamını Oku';
280
+ container.setAttribute('data-full', 'false');
281
+ } else {
282
+ // Show full version
283
+ shortText.style.display = 'none';
284
+ fullText.style.display = 'inline';
285
+ button.innerHTML = ' Daha Az Göster';
286
+ container.setAttribute('data-full', 'true');
287
+ }
288
+ }
289
+
290
  // =============================================
291
  // Helper Functions
292
  // =============================================
 
336
  description: 'Kullanc mesaj gndermeye altnda uygulama aniden kapanyor. Bu durum zellikle uzun mesajlarda grlyor...',
337
  application: 'BiP',
338
  platform: 'android',
 
 
339
  priority: 'high',
340
+ component: 'Android Client',
341
+ severity: 'Critical',
342
  cross_encoder_score: 5.2145,
343
  version_similarity: 1.0,
344
  platform_similarity: 1.0,
 
351
  description: 'Mesaj gnderme ilemi srasnda uygulama donuyor ve kapanyor...',
352
  application: 'BiP',
353
  platform: 'android',
 
 
354
  priority: 'medium',
355
+ component: 'Android Client',
356
+ severity: 'High',
357
  cross_encoder_score: 4.8521,
358
  version_similarity: 0.9,
359
  platform_similarity: 1.0,
 
366
  description: 'BiP uygulamasnda mesaj yazp gnder butonuna bastmda uygulama kapanyor...',
367
  application: 'BiP',
368
  platform: 'android',
 
 
369
  priority: 'high',
370
+ component: 'Android Client',
371
+ severity: 'Critical',
372
  cross_encoder_score: 4.5123,
373
  version_similarity: 0.7,
374
  platform_similarity: 1.0,
 
602
  if (!selectEl) continue;
603
 
604
  try {
605
+ // Get current user's userId
606
+ const session = JSON.parse(localStorage.getItem('userSession'));
607
+ const userId = session?.uid || session?.username || 'anonymous';
608
+
609
  // Fetch unique values from backend
610
+ const response = await fetch(`${API_BASE_URL}/column_values/${encodeURIComponent(columnName)}?user_id=${encodeURIComponent(userId)}`);
611
  const data = await response.json();
612
 
613
  if (data.success && data.values) {
web/column_mapping.html CHANGED
@@ -543,12 +543,17 @@
543
  // If custom data is uploaded, send selected columns to backend
544
  if (dataSelection === 'new' && uploadedDataInfo) {
545
  try {
 
 
 
 
546
  const response = await fetch('http://localhost:5001/api/update_selected_columns', {
547
  method: 'POST',
548
  headers: {
549
  'Content-Type': 'application/json'
550
  },
551
  body: JSON.stringify({
 
552
  selectedColumns: selectedColumns,
553
  metadataColumns: selectedMetadataColumns
554
  })
 
543
  // If custom data is uploaded, send selected columns to backend
544
  if (dataSelection === 'new' && uploadedDataInfo) {
545
  try {
546
+ // Get current user's userId
547
+ const session = JSON.parse(localStorage.getItem('userSession'));
548
+ const userId = session?.uid || session?.username || 'anonymous';
549
+
550
  const response = await fetch('http://localhost:5001/api/update_selected_columns', {
551
  method: 'POST',
552
  headers: {
553
  'Content-Type': 'application/json'
554
  },
555
  body: JSON.stringify({
556
+ userId: userId,
557
  selectedColumns: selectedColumns,
558
  metadataColumns: selectedMetadataColumns
559
  })
web/create_report.html CHANGED
@@ -66,13 +66,10 @@
66
  <!-- Header -->
67
  <header class="header">
68
  <div class="logo">
69
- <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
70
- <rect width="32" height="32" rx="8" fill="#FFCC00"/>
71
- <path d="M16 8L22 14L16 20L10 14L16 8Z" fill="#000000"/>
72
- <path d="M16 12L20 16L16 20L12 16L16 12Z" fill="#333333"/>
73
- </svg>
74
  <h1>Bug Rapor Sistemi</h1>
75
- <span style="background: #FFCC00; color: #000; padding: 4px 12px; border-radius: 4px; font-size: 0.7rem; font-weight: 700; margin-left: 12px;">TURKCELL</span>
76
  </div>
77
  </header>
78
 
 
66
  <!-- Header -->
67
  <header class="header">
68
  <div class="logo">
69
+ <img src="https://ffo3gv1cf3ir.merlincdn.net/SiteAssets/Bireysel/Navigasyon/turkcell-logo.png?20251016_03"
70
+ alt="Turkcell Logo"
71
+ style="height: 32px; margin-right: 12px;">
 
 
72
  <h1>Bug Rapor Sistemi</h1>
 
73
  </div>
74
  </header>
75
 
web/create_report.js CHANGED
@@ -92,15 +92,32 @@ async function handleFormSubmit(e) {
92
  // Check if we're replacing an old report
93
  if (reportToReplace) {
94
  console.log('✅ reportToReplace is truthy, adding replace params...');
 
 
 
 
 
 
 
 
95
  formData.replace_report = true;
96
- formData.old_report_summary = reportToReplace.summary;
97
- formData.old_report_id = reportToReplace.reportId;
 
98
  console.log('🔄 Replacing old report:', reportToReplace);
99
  console.log('📤 formData with replace:', formData);
100
  } else {
101
  console.log('❌ reportToReplace is falsy, NOT replacing');
102
  }
103
 
 
 
 
 
 
 
 
 
104
  // Send to API
105
  const response = await fetch(`${API_BASE_URL}/create_report`, {
106
  method: 'POST',
@@ -125,6 +142,7 @@ async function handleFormSubmit(e) {
125
  if (successMessage) {
126
  // Update message based on whether we replaced a report
127
  if (reportToReplace) {
 
128
  const successContent = successMessage.querySelector('strong');
129
  if (successContent) {
130
  successContent.innerHTML = `
@@ -134,9 +152,31 @@ async function handleFormSubmit(e) {
134
  }
135
  const successPara = successMessage.querySelector('p');
136
  if (successPara) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  successPara.innerHTML = `
138
- Eski rapor silindi ve yeni rapor kaydedildi.<br>
139
- Rapor ID: <strong id="reportId">${data.report_id || 'N/A'}</strong>
140
  `;
141
  }
142
  }
@@ -144,10 +184,10 @@ async function handleFormSubmit(e) {
144
  successMessage.classList.add('show');
145
  successMessage.scrollIntoView({ behavior: 'smooth', block: 'center' });
146
 
147
- // Hide success message after 5 seconds
148
  setTimeout(() => {
149
  successMessage.classList.remove('show');
150
- }, 5000);
151
  }
152
 
153
  // Clear replace report state
@@ -157,8 +197,20 @@ async function handleFormSubmit(e) {
157
  replaceMessage.remove();
158
  }
159
 
 
 
 
 
 
 
160
  // Reset form
161
  form.reset();
 
 
 
 
 
 
162
  } else {
163
  throw new Error(data.error || 'Kaydetme baarsz');
164
  }
@@ -190,8 +242,12 @@ async function searchSimilarReports() {
190
  const component = componentEl.value;
191
  const appVersion = appVersionEl ? appVersionEl.value.trim() : '';
192
 
 
 
 
193
  // Need at least summary and component
194
  if (!summary || summary.length < 10 || !component) {
 
195
  if (similarReportsSection) {
196
  similarReportsSection.style.display = 'none';
197
  }
@@ -235,15 +291,20 @@ async function searchSimilarReports() {
235
  similarReportsSection.style.display = 'block';
236
 
237
  // Build search request
 
 
 
238
  const searchRequest = {
239
  query: summary,
240
- top_k: 5
 
241
  };
242
 
243
  if (application) searchRequest.application = application;
244
  if (platform) searchRequest.platform = platform;
245
  if (appVersion) searchRequest.version = appVersion;
246
 
 
247
  // Call API
248
  const response = await fetch(`${API_BASE_URL}/search`, {
249
  method: 'POST',
@@ -252,9 +313,11 @@ async function searchSimilarReports() {
252
  });
253
 
254
  if (!response.ok) {
 
255
  throw new Error(`HTTP ${response.status}`);
256
  }
257
 
 
258
  const data = await response.json();
259
 
260
  // Display results
@@ -268,11 +331,13 @@ async function searchSimilarReports() {
268
  }
269
 
270
  } catch (error) {
271
- console.error('Similar reports search error:', error);
 
272
  const similarReportsSection = document.getElementById('similarReportsSection');
273
  if (similarReportsSection) {
274
  similarReportsSection.style.display = 'none';
275
  }
 
276
  }
277
  }
278
 
@@ -297,7 +362,31 @@ function displaySimilarReports(results) {
297
  // Copy result card HTML from app.js style
298
  similarReportsList.innerHTML = results.map((result, index) => {
299
  const matchQuality = getMatchQuality(result.final_score);
300
- const versionDisplay = result.app_version || 'N/A';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
  return `
303
  <div class="result-card">
@@ -307,14 +396,32 @@ function displaySimilarReports(results) {
307
  <span class="result-score">Score: ${result.final_score.toFixed(4)}</span>
308
  </div>
309
 
310
- <h3 class="result-title">${escapeHtml(result.summary)}</h3>
311
- <p class="result-description">${escapeHtml(result.description.substring(0, 150))}...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
  <div class="result-meta">
314
- <span class="meta-tag" title="Application"> ${result.application || 'Unknown'}</span>
315
- <span class="meta-tag" title="Platform"> ${result.platform || 'unknown'}</span>
316
- <span class="meta-tag" title="Version"> ${versionDisplay}</span>
317
- <span class="meta-tag" title="Priority"> ${result.priority || 'none'}</span>
318
  </div>
319
 
320
  <div class="result-scores">
@@ -328,7 +435,7 @@ function displaySimilarReports(results) {
328
  </div>
329
  <div class="score-item">
330
  <span class="score-label">Platform:</span>
331
- <span class="score-value">${result.platform_match ? '' : ''}</span>
332
  </div>
333
  </div>
334
 
@@ -336,7 +443,7 @@ function displaySimilarReports(results) {
336
  <button
337
  class="replace-report-btn btn btn-secondary"
338
  data-index="${index}"
339
- data-summary="${result.summary}"
340
  data-report-id="${result.report_id || index}"
341
  style="flex: 1; padding: 8px 16px; font-size: 0.85rem; background: linear-gradient(135deg, #FF9500 0%, #FF6B00 100%); color: white; border: none;"
342
  title="Bu rapor duplicate ise, yeni raporu bu eskisinin yerine kaydet"
@@ -352,7 +459,7 @@ function displaySimilarReports(results) {
352
  `;
353
  }).join('');
354
 
355
- // Attach event listeners to replace buttons
356
  setTimeout(() => {
357
  const replaceButtons = document.querySelectorAll('.replace-report-btn');
358
  replaceButtons.forEach(button => {
@@ -364,6 +471,33 @@ function displaySimilarReports(results) {
364
  replaceReport(index, summary, reportId);
365
  });
366
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  }, 100);
368
  }
369
 
@@ -859,7 +993,11 @@ async function loadCategoricalFieldOptions() {
859
  if (!columnName) continue;
860
 
861
  try {
862
- const response = await fetch(`${API_BASE_URL}/column_values/${encodeURIComponent(columnName)}`);
 
 
 
 
863
  const data = await response.json();
864
 
865
  if (data.success && data.values) {
 
92
  // Check if we're replacing an old report
93
  if (reportToReplace) {
94
  console.log('✅ reportToReplace is truthy, adding replace params...');
95
+
96
+ // IMPORTANT: Always use the CURRENT summary from reportToReplace
97
+ // This ensures we're replacing the most up-to-date version
98
+ const oldSummary = reportToReplace.summary || reportToReplace.Summary || '';
99
+
100
+ console.log('🔍 Old report to replace:', oldSummary);
101
+ console.log('🆕 New summary:', formData.summary);
102
+
103
  formData.replace_report = true;
104
+ formData.old_report_summary = oldSummary;
105
+ formData.old_report_id = reportToReplace.reportId || reportToReplace.report_id || '';
106
+
107
  console.log('🔄 Replacing old report:', reportToReplace);
108
  console.log('📤 formData with replace:', formData);
109
  } else {
110
  console.log('❌ reportToReplace is falsy, NOT replacing');
111
  }
112
 
113
+ // Add userId to formData
114
+ const session = JSON.parse(localStorage.getItem('userSession'));
115
+ const userId = session?.uid || session?.username || 'anonymous';
116
+ formData.userId = userId;
117
+
118
+ console.log('📤 Sending formData to backend:', formData);
119
+ console.log('👤 User ID:', userId);
120
+
121
  // Send to API
122
  const response = await fetch(`${API_BASE_URL}/create_report`, {
123
  method: 'POST',
 
142
  if (successMessage) {
143
  // Update message based on whether we replaced a report
144
  if (reportToReplace) {
145
+ console.log('✅ Report replacement successful, clearing reportToReplace');
146
  const successContent = successMessage.querySelector('strong');
147
  if (successContent) {
148
  successContent.innerHTML = `
 
152
  }
153
  const successPara = successMessage.querySelector('p');
154
  if (successPara) {
155
+ const embeddingMsg = data.embeddings_updated
156
+ ? '<span style="color: #10B981; font-size: 0.95rem; display: block; margin-top: 8px;">✅ <strong>Embedding güncellendi!</strong> Yeni rapor arama sonuçlarında görünüyor.</span>'
157
+ : '<span style="color: #F59E0B; font-size: 0.95rem; display: block; margin-top: 8px;">⚠️ Embedding güncellenemedi. Lütfen veriyi yeniden yükleyin.</span>';
158
+
159
+ successPara.innerHTML = `
160
+ <strong style="font-size: 1.1rem;">Eski rapor silindi, yeni rapor kaydedildi!</strong><br>
161
+ Rapor ID: <strong id="reportId">${data.report_id || 'N/A'}</strong><br>
162
+ ${embeddingMsg}
163
+ `;
164
+ }
165
+
166
+ // CRITICAL: Clear reportToReplace after successful replacement
167
+ reportToReplace = null;
168
+ console.log('🧹 reportToReplace cleared');
169
+ } else {
170
+ // Regular save message
171
+ const successPara = successMessage.querySelector('p');
172
+ if (successPara) {
173
+ const embeddingMsg = data.embeddings_updated
174
+ ? '<span style="color: #10B981; font-size: 0.95rem; display: block; margin-top: 8px;">✅ <strong>Embedding oluşturuldu!</strong> Rapor arama sonuçlarında görünüyor.</span>'
175
+ : '<span style="color: #F59E0B; font-size: 0.95rem; display: block; margin-top: 8px;">⚠️ Embedding oluşturulamadı. Lütfen veriyi yeniden yükleyin.</span>';
176
+
177
  successPara.innerHTML = `
178
+ Rapor ID: <strong id="reportId">${data.report_id || 'N/A'}</strong><br>
179
+ ${embeddingMsg}
180
  `;
181
  }
182
  }
 
184
  successMessage.classList.add('show');
185
  successMessage.scrollIntoView({ behavior: 'smooth', block: 'center' });
186
 
187
+ // Hide success message after 7 seconds (increased for embedding message)
188
  setTimeout(() => {
189
  successMessage.classList.remove('show');
190
+ }, 7000);
191
  }
192
 
193
  // Clear replace report state
 
197
  replaceMessage.remove();
198
  }
199
 
200
+ // Cancel any pending search
201
+ if (searchTimeout) {
202
+ clearTimeout(searchTimeout);
203
+ searchTimeout = null;
204
+ }
205
+
206
  // Reset form
207
  form.reset();
208
+
209
+ // Hide similar reports section after reset
210
+ const similarReportsSection = document.getElementById('similarReportsSection');
211
+ if (similarReportsSection) {
212
+ similarReportsSection.style.display = 'none';
213
+ }
214
  } else {
215
  throw new Error(data.error || 'Kaydetme baarsz');
216
  }
 
242
  const component = componentEl.value;
243
  const appVersion = appVersionEl ? appVersionEl.value.trim() : '';
244
 
245
+ // Get similar reports section
246
+ const similarReportsSection = document.getElementById('similarReportsSection');
247
+
248
  // Need at least summary and component
249
  if (!summary || summary.length < 10 || !component) {
250
+ console.log('⏸️ Search skipped: insufficient data (summary length:', summary.length, ', component:', component, ')');
251
  if (similarReportsSection) {
252
  similarReportsSection.style.display = 'none';
253
  }
 
291
  similarReportsSection.style.display = 'block';
292
 
293
  // Build search request
294
+ const session = JSON.parse(localStorage.getItem('userSession'));
295
+ const userId = session?.uid || session?.username || 'anonymous';
296
+
297
  const searchRequest = {
298
  query: summary,
299
+ top_k: 5,
300
+ user_id: userId // Add user_id for user-specific search
301
  };
302
 
303
  if (application) searchRequest.application = application;
304
  if (platform) searchRequest.platform = platform;
305
  if (appVersion) searchRequest.version = appVersion;
306
 
307
+
308
  // Call API
309
  const response = await fetch(`${API_BASE_URL}/search`, {
310
  method: 'POST',
 
313
  });
314
 
315
  if (!response.ok) {
316
+ console.warn('Search response not OK:', response.status, response.statusText);
317
  throw new Error(`HTTP ${response.status}`);
318
  }
319
 
320
+ // Parse JSON response
321
  const data = await response.json();
322
 
323
  // Display results
 
331
  }
332
 
333
  } catch (error) {
334
+ // Silently handle search errors - they're not critical
335
+ console.warn('⚠️ Similar reports search failed (non-critical):', error.message);
336
  const similarReportsSection = document.getElementById('similarReportsSection');
337
  if (similarReportsSection) {
338
  similarReportsSection.style.display = 'none';
339
  }
340
+ // Don't throw or alert - search is optional
341
  }
342
  }
343
 
 
362
  // Copy result card HTML from app.js style
363
  similarReportsList.innerHTML = results.map((result, index) => {
364
  const matchQuality = getMatchQuality(result.final_score);
365
+
366
+ // Build metadata tags - show ALL properties except internal ones
367
+ const excludedKeys = ['final_score', 'cross_encoder_score', 'version_similarity',
368
+ 'platform_similarity', 'language_similarity', 'index',
369
+ 'summary', 'Summary', 'description', 'Description', 'desc',
370
+ 'platform_match', 'report_id'];
371
+
372
+ let metaTagsHTML = '';
373
+ for (const [key, value] of Object.entries(result)) {
374
+ if (!excludedKeys.includes(key) && value !== null && value !== undefined && value !== '' && value !== 'N/A') {
375
+ const displayKey = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
376
+ metaTagsHTML += `<span class="meta-tag" title="${displayKey}"><strong>${displayKey}:</strong> ${escapeHtml(String(value))}</span>`;
377
+ }
378
+ }
379
+
380
+ // Prepare description with "Read More" functionality
381
+ // Handle case-insensitive field names (Description vs description)
382
+ const descValue = result.description || result.Description || result.desc || '';
383
+ const summaryValue = result.summary || result.Summary || '';
384
+
385
+ const fullDescription = escapeHtml(descValue);
386
+ const shortDescription = descValue.length > 200
387
+ ? escapeHtml(descValue.substring(0, 200)) + '...'
388
+ : fullDescription;
389
+ const needsReadMore = descValue.length > 200;
390
 
391
  return `
392
  <div class="result-card">
 
396
  <span class="result-score">Score: ${result.final_score.toFixed(4)}</span>
397
  </div>
398
 
399
+ <h3 class="result-title">${escapeHtml(summaryValue)}</h3>
400
+
401
+ <div class="result-description-container">
402
+ <div class="result-description" data-full="${needsReadMore ? 'false' : 'true'}" data-card-index="${index}">
403
+ <span class="description-text">${shortDescription}</span>
404
+ <span class="description-full" style="display: none;">${fullDescription}</span>
405
+ </div>
406
+ ${needsReadMore ? `
407
+ <button class="read-more-btn" data-card-index="${index}" style="
408
+ background: none;
409
+ border: none;
410
+ color: var(--primary-color);
411
+ font-weight: 600;
412
+ cursor: pointer;
413
+ padding: 4px 0;
414
+ margin-top: 4px;
415
+ font-size: 0.9rem;
416
+ text-decoration: underline;
417
+ ">
418
+ 📖 Devamını Oku
419
+ </button>
420
+ ` : ''}
421
+ </div>
422
 
423
  <div class="result-meta">
424
+ ${metaTagsHTML}
 
 
 
425
  </div>
426
 
427
  <div class="result-scores">
 
435
  </div>
436
  <div class="score-item">
437
  <span class="score-label">Platform:</span>
438
+ <span class="score-value">${result.platform_match ? '' : ''}</span>
439
  </div>
440
  </div>
441
 
 
443
  <button
444
  class="replace-report-btn btn btn-secondary"
445
  data-index="${index}"
446
+ data-summary="${escapeHtml(summaryValue)}"
447
  data-report-id="${result.report_id || index}"
448
  style="flex: 1; padding: 8px 16px; font-size: 0.85rem; background: linear-gradient(135deg, #FF9500 0%, #FF6B00 100%); color: white; border: none;"
449
  title="Bu rapor duplicate ise, yeni raporu bu eskisinin yerine kaydet"
 
459
  `;
460
  }).join('');
461
 
462
+ // Attach event listeners to replace buttons and read more buttons
463
  setTimeout(() => {
464
  const replaceButtons = document.querySelectorAll('.replace-report-btn');
465
  replaceButtons.forEach(button => {
 
471
  replaceReport(index, summary, reportId);
472
  });
473
  });
474
+
475
+ // Attach read more button listeners
476
+ const readMoreButtons = document.querySelectorAll('.read-more-btn');
477
+ readMoreButtons.forEach(button => {
478
+ button.addEventListener('click', function(e) {
479
+ e.preventDefault();
480
+ const cardIndex = this.getAttribute('data-card-index');
481
+ const container = document.querySelector(`.result-description[data-card-index="${cardIndex}"]`);
482
+ const shortText = container.querySelector('.description-text');
483
+ const fullText = container.querySelector('.description-full');
484
+ const isFull = container.getAttribute('data-full') === 'true';
485
+
486
+ if (isFull) {
487
+ // Show short version
488
+ shortText.style.display = 'inline';
489
+ fullText.style.display = 'none';
490
+ this.innerHTML = '📖 Devamını Oku';
491
+ container.setAttribute('data-full', 'false');
492
+ } else {
493
+ // Show full version
494
+ shortText.style.display = 'none';
495
+ fullText.style.display = 'inline';
496
+ this.innerHTML = '📕 Daha Az Göster';
497
+ container.setAttribute('data-full', 'true');
498
+ }
499
+ });
500
+ });
501
  }, 100);
502
  }
503
 
 
993
  if (!columnName) continue;
994
 
995
  try {
996
+ // Get current user's userId
997
+ const session = JSON.parse(localStorage.getItem('userSession'));
998
+ const userId = session?.uid || session?.username || 'anonymous';
999
+
1000
+ const response = await fetch(`${API_BASE_URL}/column_values/${encodeURIComponent(columnName)}?user_id=${encodeURIComponent(userId)}`);
1001
  const data = await response.json();
1002
 
1003
  if (data.success && data.values) {
web/data_selection.html CHANGED
@@ -6,6 +6,12 @@
6
  <title>Data Selection - Bug Report System</title>
7
  <link rel="stylesheet" href="style.css">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
 
 
 
 
 
 
9
  <style>
10
  .selection-container {
11
  max-width: 900px;
@@ -227,12 +233,17 @@
227
  <div class="selection-container" style="position: relative; z-index: 1;">
228
  <!-- Welcome Banner -->
229
  <div class="welcome-banner">
230
- <div>
231
- <h1 style="margin: 0 0 var(--spacing-xs) 0;">Hoş Geldiniz, <span id="userName"></span>!</h1>
232
- <p style="margin: 0; opacity: 0.9;">Duplicate detection sistemini kullanmaya başlayalım</p>
 
 
 
 
 
233
  </div>
234
  <button onclick="logout()" class="btn btn-secondary" style="background: rgba(0,0,0,0.2); color: white; border: none;">
235
- Çıkış
236
  </button>
237
  </div>
238
 
@@ -244,14 +255,12 @@
244
  <div class="data-option" onclick="selectOption('existing')" id="optionExisting">
245
  <div class="electric-spark"></div>
246
  <div class="option-title">Mevcut Veri Setini Kullan</div>
247
- <div class="option-description">
248
- Sistemde yüklü olan <strong>data_with_application.csv</strong> dosyasını kullan
249
  </div>
250
- <ul class="option-features">
251
- <li>14,268 adet bug raporu</li>
252
- <li>Embeddings ve FAISS index hazır</li>
253
- <li>Hızlı başlangıç (anında kullanıma hazır)</li>
254
- <li>BiP, TV+, Fizy ve diğer Turkcell uygulamaları</li>
255
  </ul>
256
  </div>
257
 
@@ -283,45 +292,142 @@
283
  </button>
284
  </div>
285
 
 
 
 
286
  <script>
287
  let selectedOption = null;
288
  const API_BASE_URL = 'http://localhost:5001/api';
 
289
 
290
- // Check if user is logged in
291
- const session = JSON.parse(localStorage.getItem('userSession'));
292
- if (!session) {
293
- window.location.href = 'login.html';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  }
295
 
296
- // Display user name
297
- document.getElementById('userName').textContent = session.name;
298
-
299
- // Check for existing uploaded data on page load
300
- checkForUploadedData();
 
 
 
 
 
 
 
 
301
 
302
  async function checkForUploadedData() {
303
  try {
304
- const response = await fetch(`${API_BASE_URL}/data_status`);
 
 
 
 
305
  const data = await response.json();
306
 
307
- if (data.customDataLoaded) {
308
- // Update the existing data option to show the uploaded data
309
- const optionExisting = document.getElementById('optionExisting');
310
- const optionTitle = optionExisting.querySelector('.option-title');
311
- const optionDesc = optionExisting.querySelector('.option-description');
312
- const optionFeatures = optionExisting.querySelector('.option-features');
313
 
 
 
 
 
 
 
 
314
  optionTitle.innerHTML = 'Mevcut Veri Setini Kullan <span style="background: #10B981; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; margin-left: 8px;">YÜKLÜ</span>';
315
  optionDesc.innerHTML = `Sistemde yüklü olan <strong>${data.fileName}</strong> dosyasını kullan`;
316
  optionFeatures.innerHTML = `
317
- <li>${data.rowCount.toLocaleString('tr-TR')} adet bug raporu</li>
318
- <li>Yüklenme tarihi: ${new Date(data.uploadedAt).toLocaleString('tr-TR')}</li>
319
- <li>${data.columnCount} adet sütun</li>
320
- <li>Hızlı başlangıç (anında kullanıma hazır)</li>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  `;
322
  }
323
  } catch (error) {
324
- console.log('No custom data loaded, showing default info');
 
 
 
 
 
 
 
 
 
325
  }
326
  }
327
 
@@ -362,10 +468,26 @@
362
  }
363
 
364
  // Logout
365
- function logout() {
366
- localStorage.removeItem('userSession');
367
- localStorage.removeItem('dataSelection');
368
- window.location.href = 'login.html';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  }
370
 
371
  // Create animated stars
 
6
  <title>Data Selection - Bug Report System</title>
7
  <link rel="stylesheet" href="style.css">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+
10
+ <!-- Firebase SDKs -->
11
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
12
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js"></script>
13
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore-compat.js"></script>
14
+
15
  <style>
16
  .selection-container {
17
  max-width: 900px;
 
233
  <div class="selection-container" style="position: relative; z-index: 1;">
234
  <!-- Welcome Banner -->
235
  <div class="welcome-banner">
236
+ <div style="display: flex; align-items: center; gap: 20px;">
237
+ <img src="https://ffo3gv1cf3ir.merlincdn.net/SiteAssets/Bireysel/Navigasyon/turkcell-logo.png?20251016_03"
238
+ alt="Turkcell Logo"
239
+ style="height: 50px; filter: brightness(0);">
240
+ <div>
241
+ <h1 style="margin: 0 0 var(--spacing-xs) 0;">Hoş Geldiniz, <span id="userName"></span>!</h1>
242
+ <p style="margin: 0; opacity: 0.9;">Duplicate detection sistemini kullanmaya başlayalım</p>
243
+ </div>
244
  </div>
245
  <button onclick="logout()" class="btn btn-secondary" style="background: rgba(0,0,0,0.2); color: white; border: none;">
246
+ 🚪 Çıkış
247
  </button>
248
  </div>
249
 
 
255
  <div class="data-option" onclick="selectOption('existing')" id="optionExisting">
256
  <div class="electric-spark"></div>
257
  <div class="option-title">Mevcut Veri Setini Kullan</div>
258
+ <div class="option-description" id="existingDataDesc">
259
+ Henüz veri yüklenmedi. Önce veri yüklemeniz gerekiyor.
260
  </div>
261
+ <ul class="option-features" id="existingDataFeatures">
262
+ <li>Veri yüklendiğinde burada görünecek</li>
263
+ <li>⬇️ Aşağıdan "Yeni Veri Seti Yükle" seçeneğini kullanın</li>
 
 
264
  </ul>
265
  </div>
266
 
 
292
  </button>
293
  </div>
294
 
295
+ <!-- Firebase Config -->
296
+ <script src="firebase-config.js"></script>
297
+
298
  <script>
299
  let selectedOption = null;
300
  const API_BASE_URL = 'http://localhost:5001/api';
301
+ let isAuthChecked = false;
302
 
303
+ // Check authentication - WAIT for Firebase auth state
304
+ function checkAuth() {
305
+ return new Promise((resolve, reject) => {
306
+ // First check localStorage
307
+ const session = JSON.parse(localStorage.getItem('userSession'));
308
+
309
+ // If Firebase is configured, also check Firebase auth
310
+ if (typeof firebase !== 'undefined' && firebase.auth) {
311
+ auth.onAuthStateChanged((user) => {
312
+ if (isAuthChecked) return; // Prevent multiple checks
313
+ isAuthChecked = true;
314
+
315
+ if (user) {
316
+ // User is signed in with Firebase
317
+ console.log('✅ Firebase auth verified:', user.email);
318
+
319
+ // Make sure localStorage session exists
320
+ if (!session) {
321
+ const newSession = {
322
+ uid: user.uid,
323
+ email: user.email,
324
+ username: user.email,
325
+ name: user.displayName || user.email,
326
+ role: 'user',
327
+ loginTime: new Date().toISOString(),
328
+ authProvider: 'firebase'
329
+ };
330
+ localStorage.setItem('userSession', JSON.stringify(newSession));
331
+ resolve(newSession);
332
+ } else {
333
+ resolve(session);
334
+ }
335
+ } else if (session) {
336
+ // No Firebase auth but has localStorage session (demo mode)
337
+ console.log('✅ Demo mode session found');
338
+ resolve(session);
339
+ } else {
340
+ // No auth at all
341
+ console.log('❌ No authentication found');
342
+ reject('Not authenticated');
343
+ }
344
+ });
345
+ } else {
346
+ // Firebase not configured, use localStorage only
347
+ if (session) {
348
+ console.log('✅ Demo mode session found');
349
+ isAuthChecked = true;
350
+ resolve(session);
351
+ } else {
352
+ console.log('❌ No session found');
353
+ isAuthChecked = true;
354
+ reject('Not authenticated');
355
+ }
356
+ }
357
+ });
358
  }
359
 
360
+ // Initialize page - wait for auth check
361
+ checkAuth()
362
+ .then((session) => {
363
+ console.log('✅ Authentication verified, loading page...');
364
+ // Display user name
365
+ document.getElementById('userName').textContent = session.name || session.username;
366
+ // Check for uploaded data
367
+ checkForUploadedData();
368
+ })
369
+ .catch((error) => {
370
+ console.log('❌ Authentication failed, redirecting to login...');
371
+ window.location.href = 'login.html';
372
+ });
373
 
374
  async function checkForUploadedData() {
375
  try {
376
+ // Get current user's userId
377
+ const session = JSON.parse(localStorage.getItem('userSession'));
378
+ const userId = session?.uid || session?.username || 'anonymous';
379
+
380
+ const response = await fetch(`${API_BASE_URL}/data_status?user_id=${encodeURIComponent(userId)}`);
381
  const data = await response.json();
382
 
383
+ const optionExisting = document.getElementById('optionExisting');
384
+ const optionTitle = optionExisting.querySelector('.option-title');
385
+ const optionDesc = document.getElementById('existingDataDesc');
386
+ const optionFeatures = document.getElementById('existingDataFeatures');
 
 
387
 
388
+ if (data.customDataLoaded) {
389
+ // Enable the existing data option
390
+ optionExisting.style.opacity = '1';
391
+ optionExisting.style.cursor = 'pointer';
392
+ optionExisting.onclick = () => selectOption('existing');
393
+
394
+ // Update with user's data
395
  optionTitle.innerHTML = 'Mevcut Veri Setini Kullan <span style="background: #10B981; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; margin-left: 8px;">YÜKLÜ</span>';
396
  optionDesc.innerHTML = `Sistemde yüklü olan <strong>${data.fileName}</strong> dosyasını kullan`;
397
  optionFeatures.innerHTML = `
398
+ <li>✅ ${data.rowCount.toLocaleString('tr-TR')} adet rapor</li>
399
+ <li>📅 Yüklenme: ${new Date(data.uploadedAt).toLocaleString('tr-TR')}</li>
400
+ <li>📊 ${data.columnCount} adet sütun</li>
401
+ <li>⚡ Hızlı başlangıç (anında kullanıma hazır)</li>
402
+ `;
403
+ } else {
404
+ // Disable the existing data option
405
+ optionExisting.style.opacity = '0.5';
406
+ optionExisting.style.cursor = 'not-allowed';
407
+ optionExisting.onclick = (e) => {
408
+ e.stopPropagation();
409
+ alert('⚠️ Henüz veri yüklenmedi. Lütfen önce "Yeni Veri Seti Yükle" seçeneğini kullanın.');
410
+ };
411
+
412
+ // Show "no data" message
413
+ optionTitle.innerHTML = 'Mevcut Veri Setini Kullan <span style="background: #EF4444; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; margin-left: 8px;">VERİ YOK</span>';
414
+ optionDesc.innerHTML = '❌ Henüz veri yüklenmedi. Önce veri yüklemeniz gerekiyor.';
415
+ optionFeatures.innerHTML = `
416
+ <li>Veri yüklendiğinde burada görünecek</li>
417
+ <li>⬇️ Aşağıdan "Yeni Veri Seti Yükle" seçeneğini kullanın</li>
418
  `;
419
  }
420
  } catch (error) {
421
+ console.error('Error checking data status:', error);
422
+
423
+ // Disable on error
424
+ const optionExisting = document.getElementById('optionExisting');
425
+ optionExisting.style.opacity = '0.5';
426
+ optionExisting.style.cursor = 'not-allowed';
427
+ optionExisting.onclick = (e) => {
428
+ e.stopPropagation();
429
+ alert('⚠️ Henüz veri yüklenmedi. Lütfen önce "Yeni Veri Seti Yükle" seçeneğini kullanın.');
430
+ };
431
  }
432
  }
433
 
 
468
  }
469
 
470
  // Logout
471
+ async function logout() {
472
+ if (confirm('Çıkış yapmak istediğinizden emin misiniz?')) {
473
+ try {
474
+ // Sign out from Firebase if available
475
+ if (typeof firebase !== 'undefined' && firebase.auth) {
476
+ await auth.signOut();
477
+ console.log('✅ Firebase signout successful');
478
+ }
479
+ } catch (error) {
480
+ console.error('Firebase signout error:', error);
481
+ }
482
+
483
+ // Clear local storage
484
+ localStorage.removeItem('userSession');
485
+ localStorage.removeItem('dataSelection');
486
+ localStorage.removeItem('systemConfig');
487
+
488
+ // Redirect to login
489
+ window.location.href = 'login.html';
490
+ }
491
  }
492
 
493
  // Create animated stars
web/data_upload.html CHANGED
@@ -85,6 +85,64 @@
85
  font-weight: 700;
86
  font-size: 0.9rem;
87
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  </style>
89
  </head>
90
  <body style="background: linear-gradient(135deg, #0A0E27 0%, #1a1f3a 100%); min-height: 100vh;">
@@ -174,6 +232,27 @@
174
  </button>
175
  </div>
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
178
  <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
179
  <script>
@@ -371,17 +450,39 @@
371
  uploadedData = null;
372
  }
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  // Continue to mapping
375
  async function continueToMapping() {
376
  if (!uploadedData) {
377
- alert(' lütfen önce bir dosya yükleyin!');
378
  return;
379
  }
380
 
381
  // Disable button
382
  const btn = document.getElementById('continueBtn');
383
  btn.disabled = true;
384
- btn.innerHTML = '<span> Veri backend\'e gnderiliyor...</span>';
385
 
386
  try {
387
  // Store uploaded data in localStorage
@@ -399,6 +500,19 @@
399
 
400
  localStorage.setItem('uploadedData', JSON.stringify(dataInfo));
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  // Send data to backend
403
  const response = await fetch('http://localhost:5001/api/upload_data', {
404
  method: 'POST',
@@ -408,14 +522,35 @@
408
  body: JSON.stringify(dataInfo)
409
  });
410
 
 
 
411
  if (!response.ok) {
412
  throw new Error(`HTTP ${response.status}`);
413
  }
414
 
 
 
415
  const result = await response.json();
416
 
 
 
 
 
 
 
 
417
  if (result.success) {
418
- console.log('Data uploaded to backend:', result.info);
 
 
 
 
 
 
 
 
 
 
419
 
420
  // Save as selectedDataset for column_mapping.html to use
421
  localStorage.setItem('selectedDataset', JSON.stringify({
@@ -425,6 +560,9 @@
425
  loadedAt: new Date().toISOString()
426
  }));
427
 
 
 
 
428
  // Redirect to column mapping
429
  window.location.href = 'column_mapping.html';
430
  } else {
@@ -433,7 +571,9 @@
433
 
434
  } catch (error) {
435
  console.error('Backend upload error:', error);
436
- alert('Veri backend\'e gönderilemedi, ancak lokal olarak kaydedildi.\n\nHata: ' + error.message + '\n\nDevam edebilirsiniz.');
 
 
437
 
438
  // Save as selectedDataset even if backend fails
439
  localStorage.setItem('selectedDataset', JSON.stringify({
@@ -445,6 +585,10 @@
445
 
446
  // Still allow to continue even if backend upload fails
447
  window.location.href = 'column_mapping.html';
 
 
 
 
448
  }
449
  }
450
 
 
85
  font-weight: 700;
86
  font-size: 0.9rem;
87
  }
88
+
89
+ /* Embedding Progress Modal */
90
+ .embedding-modal {
91
+ display: none;
92
+ position: fixed;
93
+ top: 0;
94
+ left: 0;
95
+ right: 0;
96
+ bottom: 0;
97
+ background: rgba(0, 0, 0, 0.8);
98
+ z-index: 10000;
99
+ justify-content: center;
100
+ align-items: center;
101
+ }
102
+ .embedding-modal.show {
103
+ display: flex;
104
+ }
105
+ .embedding-modal-content {
106
+ background: linear-gradient(135deg, #FFFFFF 0%, #F3F4F6 100%);
107
+ border-radius: var(--radius-xl);
108
+ padding: var(--spacing-3xl);
109
+ max-width: 500px;
110
+ width: 90%;
111
+ text-align: center;
112
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
113
+ }
114
+ .embedding-spinner {
115
+ width: 80px;
116
+ height: 80px;
117
+ border: 8px solid #f3f3f3;
118
+ border-top: 8px solid var(--primary-color);
119
+ border-radius: 50%;
120
+ animation: spin 1s linear infinite;
121
+ margin: 0 auto var(--spacing-xl);
122
+ }
123
+ @keyframes spin {
124
+ 0% { transform: rotate(0deg); }
125
+ 100% { transform: rotate(360deg); }
126
+ }
127
+ .embedding-progress-bar {
128
+ width: 100%;
129
+ height: 40px;
130
+ background: #E5E7EB;
131
+ border-radius: 20px;
132
+ overflow: hidden;
133
+ margin: var(--spacing-xl) 0;
134
+ }
135
+ .embedding-progress-fill {
136
+ height: 100%;
137
+ background: linear-gradient(90deg, #FBBF24 0%, #F59E0B 50%, #D97706 100%);
138
+ transition: width 0.5s ease-out;
139
+ display: flex;
140
+ align-items: center;
141
+ justify-content: center;
142
+ color: #fff;
143
+ font-weight: 700;
144
+ font-size: 1rem;
145
+ }
146
  </style>
147
  </head>
148
  <body style="background: linear-gradient(135deg, #0A0E27 0%, #1a1f3a 100%); min-height: 100vh;">
 
232
  </button>
233
  </div>
234
 
235
+ <!-- Embedding Progress Modal -->
236
+ <div id="embeddingModal" class="embedding-modal">
237
+ <div class="embedding-modal-content">
238
+ <div class="embedding-spinner"></div>
239
+ <h2 style="color: var(--secondary-color); margin-bottom: var(--spacing-md);">
240
+ 🤖 Embedding Oluşturuluyor
241
+ </h2>
242
+ <p id="embeddingStatus" style="color: #6B7280; margin-bottom: var(--spacing-lg); font-size: 1rem;">
243
+ Verileriniz işleniyor...
244
+ </p>
245
+ <div class="embedding-progress-bar">
246
+ <div id="embeddingProgressFill" class="embedding-progress-fill" style="width: 0%;">
247
+ 0%
248
+ </div>
249
+ </div>
250
+ <p style="color: #9CA3AF; font-size: 0.9rem; margin-top: var(--spacing-lg);">
251
+ ℹ️ Bu işlem birkaç dakika sürebilir. Lütfen sayfayı kapatmayın.
252
+ </p>
253
+ </div>
254
+ </div>
255
+
256
  <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
257
  <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
258
  <script>
 
450
  uploadedData = null;
451
  }
452
 
453
+ // Show embedding progress modal
454
+ function showEmbeddingModal() {
455
+ document.getElementById('embeddingModal').classList.add('show');
456
+ }
457
+
458
+ // Hide embedding progress modal
459
+ function hideEmbeddingModal() {
460
+ document.getElementById('embeddingModal').classList.remove('show');
461
+ }
462
+
463
+ // Update embedding progress
464
+ function updateEmbeddingProgress(percent, message) {
465
+ const fill = document.getElementById('embeddingProgressFill');
466
+ const statusText = document.getElementById('embeddingStatus');
467
+
468
+ fill.style.width = percent + '%';
469
+ fill.textContent = percent + '%';
470
+ if (message) {
471
+ statusText.textContent = message;
472
+ }
473
+ }
474
+
475
  // Continue to mapping
476
  async function continueToMapping() {
477
  if (!uploadedData) {
478
+ alert('⚠️ Lütfen önce bir dosya yükleyin!');
479
  return;
480
  }
481
 
482
  // Disable button
483
  const btn = document.getElementById('continueBtn');
484
  btn.disabled = true;
485
+ btn.innerHTML = '<span>⏳ Veri backend\'e gönderiliyor...</span>';
486
 
487
  try {
488
  // Store uploaded data in localStorage
 
500
 
501
  localStorage.setItem('uploadedData', JSON.stringify(dataInfo));
502
 
503
+ // Get user session for userId
504
+ const userSession = JSON.parse(localStorage.getItem('userSession') || '{}');
505
+ const userId = userSession.uid || userSession.username || 'anonymous';
506
+
507
+ // Add userId to dataInfo
508
+ dataInfo.userId = userId;
509
+
510
+ console.log('📤 Uploading data for user:', userId);
511
+
512
+ // Show embedding progress modal
513
+ showEmbeddingModal();
514
+ updateEmbeddingProgress(10, '📤 Veri backend\'e gönderiliyor...');
515
+
516
  // Send data to backend
517
  const response = await fetch('http://localhost:5001/api/upload_data', {
518
  method: 'POST',
 
522
  body: JSON.stringify(dataInfo)
523
  });
524
 
525
+ updateEmbeddingProgress(30, '🔄 Veri işleniyor...');
526
+
527
  if (!response.ok) {
528
  throw new Error(`HTTP ${response.status}`);
529
  }
530
 
531
+ updateEmbeddingProgress(50, '🤖 Embedding modeli yükleniyor...');
532
+
533
  const result = await response.json();
534
 
535
+ updateEmbeddingProgress(70, '✨ Embeddings oluşturuluyor...');
536
+
537
+ // Simulate embedding creation time for better UX
538
+ await new Promise(resolve => setTimeout(resolve, 1000));
539
+
540
+ updateEmbeddingProgress(90, '💾 Veriler kaydediliyor...');
541
+
542
  if (result.success) {
543
+ console.log('Data uploaded to backend:', result.info);
544
+
545
+ // Show embedding creation status
546
+ if (result.info && result.info.embeddingsCreated) {
547
+ console.log('✅ Embeddings created successfully!');
548
+ console.log('📁 Embeddings path:', result.info.embeddingsPath);
549
+ updateEmbeddingProgress(100, '✅ Embedding başarıyla oluşturuldu!');
550
+ } else {
551
+ console.warn('⚠️ Embeddings creation failed or not completed');
552
+ updateEmbeddingProgress(100, '⚠️ Embedding oluşturma tamamlanamadı');
553
+ }
554
 
555
  // Save as selectedDataset for column_mapping.html to use
556
  localStorage.setItem('selectedDataset', JSON.stringify({
 
560
  loadedAt: new Date().toISOString()
561
  }));
562
 
563
+ // Wait a bit before redirecting
564
+ await new Promise(resolve => setTimeout(resolve, 1500));
565
+
566
  // Redirect to column mapping
567
  window.location.href = 'column_mapping.html';
568
  } else {
 
571
 
572
  } catch (error) {
573
  console.error('Backend upload error:', error);
574
+ hideEmbeddingModal();
575
+
576
+ alert('⚠️ Veri backend\'e gönderilemedi, ancak lokal olarak kaydedildi.\n\nHata: ' + error.message + '\n\nDevam edebilirsiniz.');
577
 
578
  // Save as selectedDataset even if backend fails
579
  localStorage.setItem('selectedDataset', JSON.stringify({
 
585
 
586
  // Still allow to continue even if backend upload fails
587
  window.location.href = 'column_mapping.html';
588
+ } finally {
589
+ // Re-enable button in case of error (but won't matter since we redirect)
590
+ btn.disabled = false;
591
+ btn.innerHTML = '<span>Sütun Eşleştirmesine Geç</span>';
592
  }
593
  }
594
 
web/firebase-config.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Firebase Configuration
2
+ // JIRA Duplicate Detection Project
3
+ const firebaseConfig = {
4
+ apiKey: "AIzaSyCgjcibKokZodcMY_L3NP9Q4vM4kt4tZgs",
5
+ authDomain: "jira-duplicate-detection.firebaseapp.com",
6
+ projectId: "jira-duplicate-detection",
7
+ storageBucket: "jira-duplicate-detection.firebasestorage.app",
8
+ messagingSenderId: "892110955459",
9
+ appId: "1:892110955459:web:5032050c5563ba449f9376",
10
+ measurementId: "G-623M713T4M"
11
+ };
12
+
13
+ // Initialize Firebase
14
+ firebase.initializeApp(firebaseConfig);
15
+
16
+ // Get Firebase services
17
+ const auth = firebase.auth();
18
+ const db = firebase.firestore();
19
+
20
+ // Optional: Initialize Analytics
21
+ try {
22
+ const analytics = firebase.analytics();
23
+ console.log('📊 Firebase Analytics initialized');
24
+ } catch (e) {
25
+ console.log('⚠️ Analytics not available');
26
+ }
27
+
28
+ console.log('🔥 Firebase initialized successfully!');
29
+ console.log('✅ Project: jira-duplicate-detection');
30
+ console.log('✅ Authentication: Ready');
31
+ console.log('✅ Firestore: Ready');
32
+
web/index.html CHANGED
@@ -25,13 +25,10 @@
25
  <!-- Header -->
26
  <header class="header">
27
  <div class="logo">
28
- <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
29
- <rect width="32" height="32" rx="8" fill="#FFCC00"/>
30
- <path d="M16 8L22 14L16 20L10 14L16 8Z" fill="#000000"/>
31
- <path d="M16 12L20 16L16 20L12 16L16 12Z" fill="#333333"/>
32
- </svg>
33
  <h1>Bug Rapor Sistemi</h1>
34
- <span style="background: #FFCC00; color: #000; padding: 4px 12px; border-radius: 4px; font-size: 0.7rem; font-weight: 700; margin-left: 12px;">TURKCELL</span>
35
  </div>
36
  <div class="header-stats">
37
  <div class="stat">
 
25
  <!-- Header -->
26
  <header class="header">
27
  <div class="logo">
28
+ <img src="https://ffo3gv1cf3ir.merlincdn.net/SiteAssets/Bireysel/Navigasyon/turkcell-logo.png?20251016_03"
29
+ alt="Turkcell Logo"
30
+ style="height: 32px; margin-right: 12px;">
 
 
31
  <h1>Bug Rapor Sistemi</h1>
 
32
  </div>
33
  <div class="header-stats">
34
  <div class="stat">
web/login.html CHANGED
@@ -3,9 +3,15 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Login - Bug Report System</title>
7
  <link rel="stylesheet" href="style.css">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
 
 
 
 
 
 
9
  <style>
10
  /* Animated Background Section */
11
  .animated-bg {
@@ -23,7 +29,6 @@
23
  background: linear-gradient(135deg, #0A0E27 0%, #1a1f3a 100%);
24
  }
25
 
26
- /* Animated Gradient Effect */
27
  .animated-bg::before {
28
  content: '';
29
  position: absolute;
@@ -34,15 +39,10 @@
34
  }
35
 
36
  @keyframes gradientMove {
37
- 0% {
38
- transform: translateY(-100%);
39
- }
40
- 100% {
41
- transform: translateY(100%);
42
- }
43
  }
44
 
45
- /* Animated Blocks */
46
  .animated-bg span {
47
  position: relative;
48
  display: block;
@@ -101,7 +101,6 @@
101
  box-shadow: 0 0 20px rgba(255, 204, 0, 0.5);
102
  }
103
 
104
- /* Form Styling */
105
  .form-group {
106
  margin-bottom: 25px;
107
  }
@@ -152,6 +151,11 @@
152
  .btn-primary:active {
153
  opacity: 0.8;
154
  }
 
 
 
 
 
155
 
156
  .error-message {
157
  background: #FEE2E2;
@@ -167,7 +171,38 @@
167
  display: block;
168
  }
169
 
170
- /* Responsive */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  @media (max-width: 900px) {
172
  .animated-bg span {
173
  width: calc(10vw - 2px);
@@ -190,7 +225,6 @@
190
  <body>
191
  <!-- Animated Background -->
192
  <section class="animated-bg">
193
- <!-- 250 animated spans for grid effect -->
194
  <script>
195
  for (let i = 0; i < 250; i++) {
196
  document.write('<span></span>');
@@ -201,38 +235,42 @@
201
  <div class="login-container">
202
  <!-- Logo -->
203
  <div class="login-header">
204
- <div class="login-logo"></div>
 
 
 
 
205
  <h1>Sistem Girişi</h1>
206
  <p>Bug Report Duplicate Detection System</p>
207
  </div>
208
 
209
  <!-- Error Message -->
210
  <div id="errorMessage" class="error-message">
211
- <strong> Hata!</strong>
212
  <p id="errorText"></p>
213
  </div>
214
 
215
  <!-- Login Form -->
216
  <form id="loginForm">
217
  <div class="form-group">
218
- <label for="username" class="form-label">
219
- <span>Kullanıcı Ad</span>
220
  <span class="required">*</span>
221
  </label>
222
  <input
223
- type="text"
224
- id="username"
225
- name="username"
226
  class="form-input"
227
  placeholder="[email protected]"
228
  required
229
- autocomplete="username"
230
  >
231
  </div>
232
 
233
  <div class="form-group">
234
  <label for="password" class="form-label">
235
- <span>Şifre</span>
236
  <span class="required">*</span>
237
  </label>
238
  <input
@@ -240,81 +278,250 @@
240
  id="password"
241
  name="password"
242
  class="form-input"
243
- placeholder=""
244
  required
245
  autocomplete="current-password"
246
  >
 
 
 
247
  </div>
248
 
249
- <button type="submit" class="btn btn-primary" id="loginBtn" style="width: 100%; margin-top: var(--spacing-lg);">
250
- <span>Giriş Yap</span>
251
  </button>
252
  </form>
253
 
254
- <!-- Demo Credentials -->
 
 
 
 
 
255
  <div style="margin-top: var(--spacing-xl); padding: var(--spacing-md); background: rgba(255, 204, 0, 0.1); border: 1px solid #FFCC00; border-radius: var(--radius-md);">
256
  <p style="font-size: 0.85rem; color: #FFCC00; margin: 0;">
257
- <strong>Demo Giriş:</strong><br>
258
- Kullanıcı: <code style="background: #444; padding: 2px 6px; border-radius: 3px;">demo</code> /
259
- <code style="background: #444; padding: 2px 6px; border-radius: 3px;">test123</code>
 
260
  </p>
261
  </div>
262
  </div>
263
 
 
 
 
264
  <script>
265
  const form = document.getElementById('loginForm');
266
  const loginBtn = document.getElementById('loginBtn');
267
  const errorMessage = document.getElementById('errorMessage');
268
  const errorText = document.getElementById('errorText');
269
 
270
- // Demo users
271
- const USERS = {
272
- 'demo': { password: 'test123', role: 'admin', name: 'Demo User' },
273
- '[email protected]': { password: 'admin123', role: 'admin', name: 'Admin User' },
274
- '[email protected]': { password: 'user123', role: 'user', name: 'Standard User' }
 
 
 
 
 
 
 
275
  };
276
 
277
- form.addEventListener('submit', (e) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  e.preventDefault();
279
 
280
- const username = document.getElementById('username').value.trim();
 
 
281
  const password = document.getElementById('password').value;
282
 
 
 
 
 
 
 
 
 
 
283
  // Disable button
284
  loginBtn.disabled = true;
285
- loginBtn.innerHTML = '<span> Giri yaplyor...</span>';
286
 
287
- // Simulate API call
288
- setTimeout(() => {
289
- // Validate credentials
290
- const user = USERS[username];
291
-
292
- if (!user || user.password !== password) {
293
- // Show error
294
- errorText.textContent = 'Kullanıcı adı veya şifre hatalı!';
295
- errorMessage.classList.add('show');
296
-
297
- // Re-enable button
298
- loginBtn.disabled = false;
299
- loginBtn.innerHTML = '<span>Giriş Yap</span>';
300
- return;
301
  }
302
 
303
- // Success - store session
304
- const session = {
305
- username: username,
306
- role: user.role,
307
- name: user.name,
308
- loginTime: new Date().toISOString()
309
- };
310
 
311
- localStorage.setItem('userSession', JSON.stringify(session));
 
312
 
313
- // Redirect to data selection
314
- window.location.href = 'data_selection.html';
315
- }, 1000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  });
317
  </script>
318
  </body>
319
  </html>
320
-
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Giriş - Bug Report System</title>
7
  <link rel="stylesheet" href="style.css">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+
10
+ <!-- Firebase SDKs -->
11
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
12
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js"></script>
13
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore-compat.js"></script>
14
+
15
  <style>
16
  /* Animated Background Section */
17
  .animated-bg {
 
29
  background: linear-gradient(135deg, #0A0E27 0%, #1a1f3a 100%);
30
  }
31
 
 
32
  .animated-bg::before {
33
  content: '';
34
  position: absolute;
 
39
  }
40
 
41
  @keyframes gradientMove {
42
+ 0% { transform: translateY(-100%); }
43
+ 100% { transform: translateY(100%); }
 
 
 
 
44
  }
45
 
 
46
  .animated-bg span {
47
  position: relative;
48
  display: block;
 
101
  box-shadow: 0 0 20px rgba(255, 204, 0, 0.5);
102
  }
103
 
 
104
  .form-group {
105
  margin-bottom: 25px;
106
  }
 
151
  .btn-primary:active {
152
  opacity: 0.8;
153
  }
154
+
155
+ .btn-primary:disabled {
156
+ opacity: 0.5;
157
+ cursor: not-allowed;
158
+ }
159
 
160
  .error-message {
161
  background: #FEE2E2;
 
171
  display: block;
172
  }
173
 
174
+ .register-link {
175
+ text-align: center;
176
+ margin-top: var(--spacing-lg);
177
+ color: #aaa;
178
+ font-size: 0.9rem;
179
+ }
180
+
181
+ .register-link a {
182
+ color: #FFCC00;
183
+ text-decoration: none;
184
+ font-weight: 600;
185
+ }
186
+
187
+ .register-link a:hover {
188
+ text-decoration: underline;
189
+ }
190
+
191
+ .forgot-password {
192
+ text-align: right;
193
+ margin-top: 8px;
194
+ }
195
+
196
+ .forgot-password a {
197
+ color: #888;
198
+ text-decoration: none;
199
+ font-size: 0.85rem;
200
+ }
201
+
202
+ .forgot-password a:hover {
203
+ color: #FFCC00;
204
+ }
205
+
206
  @media (max-width: 900px) {
207
  .animated-bg span {
208
  width: calc(10vw - 2px);
 
225
  <body>
226
  <!-- Animated Background -->
227
  <section class="animated-bg">
 
228
  <script>
229
  for (let i = 0; i < 250; i++) {
230
  document.write('<span></span>');
 
235
  <div class="login-container">
236
  <!-- Logo -->
237
  <div class="login-header">
238
+ <div class="login-logo">
239
+ <img src="https://ffo3gv1cf3ir.merlincdn.net/SiteAssets/Bireysel/Navigasyon/turkcell-logo.png?20251016_03"
240
+ alt="Turkcell Logo"
241
+ style="width: 100%; height: 100%; object-fit: contain; padding: 10px;">
242
+ </div>
243
  <h1>Sistem Girişi</h1>
244
  <p>Bug Report Duplicate Detection System</p>
245
  </div>
246
 
247
  <!-- Error Message -->
248
  <div id="errorMessage" class="error-message">
249
+ <strong>⚠️ Hata!</strong>
250
  <p id="errorText"></p>
251
  </div>
252
 
253
  <!-- Login Form -->
254
  <form id="loginForm">
255
  <div class="form-group">
256
+ <label for="email" class="form-label">
257
+ <span>📧 E-posta</span>
258
  <span class="required">*</span>
259
  </label>
260
  <input
261
+ type="email"
262
+ id="email"
263
+ name="email"
264
  class="form-input"
265
  placeholder="[email protected]"
266
  required
267
+ autocomplete="email"
268
  >
269
  </div>
270
 
271
  <div class="form-group">
272
  <label for="password" class="form-label">
273
+ <span>🔒 Şifre</span>
274
  <span class="required">*</span>
275
  </label>
276
  <input
 
278
  id="password"
279
  name="password"
280
  class="form-input"
281
+ placeholder="••••••••"
282
  required
283
  autocomplete="current-password"
284
  >
285
+ <div class="forgot-password">
286
+ <a href="#" onclick="resetPassword(); return false;">Şifremi unuttum?</a>
287
+ </div>
288
  </div>
289
 
290
+ <button type="submit" class="btn btn-primary" id="loginBtn">
291
+ <span>🚀 Giriş Yap</span>
292
  </button>
293
  </form>
294
 
295
+ <!-- Register Link -->
296
+ <div class="register-link">
297
+ Henüz hesabınız yok mu? <a href="register.html">Kayıt Ol</a>
298
+ </div>
299
+
300
+ <!-- Demo Mode -->
301
  <div style="margin-top: var(--spacing-xl); padding: var(--spacing-md); background: rgba(255, 204, 0, 0.1); border: 1px solid #FFCC00; border-radius: var(--radius-md);">
302
  <p style="font-size: 0.85rem; color: #FFCC00; margin: 0;">
303
+ <strong>💡 Demo Mod:</strong><br>
304
+ Firebase yapılandırılmamışsa demo hesabı kullanabilirsiniz.<br>
305
+ E-posta: <code style="background: #444; padding: 2px 6px; border-radius: 3px;">[email protected]</code><br>
306
+ Şifre: <code style="background: #444; padding: 2px 6px; border-radius: 3px;">demo123</code>
307
  </p>
308
  </div>
309
  </div>
310
 
311
+ <!-- Firebase Config -->
312
+ <script src="firebase-config.js"></script>
313
+
314
  <script>
315
  const form = document.getElementById('loginForm');
316
  const loginBtn = document.getElementById('loginBtn');
317
  const errorMessage = document.getElementById('errorMessage');
318
  const errorText = document.getElementById('errorText');
319
 
320
+ // Demo users (fallback if Firebase is not configured)
321
+ const DEMO_USERS = {
322
+ 'demo@turkcell.com.tr': {
323
+ password: 'demo123',
324
+ role: 'admin',
325
+ name: 'Demo User'
326
+ },
327
328
+ password: 'admin123',
329
+ role: 'admin',
330
+ name: 'Admin User'
331
+ }
332
  };
333
 
334
+ // Check if Firebase is configured
335
+ const isFirebaseConfigured = () => {
336
+ try {
337
+ const config = firebase.app().options;
338
+ return config.apiKey !== 'YOUR_API_KEY';
339
+ } catch (e) {
340
+ return false;
341
+ }
342
+ };
343
+
344
+ // Show error
345
+ function showError(message) {
346
+ errorText.textContent = message;
347
+ errorMessage.classList.add('show');
348
+ }
349
+
350
+ // Hide error
351
+ function hideError() {
352
+ errorMessage.classList.remove('show');
353
+ }
354
+
355
+ // Reset password
356
+ async function resetPassword() {
357
+ const email = prompt('E-posta adresinizi girin:');
358
+
359
+ if (!email) return;
360
+
361
+ if (!isFirebaseConfigured()) {
362
+ alert('Firebase yapılandırılmamış! Demo modda şifre sıfırlama mevcut değil.');
363
+ return;
364
+ }
365
+
366
+ try {
367
+ await auth.sendPasswordResetEmail(email);
368
+ alert('✅ Şifre sıfırlama e-postası gönderildi! Lütfen e-posta kutunuzu kontrol edin.');
369
+ } catch (error) {
370
+ console.error('Password reset error:', error);
371
+ alert('❌ Hata: ' + (error.message || 'Şifre sıfırlama e-postası gönderilemedi!'));
372
+ }
373
+ }
374
+
375
+ // Firebase login
376
+ async function loginWithFirebase(email, password) {
377
+ try {
378
+ const userCredential = await auth.signInWithEmailAndPassword(email, password);
379
+ const user = userCredential.user;
380
+
381
+ console.log('✅ Firebase login successful:', user.uid);
382
+
383
+ // Get user data from Firestore
384
+ const userDoc = await db.collection('users').doc(user.uid).get();
385
+ const userData = userDoc.data();
386
+
387
+ // Update last login
388
+ await db.collection('users').doc(user.uid).update({
389
+ lastLogin: firebase.firestore.FieldValue.serverTimestamp()
390
+ });
391
+
392
+ // Create session
393
+ const session = {
394
+ uid: user.uid,
395
+ email: user.email,
396
+ username: user.email,
397
+ name: userData?.fullName || user.displayName || user.email,
398
+ role: userData?.role || 'user',
399
+ loginTime: new Date().toISOString(),
400
+ authProvider: 'firebase'
401
+ };
402
+
403
+ localStorage.setItem('userSession', JSON.stringify(session));
404
+
405
+ console.log('✅ Session created:', session);
406
+
407
+ // Redirect
408
+ window.location.href = 'data_selection.html';
409
+
410
+ } catch (error) {
411
+ throw error;
412
+ }
413
+ }
414
+
415
+ // Demo login (fallback)
416
+ function loginWithDemo(email, password) {
417
+ const user = DEMO_USERS[email];
418
+
419
+ if (!user || user.password !== password) {
420
+ throw new Error('Kullanıcı adı veya şifre hatalı!');
421
+ }
422
+
423
+ // Create demo session
424
+ const session = {
425
+ username: email,
426
+ email: email,
427
+ role: user.role,
428
+ name: user.name,
429
+ loginTime: new Date().toISOString(),
430
+ authProvider: 'demo'
431
+ };
432
+
433
+ localStorage.setItem('userSession', JSON.stringify(session));
434
+
435
+ console.log('✅ Demo login successful:', session);
436
+
437
+ // Redirect
438
+ window.location.href = 'data_selection.html';
439
+ }
440
+
441
+ // Form submit
442
+ form.addEventListener('submit', async (e) => {
443
  e.preventDefault();
444
 
445
+ hideError();
446
+
447
+ const email = document.getElementById('email').value.trim();
448
  const password = document.getElementById('password').value;
449
 
450
+ // Validate
451
+ if (!email || !password) {
452
+ showError('Lütfen tüm alanları doldurun!');
453
+ return;
454
+ }
455
+
456
+ // Set logging in flag to prevent auth state change redirect
457
+ isLoggingIn = true;
458
+
459
  // Disable button
460
  loginBtn.disabled = true;
461
+ loginBtn.innerHTML = '<span>⏳ Giriş yapılıyor...</span>';
462
 
463
+ try {
464
+ if (isFirebaseConfigured()) {
465
+ // Try Firebase login
466
+ console.log('🔥 Attempting Firebase login...');
467
+ await loginWithFirebase(email, password);
468
+ } else {
469
+ // Fallback to demo login
470
+ console.log('💡 Firebase not configured, using demo login...');
471
+ await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate delay
472
+ loginWithDemo(email, password);
 
 
 
 
473
  }
474
 
475
+ } catch (error) {
476
+ console.error('❌ Login error:', error);
 
 
 
 
 
477
 
478
+ // Reset logging in flag on error
479
+ isLoggingIn = false;
480
 
481
+ let errorMsg = 'Giriş yapılamadı!';
482
+
483
+ // Handle Firebase errors
484
+ switch (error.code) {
485
+ case 'auth/user-not-found':
486
+ errorMsg = 'Kullanıcı bulunamadı!';
487
+ break;
488
+ case 'auth/wrong-password':
489
+ errorMsg = 'Hatalı şifre!';
490
+ break;
491
+ case 'auth/invalid-email':
492
+ errorMsg = 'Geçersiz e-posta adresi!';
493
+ break;
494
+ case 'auth/user-disabled':
495
+ errorMsg = 'Bu hesap devre dışı bırakılmış!';
496
+ break;
497
+ case 'auth/too-many-requests':
498
+ errorMsg = 'Çok fazla başarısız deneme! Lütfen daha sonra tekrar deneyin.';
499
+ break;
500
+ default:
501
+ errorMsg = error.message || errorMsg;
502
+ }
503
+
504
+ showError(errorMsg);
505
+
506
+ // Re-enable button
507
+ loginBtn.disabled = false;
508
+ loginBtn.innerHTML = '<span>🚀 Giriş Yap</span>';
509
+ }
510
+ });
511
+
512
+ // Check if already logged in (only on page load, not during login process)
513
+ let isLoggingIn = false;
514
+
515
+ auth.onAuthStateChanged((user) => {
516
+ // Only redirect if user exists, we're on login page, and not currently logging in
517
+ if (user && window.location.pathname.includes('login.html') && !isLoggingIn) {
518
+ console.log('✅ User already logged in, redirecting...');
519
+ // Small delay to prevent loop
520
+ setTimeout(() => {
521
+ window.location.href = 'data_selection.html';
522
+ }, 100);
523
+ }
524
  });
525
  </script>
526
  </body>
527
  </html>
 
web/register.html ADDED
@@ -0,0 +1,531 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="tr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Kayıt Ol - Bug Report System</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+
10
+ <!-- Firebase SDKs -->
11
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
12
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js"></script>
13
+ <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore-compat.js"></script>
14
+
15
+ <style>
16
+ /* Animated Background Section */
17
+ .animated-bg {
18
+ position: fixed;
19
+ top: 0;
20
+ left: 0;
21
+ width: 100vw;
22
+ height: 100vh;
23
+ display: flex;
24
+ justify-content: center;
25
+ align-items: center;
26
+ gap: 2px;
27
+ flex-wrap: wrap;
28
+ overflow: hidden;
29
+ background: linear-gradient(135deg, #0A0E27 0%, #1a1f3a 100%);
30
+ }
31
+
32
+ .animated-bg::before {
33
+ content: '';
34
+ position: absolute;
35
+ width: 100%;
36
+ height: 100%;
37
+ background: linear-gradient(#0A0E27, #FFCC00, #0A0E27);
38
+ animation: gradientMove 5s linear infinite;
39
+ }
40
+
41
+ @keyframes gradientMove {
42
+ 0% { transform: translateY(-100%); }
43
+ 100% { transform: translateY(100%); }
44
+ }
45
+
46
+ .animated-bg span {
47
+ position: relative;
48
+ display: block;
49
+ width: calc(6.25vw - 2px);
50
+ height: calc(6.25vw - 2px);
51
+ background: #1a1f3a;
52
+ z-index: 2;
53
+ transition: 1.5s;
54
+ }
55
+
56
+ .animated-bg span:hover {
57
+ background: #FFCC00;
58
+ transition: 0s;
59
+ }
60
+
61
+ /* Register Container */
62
+ .register-container {
63
+ position: relative;
64
+ max-width: 500px;
65
+ margin: 0 auto;
66
+ padding: 40px;
67
+ background: rgba(34, 34, 34, 0.95);
68
+ backdrop-filter: blur(10px);
69
+ border-radius: 16px;
70
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.9);
71
+ z-index: 1000;
72
+ max-height: 90vh;
73
+ overflow-y: auto;
74
+ }
75
+
76
+ .register-header {
77
+ text-align: center;
78
+ margin-bottom: var(--spacing-xl);
79
+ }
80
+
81
+ .register-header h1 {
82
+ color: #FFCC00;
83
+ text-transform: uppercase;
84
+ font-size: 2em;
85
+ margin-bottom: var(--spacing-sm);
86
+ }
87
+
88
+ .register-header p {
89
+ color: #aaa;
90
+ font-size: 0.9rem;
91
+ }
92
+
93
+ .register-logo {
94
+ width: 80px;
95
+ height: 80px;
96
+ margin: 0 auto var(--spacing-md);
97
+ background: linear-gradient(135deg, #FFCC00 0%, #FF9500 100%);
98
+ border-radius: 20px;
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ font-size: 3rem;
103
+ box-shadow: 0 0 20px rgba(255, 204, 0, 0.5);
104
+ }
105
+
106
+ .form-group {
107
+ margin-bottom: 20px;
108
+ }
109
+
110
+ .form-label {
111
+ color: #fff;
112
+ margin-bottom: 8px;
113
+ display: block;
114
+ font-size: 0.9rem;
115
+ }
116
+
117
+ .form-input {
118
+ width: 100%;
119
+ background: #333;
120
+ border: none;
121
+ outline: none;
122
+ padding: 12px 15px;
123
+ border-radius: 4px;
124
+ color: #fff;
125
+ font-weight: 500;
126
+ font-size: 1em;
127
+ transition: 0.3s;
128
+ }
129
+
130
+ .form-input:focus {
131
+ background: #444;
132
+ box-shadow: 0 0 10px rgba(255, 204, 0, 0.3);
133
+ }
134
+
135
+ .form-input.error {
136
+ border: 2px solid #EF4444;
137
+ }
138
+
139
+ .password-strength {
140
+ margin-top: 8px;
141
+ height: 4px;
142
+ background: #333;
143
+ border-radius: 2px;
144
+ overflow: hidden;
145
+ }
146
+
147
+ .password-strength-bar {
148
+ height: 100%;
149
+ width: 0%;
150
+ transition: all 0.3s;
151
+ }
152
+
153
+ .password-strength-bar.weak {
154
+ width: 33%;
155
+ background: #EF4444;
156
+ }
157
+
158
+ .password-strength-bar.medium {
159
+ width: 66%;
160
+ background: #F59E0B;
161
+ }
162
+
163
+ .password-strength-bar.strong {
164
+ width: 100%;
165
+ background: #10B981;
166
+ }
167
+
168
+ .password-hint {
169
+ font-size: 0.75rem;
170
+ color: #888;
171
+ margin-top: 4px;
172
+ }
173
+
174
+ .btn-primary {
175
+ width: 100%;
176
+ padding: 15px;
177
+ background: #FFCC00;
178
+ color: #000;
179
+ border: none;
180
+ font-weight: 600;
181
+ font-size: 1.2em;
182
+ letter-spacing: 0.05em;
183
+ cursor: pointer;
184
+ border-radius: 4px;
185
+ transition: 0.3s;
186
+ }
187
+
188
+ .btn-primary:hover {
189
+ background: #FFD700;
190
+ box-shadow: 0 0 20px rgba(255, 204, 0, 0.5);
191
+ }
192
+
193
+ .btn-primary:disabled {
194
+ opacity: 0.5;
195
+ cursor: not-allowed;
196
+ }
197
+
198
+ .message {
199
+ padding: var(--spacing-md);
200
+ border-radius: var(--radius-md);
201
+ margin-bottom: var(--spacing-md);
202
+ display: none;
203
+ }
204
+
205
+ .message.show {
206
+ display: block;
207
+ }
208
+
209
+ .error-message {
210
+ background: #FEE2E2;
211
+ border: 1px solid #EF4444;
212
+ color: #991B1B;
213
+ }
214
+
215
+ .success-message {
216
+ background: #D1FAE5;
217
+ border: 1px solid #10B981;
218
+ color: #065F46;
219
+ }
220
+
221
+ .login-link {
222
+ text-align: center;
223
+ margin-top: var(--spacing-lg);
224
+ color: #aaa;
225
+ font-size: 0.9rem;
226
+ }
227
+
228
+ .login-link a {
229
+ color: #FFCC00;
230
+ text-decoration: none;
231
+ font-weight: 600;
232
+ }
233
+
234
+ .login-link a:hover {
235
+ text-decoration: underline;
236
+ }
237
+
238
+ @media (max-width: 600px) {
239
+ .register-container {
240
+ margin: 20px;
241
+ padding: 30px 20px;
242
+ }
243
+ }
244
+ </style>
245
+ </head>
246
+ <body>
247
+ <!-- Animated Background -->
248
+ <section class="animated-bg">
249
+ <script>
250
+ for (let i = 0; i < 250; i++) {
251
+ document.write('<span></span>');
252
+ }
253
+ </script>
254
+ </section>
255
+
256
+ <div class="register-container">
257
+ <!-- Logo -->
258
+ <div class="register-header">
259
+ <div class="register-logo">
260
+ <img src="https://ffo3gv1cf3ir.merlincdn.net/SiteAssets/Bireysel/Navigasyon/turkcell-logo.png?20251016_03"
261
+ alt="Turkcell Logo"
262
+ style="width: 100%; height: 100%; object-fit: contain; padding: 10px;">
263
+ </div>
264
+ <h1>Kayıt Ol</h1>
265
+ <p>Yeni hesap oluştur</p>
266
+ </div>
267
+
268
+ <!-- Messages -->
269
+ <div id="errorMessage" class="message error-message">
270
+ <strong>⚠️ Hata!</strong>
271
+ <p id="errorText"></p>
272
+ </div>
273
+
274
+ <div id="successMessage" class="message success-message">
275
+ <strong>✅ Başarılı!</strong>
276
+ <p id="successText"></p>
277
+ </div>
278
+
279
+ <!-- Register Form -->
280
+ <form id="registerForm">
281
+ <div class="form-group">
282
+ <label for="fullName" class="form-label">
283
+ <span>👤 Ad Soyad</span>
284
+ <span class="required">*</span>
285
+ </label>
286
+ <input
287
+ type="text"
288
+ id="fullName"
289
+ class="form-input"
290
+ placeholder="Adınız Soyadınız"
291
+ required
292
+ minlength="3"
293
+ >
294
+ </div>
295
+
296
+ <div class="form-group">
297
+ <label for="email" class="form-label">
298
+ <span>📧 E-posta</span>
299
+ <span class="required">*</span>
300
+ </label>
301
+ <input
302
+ type="email"
303
+ id="email"
304
+ class="form-input"
305
+ placeholder="[email protected]"
306
+ required
307
+ >
308
+ </div>
309
+
310
+ <div class="form-group">
311
+ <label for="password" class="form-label">
312
+ <span>🔒 Şifre</span>
313
+ <span class="required">*</span>
314
+ </label>
315
+ <input
316
+ type="password"
317
+ id="password"
318
+ class="form-input"
319
+ placeholder="En az 6 karakter"
320
+ required
321
+ minlength="6"
322
+ >
323
+ <div class="password-strength">
324
+ <div id="passwordStrengthBar" class="password-strength-bar"></div>
325
+ </div>
326
+ <div class="password-hint">
327
+ En az 6 karakter, büyük harf, küçük harf ve rakam içermelidir
328
+ </div>
329
+ </div>
330
+
331
+ <div class="form-group">
332
+ <label for="confirmPassword" class="form-label">
333
+ <span>🔒 Şifre Tekrar</span>
334
+ <span class="required">*</span>
335
+ </label>
336
+ <input
337
+ type="password"
338
+ id="confirmPassword"
339
+ class="form-input"
340
+ placeholder="Şifrenizi tekrar girin"
341
+ required
342
+ minlength="6"
343
+ >
344
+ </div>
345
+
346
+ <div class="form-group">
347
+ <label for="role" class="form-label">
348
+ <span>🎭 Rol</span>
349
+ <span class="required">*</span>
350
+ </label>
351
+ <select id="role" class="form-input" required>
352
+ <option value="">Seçiniz</option>
353
+ <option value="user">Kullanıcı</option>
354
+ <option value="admin">Admin</option>
355
+ <option value="developer">Developer</option>
356
+ <option value="tester">Tester</option>
357
+ </select>
358
+ </div>
359
+
360
+ <button type="submit" class="btn btn-primary" id="registerBtn">
361
+ <span>🚀 Kayıt Ol</span>
362
+ </button>
363
+ </form>
364
+
365
+ <!-- Login Link -->
366
+ <div class="login-link">
367
+ Zaten hesabınız var mı? <a href="login.html">Giriş Yap</a>
368
+ </div>
369
+ </div>
370
+
371
+ <!-- Firebase Config -->
372
+ <script src="firebase-config.js"></script>
373
+
374
+ <script>
375
+ const form = document.getElementById('registerForm');
376
+ const registerBtn = document.getElementById('registerBtn');
377
+ const errorMessage = document.getElementById('errorMessage');
378
+ const errorText = document.getElementById('errorText');
379
+ const successMessage = document.getElementById('successMessage');
380
+ const successText = document.getElementById('successText');
381
+ const passwordInput = document.getElementById('password');
382
+ const confirmPasswordInput = document.getElementById('confirmPassword');
383
+ const passwordStrengthBar = document.getElementById('passwordStrengthBar');
384
+
385
+ // Password strength checker
386
+ passwordInput.addEventListener('input', () => {
387
+ const password = passwordInput.value;
388
+ let strength = 0;
389
+
390
+ if (password.length >= 6) strength++;
391
+ if (password.length >= 10) strength++;
392
+ if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
393
+ if (/\d/.test(password)) strength++;
394
+ if (/[^a-zA-Z0-9]/.test(password)) strength++;
395
+
396
+ passwordStrengthBar.className = 'password-strength-bar';
397
+ if (strength <= 2) {
398
+ passwordStrengthBar.classList.add('weak');
399
+ } else if (strength <= 4) {
400
+ passwordStrengthBar.classList.add('medium');
401
+ } else {
402
+ passwordStrengthBar.classList.add('strong');
403
+ }
404
+ });
405
+
406
+ // Show error
407
+ function showError(message) {
408
+ errorText.textContent = message;
409
+ errorMessage.classList.add('show');
410
+ successMessage.classList.remove('show');
411
+ }
412
+
413
+ // Show success
414
+ function showSuccess(message) {
415
+ successText.textContent = message;
416
+ successMessage.classList.add('show');
417
+ errorMessage.classList.remove('show');
418
+ }
419
+
420
+ // Register form submit
421
+ form.addEventListener('submit', async (e) => {
422
+ e.preventDefault();
423
+
424
+ const fullName = document.getElementById('fullName').value.trim();
425
+ const email = document.getElementById('email').value.trim();
426
+ const password = document.getElementById('password').value;
427
+ const confirmPassword = document.getElementById('confirmPassword').value;
428
+ const role = document.getElementById('role').value;
429
+
430
+ // Validate
431
+ if (!fullName || fullName.length < 3) {
432
+ showError('Ad Soyad en az 3 karakter olmalıdır!');
433
+ return;
434
+ }
435
+
436
+ if (!email || !email.includes('@')) {
437
+ showError('Geçerli bir e-posta adresi girin!');
438
+ return;
439
+ }
440
+
441
+ if (password.length < 6) {
442
+ showError('Şifre en az 6 karakter olmalıdır!');
443
+ return;
444
+ }
445
+
446
+ if (password !== confirmPassword) {
447
+ showError('Şifreler eşleşmiyor!');
448
+ confirmPasswordInput.classList.add('error');
449
+ return;
450
+ }
451
+
452
+ if (!role) {
453
+ showError('Lütfen bir rol seçin!');
454
+ return;
455
+ }
456
+
457
+ // Disable button
458
+ registerBtn.disabled = true;
459
+ registerBtn.innerHTML = '<span>⏳ Kayıt yapılıyor...</span>';
460
+
461
+ try {
462
+ // Create user with Firebase Authentication
463
+ const userCredential = await auth.createUserWithEmailAndPassword(email, password);
464
+ const user = userCredential.user;
465
+
466
+ console.log('✅ User created:', user.uid);
467
+
468
+ // Update display name
469
+ await user.updateProfile({
470
+ displayName: fullName
471
+ });
472
+
473
+ // Save additional user info to Firestore
474
+ await db.collection('users').doc(user.uid).set({
475
+ uid: user.uid,
476
+ email: email,
477
+ fullName: fullName,
478
+ role: role,
479
+ createdAt: firebase.firestore.FieldValue.serverTimestamp(),
480
+ lastLogin: firebase.firestore.FieldValue.serverTimestamp()
481
+ });
482
+
483
+ console.log('✅ User data saved to Firestore');
484
+
485
+ // Show success
486
+ showSuccess('Kayıt başarılı! Giriş sayfasına yönlendiriliyorsunuz...');
487
+
488
+ // Redirect to login after 2 seconds
489
+ setTimeout(() => {
490
+ window.location.href = 'login.html';
491
+ }, 2000);
492
+
493
+ } catch (error) {
494
+ console.error('❌ Registration error:', error);
495
+
496
+ // Handle Firebase errors
497
+ let errorMsg = 'Kayıt sırasında bir hata oluştu!';
498
+
499
+ switch (error.code) {
500
+ case 'auth/email-already-in-use':
501
+ errorMsg = 'Bu e-posta adresi zaten kullanımda!';
502
+ break;
503
+ case 'auth/invalid-email':
504
+ errorMsg = 'Geçersiz e-posta adresi!';
505
+ break;
506
+ case 'auth/operation-not-allowed':
507
+ errorMsg = 'E-posta/şifre girişi etkinleştirilmemiş!';
508
+ break;
509
+ case 'auth/weak-password':
510
+ errorMsg = 'Şifre çok zayıf! En az 6 karakter olmalı.';
511
+ break;
512
+ default:
513
+ errorMsg = error.message;
514
+ }
515
+
516
+ showError(errorMsg);
517
+
518
+ // Re-enable button
519
+ registerBtn.disabled = false;
520
+ registerBtn.innerHTML = '<span>🚀 Kayıt Ol</span>';
521
+ }
522
+ });
523
+
524
+ // Remove error class on input
525
+ confirmPasswordInput.addEventListener('input', () => {
526
+ confirmPasswordInput.classList.remove('error');
527
+ });
528
+ </script>
529
+ </body>
530
+ </html>
531
+