esrakoc commited on
Commit
3d99898
·
1 Parent(s): 8a46be3

Upload 15 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Temel Python imajı
2
+ FROM python:3.12-slim
3
+
4
+ # Çalışma dizinini belirle
5
+ WORKDIR /app
6
+
7
+ # Gereken dosyaları kopyala
8
+ COPY requirements.txt .
9
+
10
+ # Bağımlılıkları yükle
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Uygulama kodlarını kopyala
14
+ COPY . .
15
+
16
+ # Uygulamayı başlat
17
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flask Web Uygulaması - TDK Chatbot.
3
+
4
+ Modern, kullanıcı dostu web arayüzü.
5
+ """
6
+
7
+ from flask import Flask, render_template, request, jsonify
8
+ import sys
9
+ import os
10
+
11
+ # src klasörünü path'e ekle
12
+ sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
13
+
14
+ from chatbot import TDKChatbot
15
+
16
+ # Flask uygulaması
17
+ app = Flask(__name__)
18
+ app.config['SECRET_KEY'] = 'tdk-chatbot-secret-key-2024'
19
+
20
+ # Chatbot'u global olarak başlat (sadece bir kere)
21
+ print("🚀 Flask uygulaması başlatılıyor...")
22
+ chatbot = None
23
+
24
+
25
+ def get_chatbot():
26
+ """Chatbot instance'ını döndürür (lazy loading)."""
27
+ global chatbot
28
+ if chatbot is None:
29
+ chatbot = TDKChatbot()
30
+ return chatbot
31
+
32
+
33
+ @app.route('/')
34
+ def home():
35
+ """Ana sayfa."""
36
+ return render_template('index.html')
37
+
38
+
39
+ @app.route('/chat', methods=['POST'])
40
+ def chat():
41
+ """
42
+ Chatbot endpoint'i.
43
+
44
+ Request JSON:
45
+ {
46
+ "message": "kullanıcı mesajı",
47
+ "top_k": 5 (opsiyonel)
48
+ }
49
+
50
+ Response JSON:
51
+ {
52
+ "response": "chatbot yanıtı",
53
+ "sources": [{"kelime": "...", "anlam": "..."}]
54
+ }
55
+ """
56
+ try:
57
+ # JSON verisi al
58
+ data = request.get_json()
59
+
60
+ if not data or 'message' not in data:
61
+ return jsonify({
62
+ 'error': 'Mesaj bulunamadı'
63
+ }), 400
64
+
65
+ message = data['message'].strip()
66
+ top_k = data.get('top_k', 5)
67
+
68
+ if not message:
69
+ return jsonify({
70
+ 'error': 'Boş mesaj gönderilemez'
71
+ }), 400
72
+
73
+ # Chatbot'tan yanıt al
74
+ bot = get_chatbot()
75
+ result = bot.chat(message, top_k=top_k)
76
+
77
+ # Kaynakları formatla
78
+ sources = []
79
+ for r in result.get('results', [])[:3]: # İlk 3 kaynağı göster
80
+ doc = r['document']
81
+ sources.append({
82
+ 'kelime': doc.get('kelime', ''),
83
+ 'anlam': doc.get('anlam', '')[:200] + '...' if len(doc.get('anlam', '')) > 200 else doc.get('anlam',
84
+ ''),
85
+ 'score': round(r.get('score', 0), 4)
86
+ })
87
+
88
+ # Yanıtı döndür
89
+ return jsonify({
90
+ 'response': result['response'],
91
+ 'sources': sources
92
+ })
93
+
94
+ except Exception as e:
95
+ print(f"Hata: {e}")
96
+ import traceback
97
+ traceback.print_exc()
98
+
99
+ return jsonify({
100
+ 'error': f'Bir hata oluştu: {str(e)}'
101
+ }), 500
102
+
103
+
104
+
105
+
106
+ if __name__ == '__main__':
107
+ # Geliştirme sunucusunu başlat
108
+ print("\n" + "=" * 70)
109
+ print("TDK CHATBOT WEB UYGULAMASI")
110
+ print("=" * 70)
111
+ print("Uygulama: http://127.0.0.1:8080")
112
+
113
+
114
+ app.run(
115
+ host='0.0.0.0',
116
+ port=7860,
117
+ debug=True
118
+ )
prepare_system.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Tüm sistemi hazırlayan ana script.
3
+
4
+ Bu script:
5
+ 1. TDK veri setini yükler
6
+ 2. Veriyi işler
7
+ 3. Embedding'leri oluşturur
8
+ 4. Vector store'u hazırlar
9
+ """
10
+
11
+ import sys
12
+ import os
13
+
14
+ # src klasörünü path'e ekle
15
+ sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
16
+
17
+ from data_loader import TDKDataLoader
18
+ from embeddings import EmbeddingModel
19
+ from vector_store import FAISSVectorStore
20
+
21
+
22
+ def main():
23
+ """Ana hazırlık fonksiyonu."""
24
+
25
+ print("=" * 70)
26
+ print("TDK CHATBOT SİSTEM HAZIRLIĞI")
27
+ print("=" * 70)
28
+ print()
29
+
30
+ # ============================================
31
+ # ADIM 1: VERİ YÜKLEME
32
+ # ============================================
33
+ print("ADIM 1: Veri Yükleme")
34
+ print("-" * 70)
35
+
36
+ loader = TDKDataLoader(cache_dir="./data")
37
+
38
+ # Daha önce işlenmiş veri var mı kontrol et
39
+ processed_file = "./data/processed_tdk.json"
40
+
41
+ if os.path.exists(processed_file):
42
+ print("Daha önce işlenmiş veri bulundu, yükleniyor...")
43
+ documents = loader.load_processed_data()
44
+ else:
45
+ print("Veri ilk kez yükleniyor...")
46
+ loader.load_dataset()
47
+ loader.explore_data()
48
+ documents = loader.process_data()
49
+ loader.save_processed_data()
50
+
51
+ if not documents:
52
+ print("Veri yüklenemedi!")
53
+ return
54
+
55
+ print(f"Toplam {len(documents)} doküman hazır")
56
+ print()
57
+
58
+ # ============================================
59
+ # ADIM 2: EMBEDDING OLUŞTURMA
60
+ # ============================================
61
+ print("ADIM 2: Embedding Oluşturma")
62
+ print("-" * 70)
63
+
64
+ embeddings_file = "./data/embeddings.pkl"
65
+
66
+ # Daha önce oluşturulmuş embedding var mı kontrol et
67
+ if os.path.exists(embeddings_file):
68
+ print("Daha önce oluşturulmuş embedding'ler bulundu, yükleniyor...")
69
+ embedding_data = EmbeddingModel.load_embeddings(embeddings_file)
70
+ embeddings = embedding_data['embeddings']
71
+ valid_documents = embedding_data['documents']
72
+ else:
73
+ print("Embedding'ler oluşturuluyor (bu işlem biraz zaman alabilir)...")
74
+
75
+ # Embedding modelini yükle
76
+ embedder = EmbeddingModel()
77
+
78
+ # Dokümanları embedding'e çevir
79
+ embeddings, valid_documents = embedder.encode_documents(documents)
80
+
81
+ # Kaydet
82
+ embedder.save_embeddings(embeddings, valid_documents, embeddings_file)
83
+
84
+ print(f"{len(valid_documents)} doküman için embedding hazır")
85
+ print(f"Embedding shape: {embeddings.shape}")
86
+ print()
87
+
88
+ # ============================================
89
+ # ADIM 3: VECTOR STORE OLUŞTURMA
90
+ # ============================================
91
+ print("ADIM 3: Vector Store Oluşturma")
92
+ print("-" * 70)
93
+
94
+ vector_store_path = "./data/vector_store"
95
+
96
+ # Vector store oluştur
97
+ store = FAISSVectorStore(embedding_dim=embeddings.shape[1])
98
+ store.create_index(embeddings, valid_documents)
99
+
100
+ # Kaydet
101
+ store.save(vector_store_path)
102
+
103
+ # İstatistikleri göster
104
+ store.get_stats()
105
+ print()
106
+
107
+ # ============================================
108
+ # ADIM 4: SİSTEM TESTİ
109
+ # ============================================
110
+ print("ADIM 4: Sistem Testi")
111
+ print("-" * 70)
112
+
113
+ # Embedding modelini test için yükle
114
+ embedder = EmbeddingModel()
115
+
116
+ # Test sorguları
117
+ test_queries = [
118
+ "kitap ne demek",
119
+ "sevgi nedir",
120
+ "bilgisayar kelimesinin anlamı"
121
+ ]
122
+
123
+ print("Test sorguları ile arama yapılıyor...\n")
124
+
125
+ for query in test_queries:
126
+ print(f"Sorgu: '{query}'")
127
+
128
+ # Sorguyu embedding'e çevir
129
+ query_emb = embedder.encode_single(query)
130
+
131
+ # Arama yap
132
+ results = store.search(query_emb, top_k=2)
133
+
134
+ # Sonuçları göster
135
+ for i, result in enumerate(results, 1):
136
+ kelime = result['document'].get('kelime', 'N/A')
137
+ anlam = result['document'].get('anlam', 'N/A')
138
+ score = result['score']
139
+
140
+ print(f" {i}. Kelime: {kelime}")
141
+ print(f" Anlam: {anlam[:100]}...")
142
+ print(f" Benzerlik: {score:.4f}")
143
+ print()
144
+
145
+ # ============================================
146
+ # TAMAMLANDI
147
+ # ============================================
148
+ print("=" * 70)
149
+ print("SİSTEM HAZIRLIĞI TAMAMLANDI!")
150
+ print("=" * 70)
151
+ print()
152
+ print("Oluşturulan dosyalar:")
153
+ print(f" - {processed_file}")
154
+ print(f" - {embeddings_file}")
155
+ print(f" - {vector_store_path}.index")
156
+ print(f" - {vector_store_path}.pkl")
157
+ print()
158
+ print("Artık chatbot'u çalıştırmaya hazırsınız!")
159
+ print()
160
+
161
+
162
+ if __name__ == "__main__":
163
+ try:
164
+ main()
165
+ except KeyboardInterrupt:
166
+ print("\n\nİşlem kullanıcı tarafından durduruldu.")
167
+ except Exception as e:
168
+ print(f"\n\nHata oluştu: {e}")
169
+ import traceback
170
+
171
+ traceback.print_exc()
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Veri işleme
2
+ datasets==4.2.0
3
+ pandas==2.3.3
4
+ numpy==2.2.6
5
+
6
+ # Embedding ve Vector Store
7
+ sentence-transformers==5.1.1
8
+ chromadb==1.1.1
9
+ faiss-cpu==1.12.0
10
+
11
+ # LLM ve RAG
12
+ google-generativeai==0.8.3
13
+ langchain==0.3.27
14
+ langchain-core==0.3.79
15
+ langchain-google-genai==2.0.8
16
+ langchain-community==0.3.19
17
+
18
+ # Web Framework
19
+ Flask==3.1.2
20
+ python-dotenv==1.1.1
21
+
22
+ # Yardımcı
23
+ tqdm==4.67.1
src/__init__.py ADDED
File without changes
src/__pycache__/chatbot.cpython-312.pyc ADDED
Binary file (10.4 kB). View file
 
src/__pycache__/data_loader.cpython-312.pyc ADDED
Binary file (9.25 kB). View file
 
src/__pycache__/embeddings.cpython-312.pyc ADDED
Binary file (7.89 kB). View file
 
src/__pycache__/vector_store.cpython-312.pyc ADDED
Binary file (8.48 kB). View file
 
src/chatbot.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ RAG Chatbot - Gemini 2.0 ile TDK Sözlük asistanı.
3
+
4
+ Bu modül:
5
+ 1. Kullanıcı sorusunu alır
6
+ 2. Vector store'dan ilgili dokümanları bulur
7
+ 3. Gemini'ye gönderir
8
+ 4. Akıllı bir yanıt üretir
9
+ """
10
+
11
+ import google.generativeai as genai
12
+ from embeddings import EmbeddingModel
13
+ from vector_store import FAISSVectorStore
14
+ import os
15
+ from dotenv import load_dotenv
16
+
17
+
18
+ class TDKChatbot:
19
+ """TDK Sözlük RAG Chatbot."""
20
+
21
+ def __init__(self, api_key=None, vector_store_path="./data/vector_store"):
22
+ """
23
+ Args:
24
+ api_key: Gemini API anahtarı
25
+ vector_store_path: Vector store dosya yolu
26
+ """
27
+ # Environment variables yükle
28
+ load_dotenv()
29
+
30
+ # API key kontrolü
31
+ self.api_key = api_key or os.getenv('GEMINI_API_KEY')
32
+ if not self.api_key:
33
+ raise ValueError("GEMINI_API_KEY bulunamadı! .env dosyasını kontrol edin.")
34
+
35
+ print("TDK Chatbot başlatılıyor...")
36
+
37
+ # Gemini'yi yapılandır
38
+ genai.configure(api_key=self.api_key)
39
+
40
+ # Gemini modelini seç (2.0 Flash - hızlı ve güçlü)
41
+ self.model = genai.GenerativeModel('gemini-2.0-flash-exp')
42
+ print("Gemini 2.0 Flash modeli yüklendi")
43
+
44
+ # Embedding modelini yükle
45
+ print("Embedding modeli yükleniyor...")
46
+ self.embedder = EmbeddingModel()
47
+
48
+ # Vector store'u yükle
49
+ print("Vector store yükleniyor...")
50
+ self.vector_store = FAISSVectorStore()
51
+ if not self.vector_store.load(vector_store_path):
52
+ raise ValueError("Vector store yüklenemedi!")
53
+
54
+ print("Chatbot hazır!\n")
55
+
56
+ def search_relevant_docs(self, query, top_k=5):
57
+ """
58
+ Sorguyla ilgili dokümanları bulur.
59
+ Hem embedding benzerliği hem de kelime eşleştirme kullanır.
60
+
61
+ Args:
62
+ query: Kullanıcı sorusu
63
+ top_k: Kaç doküman getirilecek
64
+
65
+ Returns:
66
+ list: İlgili dokümanlar
67
+ """
68
+ # Sorguyu embedding'e çevir
69
+ query_embedding = self.embedder.encode_single(query)
70
+
71
+ # 1. Önce kelime bazlı eşleştirme yap (çok daha etkili!)
72
+ query_lower = query.lower()
73
+ query_words = query_lower.split()
74
+
75
+ # Sorgudan "ne demek", "nedir", "anlamı" gibi kelimeleri çıkar
76
+ stop_words = ['ne', 'nedir', 'demek', 'anlamı', 'anlam', 'kelimesinin',
77
+ 'kelimesi', 'nedir', 'açıklar', 'mısın', 'misin', 'anlamına',
78
+ 'hakkında', 'için', 'nasıl', 'bir', 'bu']
79
+
80
+ search_terms = [word for word in query_words if word not in stop_words and len(word) > 2]
81
+
82
+ # 2. Önce tam kelime eşleşmesi ara
83
+ exact_matches = []
84
+ if search_terms:
85
+ main_term = search_terms[0] # İlk anlamlı kelime
86
+
87
+ for i, doc in enumerate(self.vector_store.documents):
88
+ doc_kelime = doc.get('kelime', '').lower()
89
+
90
+ # Tam eşleşme
91
+ if doc_kelime == main_term:
92
+ exact_matches.append({
93
+ 'score': 1.0, # En yüksek skor
94
+ 'document': doc,
95
+ 'distance': 0.0,
96
+ 'match_type': 'exact'
97
+ })
98
+ # Kısmi eşleşme (kelime içeriyor)
99
+ elif main_term in doc_kelime or doc_kelime in main_term:
100
+ exact_matches.append({
101
+ 'score': 0.8,
102
+ 'document': doc,
103
+ 'distance': 0.2,
104
+ 'match_type': 'partial'
105
+ })
106
+
107
+ # 3. Eğer tam eşleşme varsa, önce onları döndür
108
+ if exact_matches:
109
+ # Skorlara göre sırala
110
+ exact_matches.sort(key=lambda x: x['score'], reverse=True)
111
+ return exact_matches[:top_k]
112
+
113
+ # 4. Tam eşleşme yoksa embedding araması yap
114
+ results = self.vector_store.search(query_embedding, top_k=top_k * 2)
115
+
116
+ # 5. Sonuçları filtrele - çok düşük skorları at
117
+ filtered_results = [r for r in results if r['score'] > 0.001]
118
+
119
+ return filtered_results[:top_k]
120
+
121
+ def create_context(self, results):
122
+ """
123
+ Bulunan dokümanlardan context oluşturur.
124
+
125
+ Args:
126
+ results: Arama sonuçları
127
+
128
+ Returns:
129
+ str: Context metni
130
+ """
131
+ if not results:
132
+ return "İlgili bilgi bulunamadı."
133
+
134
+ context = "İlgili TDK Sözlük bilgileri:\n\n"
135
+
136
+ for i, result in enumerate(results, 1):
137
+ doc = result['document']
138
+ kelime = doc.get('kelime', 'N/A')
139
+ anlam = doc.get('anlam', 'N/A')
140
+ text = doc.get('text', '')
141
+
142
+ context += f"{i}. **{kelime}**\n"
143
+ context += f" {anlam}\n"
144
+
145
+ # Örnek varsa ekle
146
+ if "Örnekler:" in text:
147
+ ornekler = text.split("Örnekler:")[1].strip()
148
+ if ornekler:
149
+ context += f" Örnekler: {ornekler[:200]}\n"
150
+
151
+ context += "\n"
152
+
153
+ return context
154
+
155
+ def generate_response(self, query, context):
156
+ """
157
+ Gemini ile yanıt üretir.
158
+
159
+ Args:
160
+ query: Kullanıcı sorusu
161
+ context: İlgili dokümanlar
162
+
163
+ Returns:
164
+ str: Gemini'nin yanıtı
165
+ """
166
+ # Prompt oluştur
167
+ prompt = f"""Sen TDK Sözlük asistanısın. Türkçe kelimeler hakkında bilgi veren yardımcı bir asistandsın.
168
+
169
+ GÖREV:
170
+ Kullanıcının sorusunu aşağıdaki TDK Sözlük bilgilerine göre yanıtla.
171
+
172
+ KURALLAR:
173
+ 1. Sadece verilen TDK bilgilerini kullan
174
+ 2. Net, anlaşılır ve dostça yanıt ver
175
+ 3. Kelime anlamlarını açıklarken örnekler ver
176
+ 4. Bilgi yoksa "Bu kelime hakkında TDK Sözlük'te bilgi bulamadım" de
177
+ 5. Türkçe dilbilgisi kurallarına uy
178
+
179
+ TDK SÖZLÜK BİLGİLERİ:
180
+ {context}
181
+
182
+ KULLANICI SORUSU:
183
+ {query}
184
+
185
+ YANITINIZ:"""
186
+
187
+ try:
188
+ # Gemini'den yanıt al
189
+ response = self.model.generate_content(prompt)
190
+ return response.text
191
+
192
+ except Exception as e:
193
+ return f"Yanıt oluşturulurken hata: {str(e)}"
194
+
195
+ def chat(self, query, top_k=5, show_context=False):
196
+ """
197
+ Ana chatbot fonksiyonu.
198
+
199
+ Args:
200
+ query: Kullanıcı sorusu
201
+ top_k: Kaç doküman kullanılacak
202
+ show_context: Context'i göster
203
+
204
+ Returns:
205
+ dict: Yanıt ve metadata
206
+ """
207
+ if not query or not query.strip():
208
+ return {
209
+ 'response': "Lütfen bir soru sorun.",
210
+ 'context': None,
211
+ 'results': []
212
+ }
213
+
214
+ # 1. İlgili dokümanları bul
215
+ results = self.search_relevant_docs(query, top_k=top_k)
216
+
217
+ if not results:
218
+ return {
219
+ 'response': "Bu konuda TDK Sözlük'te bilgi bulamadım. Başka bir şey sorar mısınız?",
220
+ 'context': None,
221
+ 'results': []
222
+ }
223
+
224
+ # 2. Context oluştur
225
+ context = self.create_context(results)
226
+
227
+ # 3. Gemini ile yanıt üret
228
+ response = self.generate_response(query, context)
229
+
230
+ # 4. Sonucu döndür
231
+ result = {
232
+ 'response': response,
233
+ 'results': results,
234
+ 'query': query
235
+ }
236
+
237
+ if show_context:
238
+ result['context'] = context
239
+
240
+ return result
241
+
242
+ def interactive_mode(self):
243
+ """Terminal'de interaktif sohbet modu."""
244
+ print("=" * 70)
245
+ print("TDK CHATBOT - İNTERAKTİF MOD")
246
+ print("=" * 70)
247
+ print("Türkçe kelimeler hakkında soru sorun!")
248
+ print("Çıkmak için 'exit', 'quit' veya 'çıkış' yazın.\n")
249
+
250
+ while True:
251
+ try:
252
+ # Kullanıcı girişi al
253
+ query = input("Siz: ").strip()
254
+
255
+ # Çıkış kontrolü
256
+ if query.lower() in ['exit', 'quit', 'çıkış', 'q']:
257
+ print("\nGörüşmek üzere!")
258
+ break
259
+
260
+ if not query:
261
+ continue
262
+
263
+ # Yanıt üret
264
+ print("\nTDK Asistanı düşünüyor...\n")
265
+ result = self.chat(query)
266
+
267
+ # Yanıtı göster
268
+ print(f"TDK Asistanı: {result['response']}\n")
269
+
270
+ # Kaynak kelimeleri göster
271
+ if result['results']:
272
+ print("Kaynak kelimeler:", end=" ")
273
+ kelimeler = [r['document']['kelime'] for r in result['results'][:3]]
274
+ print(", ".join(kelimeler))
275
+ print()
276
+
277
+ except KeyboardInterrupt:
278
+ print("\n\nGörüşmek üzere!")
279
+ break
280
+ except Exception as e:
281
+ print(f"\nHata: {e}\n")
282
+
283
+
284
+ # Test için main fonksiyonu
285
+ if __name__ == "__main__":
286
+ try:
287
+ # Chatbot'u başlat
288
+ chatbot = TDKChatbot()
289
+
290
+ # Test sorguları
291
+ test_queries = [
292
+ "kitap ne demek?",
293
+ "sevgi kelimesinin anlamı nedir?",
294
+ "bilgisayar nedir?",
295
+ "merhaba kelimesini açıklar mısın?"
296
+ ]
297
+
298
+ print("=" * 70)
299
+ print("TEST SORULARI")
300
+ print("=" * 70)
301
+ print()
302
+
303
+ for query in test_queries:
304
+ print(f"Soru: {query}")
305
+ result = chatbot.chat(query, top_k=3)
306
+ print(f"Yanıt: {result['response']}\n")
307
+ print("-" * 70)
308
+ print()
309
+
310
+ # İnteraktif mod başlat
311
+ chatbot.interactive_mode()
312
+
313
+ except Exception as e:
314
+ print(f"Hata: {e}")
315
+ import traceback
316
+
317
+ traceback.print_exc()
src/data_loader.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TDK Sözlük veri setini yükleyen ve işleyen modül.
3
+
4
+ Bu modül Hugging Face'den veri setini indirir, temizler ve
5
+ RAG sistemi için uygun formata dönüştürür.
6
+ """
7
+
8
+ from datasets import load_dataset
9
+ import pandas as pd
10
+ from tqdm import tqdm
11
+ import json
12
+ import os
13
+
14
+
15
+ class TDKDataLoader:
16
+ """TDK Sözlük veri setini yükler ve işler."""
17
+
18
+ def __init__(self, cache_dir="./data"):
19
+ """
20
+ Args:
21
+ cache_dir: Veri setinin kaydedileceği klasör
22
+ """
23
+ self.cache_dir = cache_dir
24
+ self.dataset = None
25
+ self.processed_data = []
26
+
27
+ # Klasör yoksa oluştur
28
+ os.makedirs(cache_dir, exist_ok=True)
29
+
30
+ def load_dataset(self):
31
+ """
32
+ Hugging Face'den TDK Sözlük veri setini yükler.
33
+
34
+ Returns:
35
+ Dataset objesi
36
+ """
37
+ print("📚 TDK Sözlük veri seti yükleniyor...")
38
+
39
+ try:
40
+ # Veri setini yükle
41
+ self.dataset = load_dataset("Ba2han/TDK_Sozluk-Turkish-v2")
42
+ print(f"Veri seti başarıyla yüklendi!")
43
+ print(f"Toplam kayıt sayısı: {len(self.dataset['train'])}")
44
+
45
+ return self.dataset
46
+
47
+ except Exception as e:
48
+ print(f"Veri seti yüklenirken hata: {e}")
49
+ return None
50
+
51
+ def explore_data(self):
52
+ """Veri setinin yapısını inceler ve örnek gösterir."""
53
+
54
+ if self.dataset is None:
55
+ print("Önce veri setini yüklemelisiniz!")
56
+ return
57
+
58
+ print("\n" + "=" * 60)
59
+ print("VERİ SETİ YAPISI")
60
+ print("=" * 60)
61
+
62
+ # İlk kaydı göster
63
+ first_item = self.dataset['train'][0]
64
+
65
+ print("\nİlk kayıt örneği:")
66
+ print("-" * 60)
67
+ for key, value in first_item.items():
68
+ # Uzun metinleri kısalt
69
+ if isinstance(value, str) and len(value) > 200:
70
+ print(f"{key}: {value[:200]}...")
71
+ else:
72
+ print(f"{key}: {value}")
73
+
74
+ print("\nSütun bilgileri:")
75
+ print("-" * 60)
76
+ for key in first_item.keys():
77
+ print(f"• {key}")
78
+
79
+ # Birkaç örnek daha göster
80
+ print("\nÖrnek kelimeler:")
81
+ print("-" * 60)
82
+ for i in range(min(10, len(self.dataset['train']))):
83
+ item = self.dataset['train'][i]
84
+ madde = item.get('madde', 'N/A')
85
+ anlam = item.get('anlam', '')
86
+
87
+ # Anlam tipini göster
88
+ anlam_type = type(anlam).__name__
89
+ print(f"{i + 1}. {madde} (anlam tipi: {anlam_type})")
90
+
91
+ # İlk anlamı göster
92
+ if isinstance(anlam, str) and anlam:
93
+ try:
94
+ import json
95
+ anlam_parsed = json.loads(anlam)
96
+ if isinstance(anlam_parsed, list) and anlam_parsed:
97
+ first_anlam = anlam_parsed[0]
98
+ if isinstance(first_anlam, dict):
99
+ print(f" → {first_anlam.get('anlam', 'N/A')[:100]}")
100
+ except:
101
+ print(f" → {anlam[:100]}")
102
+
103
+ def process_data(self):
104
+ """
105
+ Veri setini RAG için uygun formata dönüştürür.
106
+
107
+ Bu veri setinde her satır tek bir kelime-anlam çifti içeriyor.
108
+ Çok basit ve düz bir yapı.
109
+ """
110
+
111
+ if self.dataset is None:
112
+ print("Önce veri setini yüklemelisiniz!")
113
+ return None
114
+
115
+ print("\nVeri işleniyor...")
116
+
117
+ self.processed_data = []
118
+ error_count = 0
119
+
120
+ # Her kelimeyi işle
121
+ for item in tqdm(self.dataset['train'], desc="İşleniyor"):
122
+ try:
123
+ # Alanları al - None değerleri boş string'e çevir
124
+ kelime = str(item.get('madde', '')).strip()
125
+ anlam = str(item.get('anlam', '')).strip()
126
+ ornek = str(item.get('ornek', '') or '').strip()
127
+ ai_ornek = str(item.get('ai_ornek', '') or '').strip()
128
+
129
+ # Kelime veya anlam boş ise atla
130
+ if not kelime or not anlam or kelime == 'None' or anlam == 'None':
131
+ error_count += 1
132
+ continue
133
+
134
+ # Tam metin oluştur (RAG için zengin context)
135
+ full_text = f"Kelime: {kelime}\n\n"
136
+ full_text += f"Anlam: {anlam}\n"
137
+
138
+ # Örnek varsa ekle
139
+ if ornek and ornek != 'None':
140
+ full_text += f"\nÖrnek kullanım: {ornek}\n"
141
+
142
+ # AI örneği varsa ekle (daha detaylı)
143
+ if ai_ornek and ai_ornek != 'None':
144
+ full_text += f"\nDetaylı örnek: {ai_ornek}\n"
145
+
146
+ # Doküman oluştur
147
+ doc = {
148
+ 'text': full_text.strip(),
149
+ 'kelime': kelime,
150
+ 'anlam': anlam,
151
+ 'ornek': ornek if (ornek and ornek != 'None') else None,
152
+ 'ai_ornek': ai_ornek if (ai_ornek and ai_ornek != 'None') else None
153
+ }
154
+
155
+ self.processed_data.append(doc)
156
+
157
+ except Exception as e:
158
+ error_count += 1
159
+ continue
160
+
161
+ print(f"{len(self.processed_data)} doküman oluşturuldu!")
162
+ if error_count > 0:
163
+ print(f"{error_count} kayıt işlenirken hata oluştu (atlandı)")
164
+
165
+ return self.processed_data
166
+
167
+ def save_processed_data(self, filename="processed_tdk.json"):
168
+ """İşlenmiş veriyi JSON olarak kaydeder."""
169
+
170
+ if not self.processed_data:
171
+ print("Önce veriyi işlemelisiniz!")
172
+ return
173
+
174
+ filepath = os.path.join(self.cache_dir, filename)
175
+
176
+ with open(filepath, 'w', encoding='utf-8') as f:
177
+ json.dump(self.processed_data, f, ensure_ascii=False, indent=2)
178
+
179
+ print(f"Veri kaydedildi: {filepath}")
180
+
181
+ def load_processed_data(self, filename="processed_tdk.json"):
182
+ """Daha önce kaydedilmiş işlenmiş veriyi yükler."""
183
+
184
+ filepath = os.path.join(self.cache_dir, filename)
185
+
186
+ if not os.path.exists(filepath):
187
+ print(f"Dosya bulunamadı: {filepath}")
188
+ return None
189
+
190
+ with open(filepath, 'r', encoding='utf-8') as f:
191
+ self.processed_data = json.load(f)
192
+
193
+ print(f"{len(self.processed_data)} doküman yüklendi!")
194
+ return self.processed_data
195
+
196
+
197
+ # Test için main fonksiyonu
198
+ if __name__ == "__main__":
199
+ # Veri yükleyiciyi oluştur
200
+ loader = TDKDataLoader()
201
+
202
+ # Veri setini yükle
203
+ loader.load_dataset()
204
+
205
+ # Veri yapısını incele
206
+ loader.explore_data()
207
+
208
+ # Veriyi işle
209
+ loader.process_data()
210
+
211
+ # İlk 3 dokümanı göster
212
+ if loader.processed_data:
213
+ print("\n" + "=" * 60)
214
+ print("İLK 3 İŞLENMİŞ DOKÜMAN")
215
+ print("=" * 60)
216
+ for i, doc in enumerate(loader.processed_data[:3], 1):
217
+ print(f"\n{i}. Doküman:")
218
+ print(f"Kelime: {doc['kelime']}")
219
+ print(f"Text:\n{doc['text']}")
220
+
221
+ # İşlenmiş veriyi kaydet
222
+ loader.save_processed_data()
223
+
224
+ print("\n✨ İşlem tamamlandı!")
src/embeddings.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Embedding modeli yönetimi.
3
+
4
+ Bu modül metinleri sayısal vektörlere dönüştürmek için
5
+ sentence-transformers kütüphanesini kullanır.
6
+ """
7
+
8
+ from sentence_transformers import SentenceTransformer
9
+ import numpy as np
10
+ from tqdm import tqdm
11
+ import pickle
12
+ import os
13
+
14
+
15
+ class EmbeddingModel:
16
+ """Türkçe metinler için embedding modeli."""
17
+
18
+ def __init__(self, model_name="emrecan/bert-base-turkish-cased-mean-nli-stsb-tr"):
19
+ """
20
+ Args:
21
+ model_name: Kullanılacak embedding modeli.
22
+ Türkçe için özel eğitilmiş model kullanıyoruz.
23
+ """
24
+ print(f"🤖 Embedding modeli yükleniyor: {model_name}")
25
+
26
+ try:
27
+ self.model = SentenceTransformer(model_name)
28
+ self.model_name = model_name
29
+ print("Model başarıyla yüklendi!")
30
+
31
+ # Model bilgilerini göster
32
+ embedding_dim = self.model.get_sentence_embedding_dimension()
33
+ print(f"Embedding boyutu: {embedding_dim}")
34
+
35
+ except Exception as e:
36
+ print(f"Model yüklenirken hata: {e}")
37
+ raise
38
+
39
+ def encode_single(self, text):
40
+ """
41
+ Tek bir metni embedding'e çevirir.
42
+
43
+ Args:
44
+ text: Çevrilecek metin
45
+
46
+ Returns:
47
+ numpy array: Embedding vektörü
48
+ """
49
+ if not text or not isinstance(text, str):
50
+ return None
51
+
52
+ try:
53
+ embedding = self.model.encode(text, convert_to_numpy=True)
54
+ return embedding
55
+ except Exception as e:
56
+ print(f"Encoding hatası: {e}")
57
+ return None
58
+
59
+ def encode_batch(self, texts, batch_size=32, show_progress=True):
60
+ """
61
+ Birden fazla metni toplu olarak embedding'e çevirir.
62
+
63
+ Args:
64
+ texts: Metin listesi
65
+ batch_size: Aynı anda işlenecek metin sayısı
66
+ show_progress: İlerleme çubuğu göster
67
+
68
+ Returns:
69
+ numpy array: Embedding matrisi (n_texts, embedding_dim)
70
+ """
71
+ if not texts:
72
+ return np.array([])
73
+
74
+ print(f"{len(texts)} metin embedding'e çevriliyor...")
75
+
76
+ try:
77
+ embeddings = self.model.encode(
78
+ texts,
79
+ batch_size=batch_size,
80
+ show_progress_bar=show_progress,
81
+ convert_to_numpy=True
82
+ )
83
+
84
+ print(f"Embedding tamamlandı! Shape: {embeddings.shape}")
85
+ return embeddings
86
+
87
+ except Exception as e:
88
+ print(f"Batch encoding hatası: {e}")
89
+ return None
90
+
91
+ def encode_documents(self, documents, text_key='text'):
92
+ """
93
+ Doküman listesini embedding'e çevirir.
94
+
95
+ Args:
96
+ documents: Doküman listesi (dict formatında)
97
+ text_key: Metin alanının key'i
98
+
99
+ Returns:
100
+ tuple: (embeddings, valid_documents)
101
+ """
102
+ if not documents:
103
+ return None, []
104
+
105
+ # Metinleri çıkar
106
+ texts = []
107
+ valid_docs = []
108
+
109
+ for doc in documents:
110
+ text = doc.get(text_key, '')
111
+ if text and isinstance(text, str):
112
+ texts.append(text)
113
+ valid_docs.append(doc)
114
+
115
+ print(f"{len(valid_docs)} geçerli doküman bulundu")
116
+
117
+ # Embedding'leri oluştur
118
+ embeddings = self.encode_batch(texts)
119
+
120
+ return embeddings, valid_docs
121
+
122
+ def save_embeddings(self, embeddings, documents, filepath):
123
+ """
124
+ Embedding'leri ve dokümanları kaydeder.
125
+
126
+ Args:
127
+ embeddings: Embedding matrisi
128
+ documents: Doküman listesi
129
+ filepath: Kayıt yolu
130
+ """
131
+ data = {
132
+ 'embeddings': embeddings,
133
+ 'documents': documents,
134
+ 'model_name': self.model_name
135
+ }
136
+
137
+ # Klasör yoksa oluştur
138
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
139
+
140
+ with open(filepath, 'wb') as f:
141
+ pickle.dump(data, f)
142
+
143
+ print(f"Embedding'ler kaydedildi: {filepath}")
144
+
145
+ @staticmethod
146
+ def load_embeddings(filepath):
147
+ """
148
+ Kaydedilmiş embedding'leri yükler.
149
+
150
+ Args:
151
+ filepath: Dosya yolu
152
+
153
+ Returns:
154
+ dict: {'embeddings', 'documents', 'model_name'}
155
+ """
156
+ if not os.path.exists(filepath):
157
+ print(f"Dosya bulunamadı: {filepath}")
158
+ return None
159
+
160
+ with open(filepath, 'rb') as f:
161
+ data = pickle.load(f)
162
+
163
+ print(f"Embedding'ler yüklendi: {filepath}")
164
+ print(f"Embedding shape: {data['embeddings'].shape}")
165
+ print(f"Model: {data['model_name']}")
166
+
167
+ return data
168
+
169
+ def test_similarity(self, text1, text2):
170
+ """
171
+ İki metin arasındaki benzerliği test eder.
172
+
173
+ Args:
174
+ text1, text2: Karşılaştırılacak metinler
175
+
176
+ Returns:
177
+ float: Benzerlik skoru (0-1 arası)
178
+ """
179
+ emb1 = self.encode_single(text1)
180
+ emb2 = self.encode_single(text2)
181
+
182
+ if emb1 is None or emb2 is None:
183
+ return None
184
+
185
+ # Cosine benzerliği hesapla
186
+ similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
187
+
188
+ print(f"\nBenzerlik Testi:")
189
+ print(f"Metin 1: {text1}")
190
+ print(f"Metin 2: {text2}")
191
+ print(f"Benzerlik: {similarity:.4f}")
192
+
193
+ return float(similarity)
194
+
195
+
196
+ # Test için main fonksiyonu
197
+ if __name__ == "__main__":
198
+ # Embedding modelini oluştur
199
+ embedder = EmbeddingModel()
200
+
201
+ # Basit test
202
+ print("\n" + "=" * 60)
203
+ print("BENZERLİK TESTİ")
204
+ print("=" * 60)
205
+
206
+ # Test metinleri
207
+ embedder.test_similarity(
208
+ "Kitap okumayı seviyorum",
209
+ "Okumak benim hobimdir"
210
+ )
211
+
212
+ embedder.test_similarity(
213
+ "Kitap okumayı seviyorum",
214
+ "Futbol oynamak eğlencelidir"
215
+ )
216
+
217
+ print("\n✨ Test tamamlandı!")
src/vector_store.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Vector Store yönetimi - FAISS kullanarak.
3
+
4
+ FAISS (Facebook AI Similarity Search):
5
+ Vektörler arasında çok hızlı benzerlik araması yapan kütüphane.
6
+ """
7
+
8
+ import faiss
9
+ import numpy as np
10
+ import pickle
11
+ import os
12
+ from typing import List, Tuple
13
+
14
+
15
+ class FAISSVectorStore:
16
+ """FAISS tabanlı vektör veritabanı."""
17
+
18
+ def __init__(self, embedding_dim=768):
19
+ """
20
+ Args:
21
+ embedding_dim: Embedding vektörlerinin boyutu
22
+ """
23
+ self.embedding_dim = embedding_dim
24
+ self.index = None
25
+ self.documents = []
26
+ self.is_trained = False
27
+
28
+ def create_index(self, embeddings, documents):
29
+ """
30
+ FAISS index'i oluşturur ve embedding'leri ekler.
31
+
32
+ Args:
33
+ embeddings: numpy array (n_docs, embedding_dim)
34
+ documents: Doküman listesi
35
+ """
36
+ print(f"🔨 FAISS index oluşturuluyor...")
37
+ print(f"Embedding shape: {embeddings.shape}")
38
+
39
+ # Boyut kontrolü
40
+ if embeddings.shape[1] != self.embedding_dim:
41
+ self.embedding_dim = embeddings.shape[1]
42
+ print(f"⚙️ Embedding boyutu güncellendi: {self.embedding_dim}")
43
+
44
+ # L2 (Euclidean) mesafe kullanarak index oluştur
45
+ # IndexFlatL2: En basit ve en doğru index tipi
46
+ self.index = faiss.IndexFlatL2(self.embedding_dim)
47
+
48
+ # Embedding'leri float32'ye çevir (FAISS zorunluluğu)
49
+ embeddings = embeddings.astype('float32')
50
+
51
+ # Index'e embedding'leri ekle
52
+ self.index.add(embeddings)
53
+ self.documents = documents
54
+ self.is_trained = True
55
+
56
+ print(f"Index oluşturuldu!")
57
+ print(f"Toplam doküman sayısı: {self.index.ntotal}")
58
+
59
+ def search(self, query_embedding, top_k=5):
60
+ """
61
+ Sorgu embedding'ine en benzer dokümanları bulur.
62
+
63
+ Args:
64
+ query_embedding: Sorgu vektörü
65
+ top_k: Kaç sonuç döndürülecek
66
+
67
+ Returns:
68
+ list: (skor, doküman) tuple'larının listesi
69
+ """
70
+ if not self.is_trained:
71
+ print("Index henüz oluşturulmamış!")
72
+ return []
73
+
74
+ # Query'yi doğru formata çevir
75
+ query_embedding = np.array([query_embedding]).astype('float32')
76
+
77
+ # Arama yap
78
+ distances, indices = self.index.search(query_embedding, top_k)
79
+
80
+ # Sonuçları hazırla
81
+ results = []
82
+ for dist, idx in zip(distances[0], indices[0]):
83
+ if idx < len(self.documents):
84
+ # Mesafeyi benzerlik skoruna çevir (düşük mesafe = yüksek benzerlik)
85
+ similarity = 1 / (1 + dist)
86
+ results.append({
87
+ 'score': float(similarity),
88
+ 'document': self.documents[idx],
89
+ 'distance': float(dist)
90
+ })
91
+
92
+ return results
93
+
94
+ def save(self, filepath):
95
+ """
96
+ Index ve dokümanları kaydeder.
97
+
98
+ Args:
99
+ filepath: Kayıt yolu (uzantısız)
100
+ """
101
+ if not self.is_trained:
102
+ print("Kaydedilecek bir index yok!")
103
+ return
104
+
105
+ # Klasör yoksa oluştur
106
+ os.makedirs(os.path.dirname(filepath) if os.path.dirname(filepath) else '.', exist_ok=True)
107
+
108
+ # FAISS index'i kaydet
109
+ index_path = f"{filepath}.index"
110
+ faiss.write_index(self.index, index_path)
111
+
112
+ # Dokümanları kaydet
113
+ docs_path = f"{filepath}.pkl"
114
+ with open(docs_path, 'wb') as f:
115
+ pickle.dump({
116
+ 'documents': self.documents,
117
+ 'embedding_dim': self.embedding_dim
118
+ }, f)
119
+
120
+ print(f"Vector store kaydedildi:")
121
+ print(f" - Index: {index_path}")
122
+ print(f" - Dokümanlar: {docs_path}")
123
+
124
+ def load(self, filepath):
125
+ """
126
+ Kaydedilmiş index ve dokümanları yükler.
127
+
128
+ Args:
129
+ filepath: Dosya yolu (uzantısız)
130
+ """
131
+ index_path = f"{filepath}.index"
132
+ docs_path = f"{filepath}.pkl"
133
+
134
+ # Dosya kontrolü
135
+ if not os.path.exists(index_path) or not os.path.exists(docs_path):
136
+ print("Dosyalar bulunamadı!")
137
+ return False
138
+
139
+ # Index'i yükle
140
+ self.index = faiss.read_index(index_path)
141
+
142
+ # Dokümanları yükle
143
+ with open(docs_path, 'rb') as f:
144
+ data = pickle.load(f)
145
+ self.documents = data['documents']
146
+ self.embedding_dim = data['embedding_dim']
147
+
148
+ self.is_trained = True
149
+
150
+ print(f"Vector store yüklendi:")
151
+ print(f"Doküman sayısı: {len(self.documents)}")
152
+ print(f"Embedding boyutu: {self.embedding_dim}")
153
+
154
+ return True
155
+
156
+ def get_stats(self):
157
+ """Index istatistiklerini gösterir."""
158
+ if not self.is_trained:
159
+ print("Index henüz oluşturulmamış!")
160
+ return
161
+
162
+ print("\n" + "=" * 60)
163
+ print("VECTOR STORE İSTATİSTİKLERİ")
164
+ print("=" * 60)
165
+ print(f"Toplam doküman: {self.index.ntotal}")
166
+ print(f"Embedding boyutu: {self.embedding_dim}")
167
+ print(f"Index tipi: {type(self.index).__name__}")
168
+ print("=" * 60)
169
+
170
+
171
+ # Test için main fonksiyonu
172
+ if __name__ == "__main__":
173
+ print("Vector Store Test Başlıyor...\n")
174
+
175
+ # Test için sahte veri oluştur
176
+ n_docs = 100
177
+ embedding_dim = 768
178
+
179
+ # Rastgele embedding'ler
180
+ test_embeddings = np.random.rand(n_docs, embedding_dim).astype('float32')
181
+
182
+ # Test dokümanları
183
+ test_documents = [
184
+ {'text': f'Test doküman {i}', 'id': i}
185
+ for i in range(n_docs)
186
+ ]
187
+
188
+ # Vector store oluştur
189
+ store = FAISSVectorStore(embedding_dim=embedding_dim)
190
+ store.create_index(test_embeddings, test_documents)
191
+
192
+ # İstatistikleri göster
193
+ store.get_stats()
194
+
195
+ # Arama testi
196
+ print("\nArama testi yapılıyor...")
197
+ query = np.random.rand(embedding_dim).astype('float32')
198
+ results = store.search(query, top_k=3)
199
+
200
+ print(f"\nEn benzer {len(results)} doküman:")
201
+ for i, result in enumerate(results, 1):
202
+ print(f"{i}. Skor: {result['score']:.4f} - {result['document']['text']}")
203
+
204
+ # Kaydetme testi
205
+ print("\nKaydetme testi...")
206
+ store.save("./data/test_store")
207
+
208
+ # Yükleme testi
209
+ print("\nYükleme testi...")
210
+ new_store = FAISSVectorStore()
211
+ new_store.load("./data/test_store")
212
+
213
+ print("\n✨ Test tamamlandı!")
static/style.css ADDED
@@ -0,0 +1,430 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* TDK Chatbot - Modern ve Profesyonel Tasarım */
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ font-family: 'Segoe UI', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ min-height: 100vh;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ padding: 20px;
17
+ }
18
+
19
+ /* Ana Container */
20
+ .container {
21
+ max-width: 1000px;
22
+ width: 100%;
23
+ background: white;
24
+ border-radius: 24px;
25
+ box-shadow: 0 20px 80px rgba(0,0,0,0.3);
26
+ overflow: hidden;
27
+ backdrop-filter: blur(10px);
28
+ }
29
+
30
+ /* Header */
31
+ .header {
32
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
33
+ color: white;
34
+ padding: 35px 40px;
35
+ text-align: center;
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .header::before {
41
+ content: '';
42
+ position: absolute;
43
+ top: -50%;
44
+ left: -50%;
45
+ width: 200%;
46
+ height: 200%;
47
+ background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
48
+ animation: pulse 15s ease-in-out infinite;
49
+ }
50
+
51
+ @keyframes pulse {
52
+ 0%, 100% { transform: scale(1) rotate(0deg); }
53
+ 50% { transform: scale(1.1) rotate(180deg); }
54
+ }
55
+
56
+ .header h1 {
57
+ font-size: 2.2em;
58
+ margin-bottom: 12px;
59
+ font-weight: 700;
60
+ position: relative;
61
+ z-index: 1;
62
+ text-shadow: 0 2px 10px rgba(0,0,0,0.2);
63
+ }
64
+
65
+ .header p {
66
+ opacity: 0.95;
67
+ font-size: 1.15em;
68
+ position: relative;
69
+ z-index: 1;
70
+ font-weight: 400;
71
+ }
72
+
73
+ /* Chat Container */
74
+ .chat-container {
75
+ height: 550px;
76
+ overflow-y: auto;
77
+ padding: 35px;
78
+ background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
79
+ scroll-behavior: smooth;
80
+ }
81
+
82
+ /* Scrollbar Styling */
83
+ .chat-container::-webkit-scrollbar {
84
+ width: 8px;
85
+ }
86
+
87
+ .chat-container::-webkit-scrollbar-track {
88
+ background: #f1f1f1;
89
+ border-radius: 10px;
90
+ }
91
+
92
+ .chat-container::-webkit-scrollbar-thumb {
93
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
94
+ border-radius: 10px;
95
+ }
96
+
97
+ .chat-container::-webkit-scrollbar-thumb:hover {
98
+ background: linear-gradient(135deg, #5568d3 0%, #653a8b 100%);
99
+ }
100
+
101
+ /* Messages */
102
+ .message {
103
+ margin-bottom: 24px;
104
+ display: flex;
105
+ gap: 12px;
106
+ animation: fadeSlideIn 0.4s ease-out;
107
+ }
108
+
109
+ @keyframes fadeSlideIn {
110
+ from {
111
+ opacity: 0;
112
+ transform: translateY(20px);
113
+ }
114
+ to {
115
+ opacity: 1;
116
+ transform: translateY(0);
117
+ }
118
+ }
119
+
120
+ .message.user {
121
+ justify-content: flex-end;
122
+ }
123
+
124
+ .message-content {
125
+ max-width: 75%;
126
+ padding: 16px 22px;
127
+ border-radius: 18px;
128
+ word-wrap: break-word;
129
+ line-height: 1.6;
130
+ box-shadow: 0 2px 10px rgba(0,0,0,0.08);
131
+ transition: transform 0.2s, box-shadow 0.2s;
132
+ }
133
+
134
+ .message-content:hover {
135
+ transform: translateY(-2px);
136
+ box-shadow: 0 4px 20px rgba(0,0,0,0.12);
137
+ }
138
+
139
+ .user .message-content {
140
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
141
+ color: white;
142
+ border-bottom-right-radius: 6px;
143
+ font-weight: 500;
144
+ }
145
+
146
+ .bot .message-content {
147
+ background: white;
148
+ color: #2d3748;
149
+ border: 1.5px solid #e2e8f0;
150
+ border-bottom-left-radius: 6px;
151
+ }
152
+
153
+ .bot .message-content strong {
154
+ color: #667eea;
155
+ font-weight: 600;
156
+ }
157
+
158
+ .bot .message-content em {
159
+ font-style: italic;
160
+ color: #4a5568;
161
+ }
162
+
163
+ /* Sources */
164
+ .sources {
165
+ margin-top: 18px;
166
+ padding: 18px;
167
+ background: linear-gradient(to bottom, #f7fafc 0%, #edf2f7 100%);
168
+ border-radius: 12px;
169
+ font-size: 0.92em;
170
+ border: 1px solid #e2e8f0;
171
+ }
172
+
173
+ .sources h4 {
174
+ margin-bottom: 12px;
175
+ color: #667eea;
176
+ font-weight: 600;
177
+ font-size: 1.05em;
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 8px;
181
+ }
182
+
183
+ .source-item {
184
+ margin-bottom: 10px;
185
+ padding: 12px 14px;
186
+ background: white;
187
+ border-radius: 8px;
188
+ border-left: 3px solid #667eea;
189
+ transition: all 0.2s;
190
+ }
191
+
192
+ .source-item:hover {
193
+ border-left-color: #764ba2;
194
+ transform: translateX(4px);
195
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15);
196
+ }
197
+
198
+ .source-item strong {
199
+ color: #764ba2;
200
+ display: block;
201
+ margin-bottom: 6px;
202
+ font-size: 1.05em;
203
+ }
204
+
205
+ /* Input Container */
206
+ .input-container {
207
+ padding: 25px 35px;
208
+ background: white;
209
+ border-top: 2px solid #f0f0f0;
210
+ box-shadow: 0 -5px 20px rgba(0,0,0,0.05);
211
+ }
212
+
213
+ .input-form {
214
+ display: flex;
215
+ gap: 12px;
216
+ align-items: center;
217
+ }
218
+
219
+ #messageInput {
220
+ flex: 1;
221
+ padding: 16px 24px;
222
+ border: 2px solid #e2e8f0;
223
+ border-radius: 30px;
224
+ font-size: 1.02em;
225
+ outline: none;
226
+ transition: all 0.3s;
227
+ font-family: inherit;
228
+ }
229
+
230
+ #messageInput:focus {
231
+ border-color: #667eea;
232
+ box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
233
+ }
234
+
235
+ #messageInput::placeholder {
236
+ color: #a0aec0;
237
+ }
238
+
239
+ #sendButton {
240
+ padding: 16px 40px;
241
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
242
+ color: white;
243
+ border: none;
244
+ border-radius: 30px;
245
+ font-size: 1.02em;
246
+ font-weight: 600;
247
+ cursor: pointer;
248
+ transition: all 0.3s;
249
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
250
+ }
251
+
252
+ #sendButton:hover {
253
+ transform: translateY(-3px);
254
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
255
+ }
256
+
257
+ #sendButton:active {
258
+ transform: translateY(-1px);
259
+ }
260
+
261
+ #sendButton:disabled {
262
+ opacity: 0.6;
263
+ cursor: not-allowed;
264
+ transform: none;
265
+ }
266
+
267
+ /* Loading Animation */
268
+ .loading {
269
+ display: none;
270
+ text-align: center;
271
+ padding: 12px;
272
+ }
273
+
274
+ .loading.active {
275
+ display: block;
276
+ }
277
+
278
+ .loading-dots {
279
+ display: inline-flex;
280
+ gap: 6px;
281
+ }
282
+
283
+ .loading-dots span {
284
+ width: 10px;
285
+ height: 10px;
286
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
287
+ border-radius: 50%;
288
+ animation: bounce 1.4s infinite ease-in-out;
289
+ }
290
+
291
+ .loading-dots span:nth-child(1) {
292
+ animation-delay: -0.32s;
293
+ }
294
+
295
+ .loading-dots span:nth-child(2) {
296
+ animation-delay: -0.16s;
297
+ }
298
+
299
+ @keyframes bounce {
300
+ 0%, 80%, 100% {
301
+ transform: scale(0);
302
+ opacity: 0.5;
303
+ }
304
+ 40% {
305
+ transform: scale(1);
306
+ opacity: 1;
307
+ }
308
+ }
309
+
310
+ /* Welcome Message */
311
+ .welcome-message {
312
+ text-align: center;
313
+ padding: 50px 30px;
314
+ color: #4a5568;
315
+ }
316
+
317
+ .welcome-message h2 {
318
+ color: #667eea;
319
+ margin-bottom: 18px;
320
+ font-size: 1.8em;
321
+ font-weight: 700;
322
+ }
323
+
324
+ .welcome-message p {
325
+ font-size: 1.1em;
326
+ color: #718096;
327
+ margin-bottom: 10px;
328
+ }
329
+
330
+ .example-questions {
331
+ margin-top: 28px;
332
+ display: flex;
333
+ flex-wrap: wrap;
334
+ gap: 12px;
335
+ justify-content: center;
336
+ }
337
+
338
+ .example-btn {
339
+ padding: 12px 24px;
340
+ background: white;
341
+ border: 2px solid #667eea;
342
+ color: #667eea;
343
+ border-radius: 25px;
344
+ cursor: pointer;
345
+ transition: all 0.3s;
346
+ font-size: 0.95em;
347
+ font-weight: 600;
348
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
349
+ }
350
+
351
+ .example-btn:hover {
352
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
353
+ color: white;
354
+ transform: translateY(-3px);
355
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
356
+ }
357
+
358
+ .example-btn:active {
359
+ transform: translateY(-1px);
360
+ }
361
+
362
+ /* Responsive Design */
363
+ @media (max-width: 768px) {
364
+ .container {
365
+ height: 100vh;
366
+ border-radius: 0;
367
+ max-width: 100%;
368
+ }
369
+
370
+ .header h1 {
371
+ font-size: 1.6em;
372
+ }
373
+
374
+ .header p {
375
+ font-size: 1em;
376
+ }
377
+
378
+ .chat-container {
379
+ height: calc(100vh - 280px);
380
+ padding: 20px;
381
+ }
382
+
383
+ .message-content {
384
+ max-width: 85%;
385
+ padding: 14px 18px;
386
+ }
387
+
388
+ .input-container {
389
+ padding: 18px 20px;
390
+ }
391
+
392
+ #messageInput {
393
+ padding: 14px 20px;
394
+ font-size: 1em;
395
+ }
396
+
397
+ #sendButton {
398
+ padding: 14px 28px;
399
+ font-size: 1em;
400
+ }
401
+
402
+ .example-questions {
403
+ flex-direction: column;
404
+ }
405
+
406
+ .example-btn {
407
+ width: 100%;
408
+ }
409
+ }
410
+
411
+ @media (max-width: 480px) {
412
+ .header {
413
+ padding: 25px 20px;
414
+ }
415
+
416
+ .header h1 {
417
+ font-size: 1.4em;
418
+ }
419
+
420
+ .welcome-message h2 {
421
+ font-size: 1.4em;
422
+ }
423
+ }
424
+
425
+ /* Dark Mode Support (Optional) */
426
+ @media (prefers-color-scheme: dark) {
427
+ body {
428
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
429
+ }
430
+ }
templates/index.html ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>TDK Sözlük Asistanı - RAG Chatbot</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ max-width: 900px;
26
+ width: 100%;
27
+ background: white;
28
+ border-radius: 20px;
29
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
30
+ overflow: hidden;
31
+ }
32
+
33
+ .header {
34
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
35
+ color: white;
36
+ padding: 30px;
37
+ text-align: center;
38
+ }
39
+
40
+ .header h1 {
41
+ font-size: 2em;
42
+ margin-bottom: 10px;
43
+ }
44
+
45
+ .header p {
46
+ opacity: 0.9;
47
+ font-size: 1.1em;
48
+ }
49
+
50
+ .chat-container {
51
+ height: 500px;
52
+ overflow-y: auto;
53
+ padding: 30px;
54
+ background: #f8f9fa;
55
+ }
56
+
57
+ .message {
58
+ margin-bottom: 20px;
59
+ display: flex;
60
+ gap: 10px;
61
+ animation: fadeIn 0.3s ease-in;
62
+ }
63
+
64
+ @keyframes fadeIn {
65
+ from { opacity: 0; transform: translateY(10px); }
66
+ to { opacity: 1; transform: translateY(0); }
67
+ }
68
+
69
+ .message.user {
70
+ justify-content: flex-end;
71
+ }
72
+
73
+ .message-content {
74
+ max-width: 70%;
75
+ padding: 15px 20px;
76
+ border-radius: 15px;
77
+ word-wrap: break-word;
78
+ }
79
+
80
+ .user .message-content {
81
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
82
+ color: white;
83
+ border-bottom-right-radius: 5px;
84
+ }
85
+
86
+ .bot .message-content {
87
+ background: white;
88
+ color: #333;
89
+ border: 1px solid #e0e0e0;
90
+ border-bottom-left-radius: 5px;
91
+ }
92
+
93
+ .sources {
94
+ margin-top: 15px;
95
+ padding: 15px;
96
+ background: #f0f0f0;
97
+ border-radius: 10px;
98
+ font-size: 0.9em;
99
+ }
100
+
101
+ .sources h4 {
102
+ margin-bottom: 10px;
103
+ color: #667eea;
104
+ }
105
+
106
+ .source-item {
107
+ margin-bottom: 8px;
108
+ padding: 8px;
109
+ background: white;
110
+ border-radius: 5px;
111
+ }
112
+
113
+ .source-item strong {
114
+ color: #764ba2;
115
+ }
116
+
117
+ .input-container {
118
+ padding: 20px 30px;
119
+ background: white;
120
+ border-top: 1px solid #e0e0e0;
121
+ }
122
+
123
+ .input-form {
124
+ display: flex;
125
+ gap: 10px;
126
+ }
127
+
128
+ #messageInput {
129
+ flex: 1;
130
+ padding: 15px 20px;
131
+ border: 2px solid #e0e0e0;
132
+ border-radius: 25px;
133
+ font-size: 1em;
134
+ outline: none;
135
+ transition: border-color 0.3s;
136
+ }
137
+
138
+ #messageInput:focus {
139
+ border-color: #667eea;
140
+ }
141
+
142
+ #sendButton {
143
+ padding: 15px 35px;
144
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
145
+ color: white;
146
+ border: none;
147
+ border-radius: 25px;
148
+ font-size: 1em;
149
+ cursor: pointer;
150
+ transition: transform 0.2s, box-shadow 0.2s;
151
+ }
152
+
153
+ #sendButton:hover {
154
+ transform: translateY(-2px);
155
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
156
+ }
157
+
158
+ #sendButton:disabled {
159
+ opacity: 0.6;
160
+ cursor: not-allowed;
161
+ transform: none;
162
+ }
163
+
164
+ .loading {
165
+ display: none;
166
+ text-align: center;
167
+ padding: 10px;
168
+ }
169
+
170
+ .loading.active {
171
+ display: block;
172
+ }
173
+
174
+ .loading-dots {
175
+ display: inline-block;
176
+ }
177
+
178
+ .loading-dots span {
179
+ display: inline-block;
180
+ width: 8px;
181
+ height: 8px;
182
+ background: #667eea;
183
+ border-radius: 50%;
184
+ margin: 0 3px;
185
+ animation: bounce 1.4s infinite ease-in-out both;
186
+ }
187
+
188
+ .loading-dots span:nth-child(1) {
189
+ animation-delay: -0.32s;
190
+ }
191
+
192
+ .loading-dots span:nth-child(2) {
193
+ animation-delay: -0.16s;
194
+ }
195
+
196
+ @keyframes bounce {
197
+ 0%, 80%, 100% {
198
+ transform: scale(0);
199
+ }
200
+ 40% {
201
+ transform: scale(1);
202
+ }
203
+ }
204
+
205
+ .welcome-message {
206
+ text-align: center;
207
+ padding: 40px 20px;
208
+ color: #666;
209
+ }
210
+
211
+ .welcome-message h2 {
212
+ color: #667eea;
213
+ margin-bottom: 15px;
214
+ }
215
+
216
+ .example-questions {
217
+ margin-top: 20px;
218
+ display: flex;
219
+ flex-wrap: wrap;
220
+ gap: 10px;
221
+ justify-content: center;
222
+ }
223
+
224
+ .example-btn {
225
+ padding: 10px 20px;
226
+ background: white;
227
+ border: 2px solid #667eea;
228
+ color: #667eea;
229
+ border-radius: 20px;
230
+ cursor: pointer;
231
+ transition: all 0.3s;
232
+ }
233
+
234
+ .example-btn:hover {
235
+ background: #667eea;
236
+ color: white;
237
+ }
238
+
239
+ @media (max-width: 768px) {
240
+ .container {
241
+ height: 100vh;
242
+ border-radius: 0;
243
+ }
244
+
245
+ .chat-container {
246
+ height: calc(100vh - 250px);
247
+ }
248
+
249
+ .message-content {
250
+ max-width: 85%;
251
+ }
252
+ }
253
+ </style>
254
+ </head>
255
+ <body>
256
+ <div class="container">
257
+ <div class="header">
258
+ <h1>📚 TDK Sözlük Asistanı</h1>
259
+ <p>Türkçe kelimelerin anlamlarını öğrenin</p>
260
+ </div>
261
+
262
+ <div class="chat-container" id="chatContainer">
263
+ <div class="welcome-message">
264
+ <h2>Merhaba! 👋</h2>
265
+ <p>Ben TDK Sözlük asistanınızım. Türkçe kelimeler hakkında size yardımcı olabilirim.</p>
266
+ <div class="example-questions">
267
+ <button class="example-btn" onclick="sendExample('kitap ne demek?')">📖 kitap ne demek?</button>
268
+ <button class="example-btn" onclick="sendExample('sevgi kelimesinin anlamı')">❤️ sevgi nedir?</button>
269
+ <button class="example-btn" onclick="sendExample('bilgisayar kelimesini açıklar mısın?')">💻 bilgisayar</button>
270
+ </div>
271
+ </div>
272
+ </div>
273
+
274
+ <div class="loading" id="loading">
275
+ <div class="loading-dots">
276
+ <span></span>
277
+ <span></span>
278
+ <span></span>
279
+ </div>
280
+ </div>
281
+
282
+ <div class="input-container">
283
+ <form class="input-form" id="chatForm">
284
+ <input
285
+ type="text"
286
+ id="messageInput"
287
+ placeholder="Bir kelime veya soru yazın..."
288
+ autocomplete="off"
289
+ required
290
+ >
291
+ <button type="submit" id="sendButton">Gönder</button>
292
+ </form>
293
+ </div>
294
+ </div>
295
+
296
+ <script>
297
+ const chatContainer = document.getElementById('chatContainer');
298
+ const chatForm = document.getElementById('chatForm');
299
+ const messageInput = document.getElementById('messageInput');
300
+ const sendButton = document.getElementById('sendButton');
301
+ const loading = document.getElementById('loading');
302
+
303
+ // Otomatik scroll
304
+ function scrollToBottom() {
305
+ chatContainer.scrollTop = chatContainer.scrollHeight;
306
+ }
307
+
308
+ // Kullanıcı mesajı ekle
309
+ function addUserMessage(message) {
310
+ const messageDiv = document.createElement('div');
311
+ messageDiv.className = 'message user';
312
+ messageDiv.innerHTML = `
313
+ <div class="message-content">${escapeHtml(message)}</div>
314
+ `;
315
+ chatContainer.appendChild(messageDiv);
316
+ scrollToBottom();
317
+ }
318
+
319
+ // Bot mesajı ekle
320
+ function addBotMessage(response, sources) {
321
+ const messageDiv = document.createElement('div');
322
+ messageDiv.className = 'message bot';
323
+
324
+ // Markdown'ı HTML'e çevir (basit)
325
+ let formattedResponse = response
326
+ .replace(/\*\*([^\*]+)\*\*/g, '<strong>$1</strong>') // **kalın** -> <strong>
327
+ .replace(/\*([^\*]+)\*/g, '<em>$1</em>') // *italik* -> <em>
328
+ .replace(/\n/g, '<br>') // Satır atlamaları
329
+ .replace(/- /g, '• '); // Liste maddeleri
330
+
331
+ let html = `<div class="message-content">${formattedResponse}`;
332
+
333
+ if (sources && sources.length > 0) {
334
+ html += '<div class="sources"><h4>📚 Kaynak Kelimeler:</h4>';
335
+ sources.forEach((source, index) => {
336
+ html += `
337
+ <div class="source-item">
338
+ <strong>${index + 1}. ${escapeHtml(source.kelime)}</strong><br>
339
+ ${escapeHtml(source.anlam)}
340
+ </div>
341
+ `;
342
+ });
343
+ html += '</div>';
344
+ }
345
+
346
+ html += '</div>';
347
+ messageDiv.innerHTML = html;
348
+ chatContainer.appendChild(messageDiv);
349
+ scrollToBottom();
350
+ }
351
+
352
+ // HTML escape
353
+ function escapeHtml(text) {
354
+ const div = document.createElement('div');
355
+ div.textContent = text;
356
+ return div.innerHTML;
357
+ }
358
+
359
+ // Welcome message'ı kaldır
360
+ function removeWelcome() {
361
+ const welcome = chatContainer.querySelector('.welcome-message');
362
+ if (welcome) {
363
+ welcome.remove();
364
+ }
365
+ }
366
+
367
+ // Form submit
368
+ chatForm.addEventListener('submit', async (e) => {
369
+ e.preventDefault();
370
+
371
+ const message = messageInput.value.trim();
372
+ if (!message) return;
373
+
374
+ // Welcome message'ı kaldır
375
+ removeWelcome();
376
+
377
+ // Kullanıcı mesajını ekle
378
+ addUserMessage(message);
379
+ messageInput.value = '';
380
+
381
+ // Loading göster
382
+ loading.classList.add('active');
383
+ sendButton.disabled = true;
384
+
385
+ try {
386
+ // API'ye istek gönder
387
+ const response = await fetch('/chat', {
388
+ method: 'POST',
389
+ headers: {
390
+ 'Content-Type': 'application/json'
391
+ },
392
+ body: JSON.stringify({
393
+ message: message,
394
+ top_k: 5
395
+ })
396
+ });
397
+
398
+ const data = await response.json();
399
+
400
+ if (response.ok) {
401
+ addBotMessage(data.response, data.sources);
402
+ } else {
403
+ addBotMessage(`❌ Hata: ${data.error}`, []);
404
+ }
405
+
406
+ } catch (error) {
407
+ addBotMessage('❌ Bağlantı hatası. Lütfen tekrar deneyin.', []);
408
+ console.error('Error:', error);
409
+ } finally {
410
+ loading.classList.remove('active');
411
+ sendButton.disabled = false;
412
+ messageInput.focus();
413
+ }
414
+ });
415
+
416
+ // Örnek soru gönder
417
+ function sendExample(text) {
418
+ messageInput.value = text;
419
+ chatForm.dispatchEvent(new Event('submit'));
420
+ }
421
+
422
+ // Enter tuşu ile gönder
423
+ messageInput.addEventListener('keypress', (e) => {
424
+ if (e.key === 'Enter' && !e.shiftKey) {
425
+ e.preventDefault();
426
+ chatForm.dispatchEvent(new Event('submit'));
427
+ }
428
+ });
429
+
430
+ // Sayfa yüklendiğinde input'a focus
431
+ messageInput.focus();
432
+ </script>
433
+ </body>
434
+ </html>