-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
547 lines (465 loc) · 20.3 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
import os
from flask import Flask, render_template, jsonify, request, redirect, url_for, session
import google.generativeai as genai
from markdown import markdown
import bleach
from datetime import datetime, timedelta
import json
from typing import Dict, List, Optional
import firebase_admin
from firebase_admin import credentials, auth
from dotenv import load_dotenv
from firebase_admin import firestore
load_dotenv()
app = Flask(__name__)
app.secret_key = 'GüvenliBirGizliAnahtar' # Bunu güvenli bir şekilde ayarlayın
API_KEY = os.getenv('API_KEY')
SERVICE_ACCOUNT_KEY_PATH = os.getenv('SERVICE_ACCOUNT_KEY_PATH')
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
# Firebase Admin SDK'yı başlatma
cred = credentials.Certificate(SERVICE_ACCOUNT_KEY_PATH)
firebase_admin.initialize_app(cred)
# Google Generative AI yapılandırması
genai.configure(api_key=GEMINI_API_KEY)
model = genai.GenerativeModel('gemini-pro')
# Firestore istemcisini başlat
db = firestore.client()
TOPIC_TREE = {
"Python": {
"Değişkenler ve Veri Tipleri": [
"Değişken tanımlama ve kullanımı",
"Temel veri tipleri (int, float, str, bool)",
"Tip dönüşümleri ve veri tipi kontrolü"
],
"Operatörler": [
"Aritmetik operatörler",
"Karşılaştırma operatörleri",
"Mantıksal ve bit düzeyinde operatörler"
],
"Koşullu İfadeler": [
"if, elif, else kullanımı",
"Karşılaştırma ve mantıksal işlemlerle koşullar oluşturma"
],
"Döngüler": [
"for ve while döngüleri",
"break ve continue ifadeleri",
"Liste ve sözlük gibi veri yapılarını döngülerle işleme"
],
"Fonksiyonlar": [
"Fonksiyon tanımlama ve çağırma",
"Parametreler ve dönüş değerleri",
"Lambda fonksiyonları"
],
"Modüller": [
"Hazır modülleri kullanma (math, random, datetime vb.)",
"Kendi modüllerinizi oluşturma",
"Python’da paket yönetimi"
]
},
"Nesne Yönelimli Programlama (OOP)": {
"Sınıflar ve Nesneler": [
"Nesne yönelimli programlama temelleri",
"Sınıf ve nesne oluşturma",
"Yapıcı (constructor) ve yıkıcı (destructor) metodlar"
],
"Hata Yönetimi": [
"try, except, finally kullanımı",
"Özel hata sınıfları tanımlama",
"Hata mesajlarını yakalama ve yönetme"
],
"Dosya İşlemleri": [
"Dosya açma, okuma, yazma ve kapama",
"Dosya ve klasör işlemleri (os modülü ile)",
"JSON ve CSV gibi veri formatları ile çalışma"
],
"Dekoratörler": [
"Fonksiyonlara özellik ekleme",
"@decorator işaretleyicisi ile dekoratör oluşturma",
"Sınıf ve metotlarda dekoratör kullanımı"
],
"Jeneratörler": [
"Generatör fonksiyonları ve yield kullanımı",
"Generatörlerle bellekte verimli veri işlemleri",
"List comprehensions ve generator expressions"
]
},
"Veri Yapıları": {
"Diziler": [
"Dizilerin tanımı ve temel özellikleri",
"Python’da liste işlemleri",
"Liste metodları (append, pop, insert, vb.)"
],
"Bağlı Listeler": [
"Tekli ve çift bağlı liste yapısı",
"Bağlı listelerde veri ekleme, silme, arama işlemleri",
"Bağlı liste örnek uygulamaları"
],
"Yığınlar (Stacks)": [
"LIFO prensibi ile çalışan yığın veri yapısı",
"push, pop, peek işlemleri",
"Yığın uygulamaları (geri al butonu, parantez dengeleme vb.)"
],
"Kuyruklar (Queues)": [
"FIFO prensibi ile çalışan kuyruk veri yapısı",
"enqueue ve dequeue işlemleri",
"Kuyruk türleri (dairesel kuyruk, çift uçlu kuyruk)"
]
},
"Gelişmiş Veri Yapıları ve Algoritmalar": {
"Ağaçlar": [
"İkili ağaçlar (Binary Trees)",
"Ağacın öncelikli, ardışık ve postorder dolaşımları",
"AVL ve B-Tree gibi dengeli ağaç yapıları"
],
"Graf Yapıları": [
"Graf veri yapısının tanımı ve türleri",
"Breadth-First Search (BFS) ve Depth-First Search (DFS) algoritmaları",
"Ağırlıklı graf ve kısa yol algoritmaları (Dijkstra, Bellman-Ford vb.)"
],
"Hash Tabloları": [
"Hash fonksiyonu ve çarpışma çözümleri",
"Hash tablolar ile veri ekleme ve silme işlemleri",
"Hashing’in veri erişimi üzerindeki etkisi"
],
"Heap Yapıları": [
"Min ve Max Heap yapıları",
"Heap sıralama algoritması (Heap Sort)",
"Öncelik kuyruğu uygulamaları"
]
}
}
def create_chat_session(user_id, first_message):
"""Her yeni sohbet için Firestore'da yeni bir sohbet oturumu oluşturur ve oturum kimliğini döndürür."""
# İlk üç kelimeyi alarak session_name oluşturuyoruz
session_name = " ".join(first_message.split()[:3]) + "..."
session_id = f"{user_id}_{int(datetime.now().timestamp())}"
session_data = {
'session_name': session_name,
'created_at': datetime.now()
}
db.collection('users').document(user_id).collection('chat_sessions').document(session_id).set(session_data)
return session_id
def add_message_to_session(user_id, session_id, content, role):
"""Firestore'da belirli bir oturuma mesaj ekler."""
message_id = f"{int(datetime.now().timestamp() * 1000)}" # Benzersiz message ID
message_ref = db.collection('users').document(user_id).collection('chat_sessions').document(session_id).collection('messages').document(message_id)
message_data = {
'content': content,
'role': role,
'timestamp': datetime.now()
}
message_ref.set(message_data)
class TopicContext:
"""Her konu için öğrenme yolunu ve içeriği tanımlar."""
def __init__(self, topic_name: str):
self.topic_name = topic_name
self.context = self._get_topic_context()
def _get_topic_context(self) -> Dict:
"""Konuya özel içerik ve öğrenme yolunu döndürür."""
contexts = {
"Değişkenler ve Veri Tipleri": {
"steps": [
"Değişken kavramı ve önemi",
"Python'da temel veri tipleri",
"Değişken tanımlama kuralları",
"Tip dönüşümleri",
"Karmaşık veri tipleri"
],
"prerequisites": [],
"examples": {
"beginner": [
"yaş = 25",
"isim = 'Ahmet'",
"boy = 1.78"
],
"intermediate": [
"x, y = 10, 20",
"liste = [1, 'abc', True]",
"sayilar = list(range(10))"
],
"advanced": [
"from dataclasses import dataclass\n@dataclass\nclass Nokta:\n x: float\n y: float",
"karmaşık_sayi = 3 + 4j",
"from typing import List, Dict\ndef isleme(items: List[Dict]) -> None: ..."
]
}
},
"Operatörler": {
"steps": [
"Aritmetik operatörler",
"Karşılaştırma operatörleri",
"Mantıksal operatörler",
"Bitwise operatörler",
"Özel operatörler"
],
"prerequisites": ["Değişkenler ve Veri Tipleri"],
"examples": {
"beginner": [
"toplam = 5 + 3",
"sonuc = 10 > 5",
"kontrol = True and False"
],
"intermediate": [
"x = 5; x += 3",
"sayilar = [1, 2, 3]; 2 in sayilar",
"bitwise_and = 5 & 3"
],
"advanced": [
"matris @ vektor # Matris çarpımı",
"nesne.__add__(self, diğer)",
"async with baglam_yonetici:"
]
}
}
# Diğer konular...
}
return contexts.get(self.topic_name, {})
def get_step_content(self, step_number: int, level: str) -> Dict:
"""Belirli bir adım için içerik ve örnekleri döndürür."""
steps = self.context.get("steps", [])
examples = self.context.get("examples", {}).get(level, [])
if 0 <= step_number < len(steps):
return {
"step_name": steps[step_number],
"examples": examples[step_number] if step_number < len(examples) else None
}
return {}
class EnhancedTutorSession:
"""Gelişmiş öğretmen asistanı oturumu."""
def __init__(self, api_key: str):
genai.configure(api_key=api_key)
self.model = genai.GenerativeModel('gemini-pro')
self.history: List[Dict] = []
self.current_topic: Optional[str] = None
self.student_level = "beginner"
self.current_step = 0
self.understanding_scores: Dict[str, List[float]] = {}
def analyze_student_level(self, message: str) -> None:
"""Öğrenci mesajını analiz ederek seviyesini belirler."""
# Seviye belirleme kriterleri
level_indicators = {
"advanced": [
"metaclass", "decorator", "generator", "async",
"threading", "multiprocessing", "optimization",
"memory management", "design pattern"
],
"intermediate": [
"class", "inheritance", "exception", "file handling",
"list comprehension", "lambda", "recursion",
"module", "package"
]
}
message_lower = message.lower()
# Gelişmiş kalıpları kontrol et
if any(indicator in message_lower for indicator in level_indicators["advanced"]):
self.student_level = "advanced"
# Orta seviye kalıpları kontrol et
elif any(indicator in message_lower for indicator in level_indicators["intermediate"]):
self.student_level = "intermediate"
def generate_prompt(self, message: str, topic_context: TopicContext) -> str:
"""Öğretmen yanıtı için prompt oluşturur."""
current_step_content = topic_context.get_step_content(self.current_step, self.student_level)
# Son 3 mesajı al
recent_history = "\n".join([
f"{msg['role']}: {msg['content']}"
for msg in self.history[-3:]
])
return f"""
Sen profesyonel ve arkadaşça bir Python eğitmenisin. '{self.current_topic}' konusunu öğretiyorsun.
ÖĞRENME BAĞLAMI:
- Öğrenci Seviyesi: {self.student_level}
- Mevcut Adım: {current_step_content.get('step_name', 'Giriş')}
- Konu İlerlemesi: {self.current_step + 1}/{len(topic_context.context.get('steps', []))}
SON KONUŞMALAR:
{recent_history}
YENİ MESAJ: {message}
YANITLAMA PRENSİPLERİ:
1. Samimi ve destekleyici ol
2. Konuyu gerçek dünya örnekleriyle açıkla
3. Her yeni kavramı kod örnekleriyle destekle
4. Öğrencinin seviyesine uygun detayda açıklama yap
5. Her açıklamadan sonra anlama kontrolü için soru sor
6. Başarıları takdir et ve motive et
7. Hataları nazikçe düzelt
YANITLAMA FORMATI:
- Ana kavramları **kalın** yap
- Kod örneklerini ```python içinde göster
- Önemli noktaları maddeler halinde listele
- Sorularını '>' işaretiyle belirt
{current_step_content.get('examples', '')}
"""
def evaluate_understanding(self, message: str) -> float:
"""Öğrencinin anlama düzeyini değerlendirir."""
# Basit bir değerlendirme metriği
understanding_indicators = {
"positive": ["anladım", "tamam", "evet", "mantıklı", "doğru"],
"negative": ["anlamadım", "tekrar", "karışık", "zor", "anlaşılmadı"],
"questioning": ["neden", "nasıl", "niçin", "?"]
}
message_lower = message.lower()
# Pozitif ve negatif göstergeleri say
positive_count = sum(1 for word in understanding_indicators["positive"] if word in message_lower)
negative_count = sum(1 for word in understanding_indicators["negative"] if word in message_lower)
question_count = sum(1 for word in understanding_indicators["questioning"] if word in message_lower)
# Anlama skorunu hesapla (-1 ile 1 arasında)
base_score = (positive_count - negative_count) / (positive_count + negative_count + 1)
question_penalty = -0.2 * (question_count / (len(message_lower.split()) + 1))
return max(-1, min(1, base_score + question_penalty))
def add_message(self, role: str, content: str) -> None:
"""Mesaj geçmişine yeni mesaj ekler."""
self.history.append({
'role': role,
'content': content,
'timestamp': datetime.now().isoformat(),
'topic': self.current_topic,
'step': self.current_step,
'level': self.student_level
})
# Öğrenci mesajlarını analiz et
if role == 'user':
self.analyze_student_level(content)
understanding_score = self.evaluate_understanding(content)
if self.current_topic:
if self.current_topic not in self.understanding_scores:
self.understanding_scores[self.current_topic] = []
self.understanding_scores[self.current_topic].append(understanding_score)
def should_advance_step(self) -> bool:
"""Bir sonraki adıma geçilip geçilmeyeceğini belirler."""
if not self.current_topic in self.understanding_scores:
return False
recent_scores = self.understanding_scores[self.current_topic][-3:] # Son 3 skor
if len(recent_scores) < 3:
return False
# Ortalama skor 0.6'dan büyükse ilerle
return sum(recent_scores) / len(recent_scores) > 0.6
def get_response(self, message: str) -> str:
"""Öğretmen yanıtını oluşturur."""
if not self.current_topic:
return "Lütfen önce bir konu seçin."
topic_context = TopicContext(self.current_topic)
prompt = self.generate_prompt(message, topic_context)
try:
response = self.model.generate_content(prompt)
# Yanıt başarılıysa ve anlama yeterliyse adımı ilerlet
if self.should_advance_step():
self.current_step += 1
self.understanding_scores[self.current_topic] = []
return response.text
except Exception as e:
return f"Üzgünüm, bir hata oluştu: {str(e)}"
# Global oturum sözlüğü
sessions: Dict[str, EnhancedTutorSession] = {}
# Flask rotaları
@app.route('/login')
def login():
return render_template('login.html', API_KEY=API_KEY)
@app.route('/api/chat', methods=['POST'])
def chat():
if 'user' not in session:
return jsonify({'status': 'error', 'message': 'Yetkisiz erişim'}), 401
data = request.json
message = data.get('message')
topic = data.get('topic')
user_id = session['user']
# Oturum zaten yoksa ilk mesajı kullanarak bir oturum adı oluştur
session_id = data.get('session_id') or create_chat_session(user_id, message)
if session_id not in sessions:
sessions[session_id] = EnhancedTutorSession(GEMINI_API_KEY)
session_obj = sessions[session_id]
session_obj.current_topic = topic
session_obj.add_message('user', message)
# Mesajı Firestore'a kaydet
add_message_to_session(user_id, session_id, message, "user")
response = session_obj.get_response(message)
session_obj.add_message('assistant', response)
# Asistan yanıtını Firestore'a kaydet
add_message_to_session(user_id, session_id, response, "assistant")
# Markdown'ı HTML'e çevir ve temizle
html_response = bleach.clean(
markdown(response, extensions=['fenced_code', 'tables']),
tags=['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code', 'pre',
'strong', 'em', 'ul', 'ol', 'li', 'table', 'thead', 'tbody',
'tr', 'th', 'td', 'blockquote', 'br'],
attributes={'code': ['class'], 'pre': ['class']}
)
return jsonify({
'response': response,
'html_response': html_response,
'student_level': session_obj.student_level,
'current_step': session_obj.current_step,
'topic': session_obj.current_topic,
'understanding_score': session_obj.understanding_scores.get(session_obj.current_topic, [0])[-1]
})
@app.route('/')
def home():
if 'user' in session:
return render_template('index.html', topic_tree=TOPIC_TREE)
else:
return redirect(url_for('login'))
@app.route('/sessionLogin', methods=['POST'])
def session_login():
id_token = request.json.get('idToken')
try:
# Token'ı doğrula
decoded_token = auth.verify_id_token(id_token)
uid = decoded_token['uid']
# Oturumu başlat
session['user'] = uid
return jsonify({'status': 'success'}), 200
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 401
@app.route('/logout')
def logout():
session.pop('user', None)
return redirect(url_for('login'))
@app.route('/api/get_chat_sessions', methods=['GET'])
def get_chat_sessions():
user_id = session.get('user')
if not user_id:
return jsonify({'status': 'error', 'message': 'Yetkisiz erişim'}), 401
chat_sessions = []
sessions_ref = db.collection('users').document(user_id).collection('chat_sessions').order_by("created_at")
for session_doc in sessions_ref.stream():
session_data = session_doc.to_dict()
session_data['session_id'] = session_doc.id
chat_sessions.append(session_data)
return jsonify(chat_sessions)
@app.route('/api/get_chat_messages', methods=['GET'])
def get_chat_messages():
try:
user_id = session.get('user')
if not user_id:
return jsonify({'status': 'error', 'message': 'Yetkisiz erişim'}), 401
session_id = request.args.get('session_id')
if not session_id:
return jsonify({'status': 'error', 'message': 'Geçersiz oturum ID'}), 400
messages = []
messages_ref = db.collection('users').document(user_id).collection('chat_sessions').document(session_id).collection('messages')
docs = messages_ref.order_by('timestamp').stream()
for doc in docs:
message = doc.to_dict()
# Her mesajı HTML'e dönüştür ve temizle
formatted_content = bleach.clean(
markdown(message['content'], extensions=['fenced_code', 'tables']),
tags=['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code', 'pre',
'strong', 'em', 'ul', 'ol', 'li', 'table', 'thead', 'tbody',
'tr', 'th', 'td', 'blockquote', 'br'],
attributes={'code': ['class'], 'pre': ['class']}
)
message['formatted_content'] = formatted_content
messages.append(message)
return jsonify(messages)
except Exception as e:
print(f"get_chat_messages hata: {e}") # Sunucu konsolunda hatayı görmek için
return jsonify({'status': 'error', 'message': 'Sunucu hatası'}), 500
@app.route('/api/new_chat_session', methods=['POST'])
def new_chat_session():
if 'user' not in session:
return jsonify({'status': 'error', 'message': 'Yetkisiz erişim'}), 401
data = request.json
session_name = data.get('session_name', 'Yeni Chat Oturumu')
user_id = session['user']
session_id = create_chat_session(user_id, session_name)
return jsonify({'session_id': session_id, 'session_name': session_name})
if __name__ == '__main__':
app.run(debug=True)