GuestUser33 commited on
Commit
7ccbf3d
·
verified ·
1 Parent(s): 867a432

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +497 -238
app.py CHANGED
@@ -32,6 +32,7 @@ class LearningSession:
32
  end_time: Optional[datetime] = None
33
  words_learned: int = 0
34
  idioms_learned: int = 0
 
35
  questions_asked: int = 0
36
 
37
  @dataclass
@@ -57,16 +58,17 @@ class PersonalizedLearningTracker:
57
  cursor = conn.cursor()
58
 
59
  cursor.execute('''
60
- CREATE TABLE IF NOT EXISTS learning_sessions (
61
- session_id TEXT PRIMARY KEY,
62
- user_id TEXT NOT NULL,
63
- start_time TEXT NOT NULL,
64
- end_time TEXT,
65
- words_learned INTEGER DEFAULT 0,
66
- idioms_learned INTEGER DEFAULT 0,
67
- questions_asked INTEGER DEFAULT 0
68
- )
69
- ''')
 
70
 
71
  cursor.execute('''
72
  CREATE TABLE IF NOT EXISTS word_progress (
@@ -214,12 +216,25 @@ class PersonalizedLearningTracker:
214
  WHERE user_id = ? AND word = ? AND category = ?
215
  ''', (now, user_id, word, category))
216
  else:
217
- cursor.execute('''
218
  INSERT INTO word_progress
219
  (user_id, word, definition, category, first_encountered, last_reviewed)
220
  VALUES (?, ?, ?, ?, ?, ?)
221
  ''', (user_id, word, definition, category, now, now))
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  conn.commit()
224
  conn.close()
225
 
@@ -327,6 +342,32 @@ class PersonalizedLearningTracker:
327
  conn.close()
328
  return words
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  def get_learning_recommendations(self, user_id: str) -> List[str]:
331
  """Get personalized learning recommendations"""
332
  progress = self.get_user_progress(user_id)
@@ -349,6 +390,7 @@ class PersonalizedLearningTracker:
349
 
350
  class PersonalizedKazakhAssistant:
351
  def __init__(self):
 
352
  self.setup_environment()
353
  self.setup_vectorstore()
354
  self.setup_llm()
@@ -358,19 +400,18 @@ class PersonalizedKazakhAssistant:
358
 
359
  def setup_environment(self):
360
  """Setup environment and configuration"""
361
-
362
  self.google_api_key = os.getenv("GOOGLE_API_KEY")
363
  self.MODEL = "gemini-1.5-flash"
364
  self.db_name = "vector_db"
365
 
366
  def setup_vectorstore(self):
367
  """Setup document loading and vector store"""
368
- folders = glob.glob("knowledge-base/*")
369
  text_loader_kwargs = {'encoding': 'utf-8'}
370
  documents = []
371
 
372
  for folder in folders:
373
- doc_type = os.path.basename(folder)
374
  loader = DirectoryLoader(
375
  folder,
376
  glob="**/*.txt",
@@ -382,6 +423,25 @@ class PersonalizedKazakhAssistant:
382
  doc.metadata["doc_type"] = doc_type
383
  documents.append(doc)
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  text_splitter = CharacterTextSplitter(separator=r'\n', chunk_size=2000, chunk_overlap=0)
386
  chunks = text_splitter.split_documents(documents)
387
 
@@ -394,122 +454,192 @@ class PersonalizedKazakhAssistant:
394
 
395
  self.vectorstore = Chroma.from_documents(documents=chunks, embedding=embeddings, persist_directory=self.db_name)
396
  print(f"Vectorstore created with {self.vectorstore._collection.count()} documents")
397
-
398
  def setup_llm(self):
399
  """Setup LLM with enhanced system prompt"""
400
  system_prompt = """
401
- You are a personalized Kazakh language learning assistant with access to a comprehensive knowledge base and user learning history. Your role is to help users learn Kazakh words and idioms while tracking their progress and providing personalized recommendations.
402
-
403
- Key capabilities:
404
- 1. **Answer Queries**: Provide accurate definitions and examples for Kazakh words and idioms from your knowledge base
405
- 2. **Track Learning Progress**: Identify and track when users learn new words or idioms
406
- 3. **Personalized Responses**: Adapt responses based on user's learning history and progress
407
- 4. **Progress Reporting**: Provide detailed progress reports when asked
408
- 5. **Learning Recommendations**: Suggest words/idioms to review or learn next
409
-
410
- Response Guidelines:
411
- - For word/idiom queries: Provide definition, usage examples, and related information
412
- - Always identify the main Kazakh word/idiom being discussed for progress tracking
413
- - Be encouraging and supportive of the user's learning journey
414
- - Use simple, clear explanations appropriate for language learners
415
- - When discussing progress, be specific and motivating
416
-
417
- Format responses naturally in conversational style, not JSON unless specifically requested.
418
- """
 
 
419
 
420
  self.llm = ChatGoogleGenerativeAI(
421
  model="models/gemini-1.5-flash",
422
  temperature=0.7,
423
- system_instruction=system_prompt
424
  )
425
 
426
-
427
- def get_user_memory(self, user_id: str):
428
- """Get or create conversation memory for a specific user"""
429
- if user_id not in self.user_memories:
430
- self.user_memories[user_id] = ConversationBufferMemory(
431
- memory_key='chat_history',
432
- return_messages=True,
433
- max_token_limit=10000
434
- )
435
- return self.user_memories[user_id]
436
-
437
- def get_user_chain(self, user_id: str):
438
- """Get or create conversation chain for a specific user"""
439
- memory = self.get_user_memory(user_id)
440
- retriever = self.vectorstore.as_retriever()
441
- return ConversationalRetrievalChain.from_llm(
442
- llm=self.llm,
443
- retriever=retriever,
444
- memory=memory
445
- )
446
 
447
  def extract_kazakh_terms(self, message: str, response: str) -> List[Tuple[str, str, str]]:
448
  """Extract meaningful Kazakh terms using document metadata to determine category"""
449
  terms = []
 
450
 
451
  try:
452
  retrieved_docs = self.vectorstore.similarity_search(message, k=5)
453
 
454
- kazakh_words = re.findall(r'[А-Яа-яӘәҒғҚқҢңӨөҰұҮүҺһІі]+(?:\s+[А-Яа-яӘәҒғҚқҢңӨөҰұҮүҺһІі]+)*', response)
 
455
 
456
- for word in kazakh_words:
457
- word = word.strip()
458
-
459
- if len(word) <= 2 or len(word) > 50:
460
- continue
461
-
462
- skip_words = ['деген', 'деп', 'берілген', 'мәтінде', 'мағынасы', 'дегеннің',
463
- 'түсіндірілген', 'келтірілген', 'болып', 'табылады', 'ауруы',
464
- 'мынадай', 'тақырыбына', 'тіркестер', 'арналған', 'байланысты']
465
-
466
- if any(skip in word.lower() for skip in skip_words):
467
- continue
468
-
469
- category = "word"
470
- definition = ""
471
 
472
- for doc in retrieved_docs:
473
- if word.lower() in doc.page_content.lower():
474
- doc_type = doc.metadata.get('doc_type', '').lower()
475
- if 'idiom' in doc_type or 'тіркес' in doc_type:
476
- category = "idiom"
477
- else:
478
- category = "word"
 
 
 
 
479
 
480
- definition = self.extract_clean_definition(word, doc.page_content, response)
481
- break
482
-
483
- if definition and len(word.split()) <= 4:
484
- if not any(phrase in word.lower() for phrase in ['қалай', 'қандай', 'қайда', 'неше', 'қашан']):
485
- terms.append((word, category, definition))
 
 
 
 
 
 
 
 
 
 
 
 
 
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  except Exception as e:
488
  print(f"Error extracting terms: {e}")
489
-
490
  return terms
491
 
492
  def extract_clean_definition(self, term: str, doc_content: str, response: str) -> str:
493
- """Extract clean definition for a term"""
 
 
494
  sentences = response.split('.')
495
  for sentence in sentences:
496
- if term.lower() in sentence.lower():
497
- clean_sentence = sentence.strip()
498
- if len(clean_sentence) > 10 and len(clean_sentence) < 150:
499
- if not any(word in clean_sentence.lower() for word in ['деген не', 'қалай аталады', 'нені білдіреді']):
500
- return clean_sentence
501
 
502
  doc_sentences = doc_content.split('.')
503
  for sentence in doc_sentences:
504
- if term.lower() in sentence.lower():
505
- clean_sentence = sentence.strip()
506
- if len(clean_sentence) > 10 and len(clean_sentence) < 150:
507
- return clean_sentence
508
 
509
  return f"Definition for {term}"
510
 
511
- def process_message(self, message: str, user_id: str = "default_user", session_token: str = None) -> str:
512
- """Process user message with proper user session management"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
 
514
  if session_token and not self.tracker.validate_session(user_id, session_token):
515
  return "Session expired. Please login again."
@@ -526,9 +656,14 @@ Format responses naturally in conversational style, not JSON unless specifically
526
  return self.get_recommendations(user_id)
527
  elif message.lower().startswith('/review'):
528
  return self.get_review_words(user_id)
 
 
529
  elif message.lower().startswith('/help'):
530
  return self.get_help_message()
531
 
 
 
 
532
  conversation_chain = self.get_user_chain(user_id)
533
  result = conversation_chain.invoke({"question": message})
534
  response = result["answer"]
@@ -607,6 +742,25 @@ Format responses naturally in conversational style, not JSON unless specifically
607
 
608
  return response
609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
  def get_help_message(self) -> str:
611
  """Get help message with available commands"""
612
  return """
@@ -616,6 +770,7 @@ Format responses naturally in conversational style, not JSON unless specifically
616
  - `/progress` - View your detailed learning progress
617
  - `/recommendations` - Get personalized learning suggestions
618
  - `/review` - See words that need review
 
619
  - `/help` - Show this help message
620
 
621
  **How to Use**:
@@ -636,22 +791,43 @@ Start learning by asking about any Kazakh term! 🌟
636
  """Create a session token for user authentication"""
637
  session_token = self.tracker.create_user_session(user_id)
638
  return session_token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
 
640
  assistant = PersonalizedKazakhAssistant()
641
 
642
-
643
- def chat_interface(message, history):
644
- """Chat interface for Gradio - uses consistent user for web interface"""
645
  try:
646
- # Use a consistent user_id for the web interface session
647
- # In a real app, you'd use proper session management
648
  web_user_id = "web_user_default" # Consistent ID
649
- response = assistant.process_message(message, web_user_id)
650
  return response
651
  except Exception as e:
652
  return f"Sorry, I encountered an error: {str(e)}. Please try again."
653
-
654
-
655
  def api_login(user_id: str) -> dict:
656
  """API endpoint for user login/session creation"""
657
  try:
@@ -668,10 +844,10 @@ def api_login(user_id: str) -> dict:
668
  "error": str(e)
669
  }
670
 
671
- def api_chat(message: str, user_id: str, session_token: str = None) -> dict:
672
- """API endpoint for chat functionality with proper user session"""
673
  try:
674
- response = assistant.process_message(message, user_id, session_token)
675
  return {
676
  "success": True,
677
  "response": response,
@@ -747,230 +923,313 @@ def api_review_words(user_id: str, session_token: str = None) -> dict:
747
  "error": str(e)
748
  }
749
 
750
- # Gradio Interface with API Testing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  with gr.Blocks(title="🇰🇿 Kazakh Learning API") as demo:
752
-
753
  gr.Markdown("# 🇰🇿 Personalized Kazakh Learning Assistant")
754
  gr.Markdown("### Multi-User Chat Interface + API Endpoints for Mobile Integration")
755
 
756
  with gr.Tab("💬 Chat Interface"):
 
 
 
 
 
 
 
 
757
  chat_interface = gr.ChatInterface(
758
- chat_interface,
 
759
  type="messages",
760
  examples=[
761
- "сәлем деген не?",
762
- "күләпара не үшін керек?",
763
- "/progress",
764
- "/recommendations",
765
- "/review"
 
 
 
766
  ]
767
  )
768
-
769
- with gr.Tab("🔌 API Testing"):
770
- gr.Markdown("## Test API Endpoints")
771
-
772
- with gr.Row():
773
- with gr.Column():
774
- user_id_input = gr.Textbox(label="User ID", value="test_user", placeholder="Enter unique user ID")
775
- session_token_input = gr.Textbox(label="Session Token", placeholder="Session token (get from login)")
776
- message_input = gr.Textbox(label="Message", placeholder="Enter your message in Kazakh or English")
777
-
778
- with gr.Row():
779
- login_btn = gr.Button("🔑 Test Login API")
780
- chat_btn = gr.Button("💬 Test Chat API")
781
- progress_btn = gr.Button("📊 Test Progress API")
782
- recommendations_btn = gr.Button("💡 Test Recommendations API")
783
- review_btn = gr.Button("📚 Test Review API")
784
-
785
- api_output = gr.JSON(label="API Response")
786
-
787
- login_btn.click(
788
- fn=lambda uid: api_login(uid),
789
- inputs=user_id_input,
790
- outputs=api_output
791
- )
792
-
793
- chat_btn.click(
794
- fn=lambda msg, uid, token: api_chat(msg, uid, token),
795
- inputs=[message_input, user_id_input, session_token_input],
796
- outputs=api_output
797
- )
798
-
799
- progress_btn.click(
800
- fn=lambda uid, token: api_progress(uid, token),
801
- inputs=[user_id_input, session_token_input],
802
- outputs=api_output
803
- )
804
-
805
- recommendations_btn.click(
806
- fn=lambda uid, token: api_recommendations(uid, token),
807
- inputs=[user_id_input, session_token_input],
808
- outputs=api_output
809
- )
810
-
811
- review_btn.click(
812
- fn=lambda uid, token: api_review_words(uid, token),
813
- inputs=[user_id_input, session_token_input],
814
- outputs=api_output
815
- )
816
-
817
  with gr.Tab("📖 API Documentation"):
818
  gr.Markdown("""
819
  ## API Endpoints for Flutter Integration
820
-
821
  ### Base URL: `https://huggingface.co/spaces/GuestUser33/kazakh-learning-api`
822
-
823
  ### Authentication Flow:
824
- 1. **Login** to get session token
825
  2. **Use session token** for subsequent API calls
826
  3. **Session tokens expire** after inactivity
827
-
828
  ### Available Endpoints:
829
-
830
  #### 1. Login API
831
  ```
832
  POST /api/predict
833
  Content-Type: application/json
834
-
835
  {
836
- "data": ["user_id"],
837
- "fn_index": 0
838
  }
839
  ```
840
- **Response**: `{"success": true, "session_token": "uuid", "user_id": "user_id"}`
841
-
842
  #### 2. Chat API
843
  ```
844
  POST /api/predict
845
  Content-Type: application/json
846
-
847
  {
848
- "data": ["message", "user_id", "session_token"],
849
- "fn_index": 1
850
  }
851
  ```
852
-
853
- #### 3. Progress API
 
 
 
 
 
 
 
 
854
  ```
855
  POST /api/predict
856
  Content-Type: application/json
857
-
858
  {
859
- "data": ["user_id", "session_token"],
860
- "fn_index": 2
861
  }
862
  ```
863
-
 
864
  #### 4. Recommendations API
865
  ```
866
  POST /api/predict
867
  Content-Type: application/json
868
-
869
  {
870
- "data": ["user_id", "session_token"],
871
- "fn_index": 3
872
  }
873
  ```
874
-
 
875
  #### 5. Review Words API
876
  ```
877
  POST /api/predict
878
  Content-Type: application/json
879
-
880
  {
881
- "data": ["user_id", "session_token"],
882
- "fn_index": 4
883
  }
884
  ```
885
-
 
 
 
 
 
 
 
 
 
 
 
 
 
886
  ### Flutter Integration Example:
887
  ```dart
 
 
 
888
  class KazakhLearningAPI {
889
- static const String baseUrl = 'https://huggingface.co/spaces/GuestUser33/kazakh-learning-api';
890
- String? sessionToken;
891
- String? userId;
892
-
893
- // Login and get session token
894
- Future<bool> login(String userId) async {
895
  final response = await http.post(
896
- Uri.parse('$baseUrl/api/predict'),
897
- headers: {'Content-Type': 'application/json'},
898
- body: jsonEncode({
899
  'data': [userId],
900
  'fn_index': 0
901
- }),
902
  );
903
-
904
  if (response.statusCode == 200) {
905
- final result = jsonDecode(response.body);
906
- if (result['data'][0]['success']) {
907
  this.userId = userId;
908
  this.sessionToken = result['data'][0]['session_token'];
909
  return true;
910
- }
911
  }
912
  return false;
913
- }
914
-
915
- // Send chat message
916
- Future<String?> sendMessage(String message) async {
 
 
 
 
917
  if (sessionToken == null) return null;
918
-
919
  final response = await http.post(
920
- Uri.parse('$baseUrl/api/predict'),
921
- headers: {'Content-Type': 'application/json'},
922
- body: jsonEncode({
923
- 'data': [message, userId, sessionToken],
924
  'fn_index': 1
925
- }),
926
  );
927
-
928
  if (response.statusCode == 200) {
929
- final result = jsonDecode(response.body);
930
- if (result['data'][0]['success']) {
931
  return result['data'][0]['response'];
932
- }
933
  }
934
  return null;
935
- }
936
-
937
- // Get user progress
938
- Future<Map<String, dynamic>?> getProgress() async {
939
  if (sessionToken == null) return null;
940
-
941
  final response = await http.post(
942
- Uri.parse('$baseUrl/api/predict'),
943
- headers: {'Content-Type': 'application/json'},
944
- body: jsonEncode({
945
  'data': [userId, sessionToken],
946
  'fn_index': 2
947
- }),
948
  );
949
-
950
  if (response.statusCode == 200) {
951
- final result = jsonDecode(response.body);
952
- if (result['data'][0]['success']) {
953
  return result['data'][0]['progress_data'];
954
- }
955
  }
956
  return null;
957
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
958
  }
959
  ```
960
-
961
  ### Key Features:
962
  - ✅ **Multi-User Support**: Each user has separate learning progress
963
  - ✅ **Session Management**: Secure session tokens for authentication
964
- - ✅ **Personalized Tracking**: Individual progress tracking per user
 
 
965
  - ✅ **API Ready**: All endpoints ready for mobile app integration
966
  - ✅ **Session Validation**: Automatic session validation and expiry
967
-
968
  ### Usage Notes:
969
  - Always call **login** first to get a session token
970
  - Include **session_token** in all subsequent API calls
 
 
971
  - Handle **session expiry** by re-logging in
972
- - Use **unique user_id** for each user (could be email, username, etc.)
973
- """)
 
974
 
975
  if __name__ == "__main__":
976
  demo.launch()
 
32
  end_time: Optional[datetime] = None
33
  words_learned: int = 0
34
  idioms_learned: int = 0
35
+ grammar_learned: int = 0
36
  questions_asked: int = 0
37
 
38
  @dataclass
 
58
  cursor = conn.cursor()
59
 
60
  cursor.execute('''
61
+ CREATE TABLE IF NOT EXISTS learning_sessions (
62
+ session_id TEXT PRIMARY KEY,
63
+ user_id TEXT NOT NULL,
64
+ start_time TEXT NOT NULL,
65
+ end_time TEXT,
66
+ words_learned INTEGER DEFAULT 0,
67
+ idioms_learned INTEGER DEFAULT 0,
68
+ grammar_learned INTEGER DEFAULT 0,
69
+ questions_asked INTEGER DEFAULT 0
70
+ )
71
+ ''')
72
 
73
  cursor.execute('''
74
  CREATE TABLE IF NOT EXISTS word_progress (
 
216
  WHERE user_id = ? AND word = ? AND category = ?
217
  ''', (now, user_id, word, category))
218
  else:
219
+ cursor.execute ('''
220
  INSERT INTO word_progress
221
  (user_id, word, definition, category, first_encountered, last_reviewed)
222
  VALUES (?, ?, ?, ?, ?, ?)
223
  ''', (user_id, word, definition, category, now, now))
224
 
225
+ cursor.execute('''
226
+ SELECT encounter_count FROM word_progress
227
+ WHERE user_id = ? AND word = ? AND category = ?
228
+ ''', (user_id, word, category))
229
+ encounter_count = cursor.fetchone()[0]
230
+
231
+ if encounter_count >= 3:
232
+ cursor.execute('''
233
+ UPDATE word_progress
234
+ SET mastery_level = ?
235
+ WHERE user_id = ? AND word = ? AND category = ?
236
+ ''', (3, user_id, word, category))
237
+
238
  conn.commit()
239
  conn.close()
240
 
 
342
  conn.close()
343
  return words
344
 
345
+ def get_mastered_words(self, user_id: str, limit: int = 10) -> List[Dict]:
346
+ """Get words with mastery level greater than 0"""
347
+ conn = sqlite3.connect(self.db_path)
348
+ cursor = conn.cursor()
349
+
350
+ cursor.execute('''
351
+ SELECT word, definition, category, mastery_level, encounter_count
352
+ FROM word_progress
353
+ WHERE user_id = ? AND mastery_level > 0
354
+ ORDER BY mastery_level DESC, encounter_count DESC
355
+ LIMIT ?
356
+ ''', (user_id, limit))
357
+
358
+ words = []
359
+ for word, definition, category, mastery, encounter_count in cursor.fetchall():
360
+ words.append({
361
+ 'word': word,
362
+ 'definition': definition,
363
+ 'category': category,
364
+ 'mastery_level': mastery,
365
+ 'encounter_count': encounter_count
366
+ })
367
+
368
+ conn.close()
369
+ return words
370
+
371
  def get_learning_recommendations(self, user_id: str) -> List[str]:
372
  """Get personalized learning recommendations"""
373
  progress = self.get_user_progress(user_id)
 
390
 
391
  class PersonalizedKazakhAssistant:
392
  def __init__(self):
393
+ self.known_terms = set()
394
  self.setup_environment()
395
  self.setup_vectorstore()
396
  self.setup_llm()
 
400
 
401
  def setup_environment(self):
402
  """Setup environment and configuration"""
 
403
  self.google_api_key = os.getenv("GOOGLE_API_KEY")
404
  self.MODEL = "gemini-1.5-flash"
405
  self.db_name = "vector_db"
406
 
407
  def setup_vectorstore(self):
408
  """Setup document loading and vector store"""
409
+ folders = glob.glob("knowledge-base/*")
410
  text_loader_kwargs = {'encoding': 'utf-8'}
411
  documents = []
412
 
413
  for folder in folders:
414
+ doc_type = os.path.basename(folder).lower()
415
  loader = DirectoryLoader(
416
  folder,
417
  glob="**/*.txt",
 
423
  doc.metadata["doc_type"] = doc_type
424
  documents.append(doc)
425
 
426
+ self.known_terms.clear()
427
+ common_words = {'бас', 'сөз', 'адам', 'жол', 'күн', 'су', 'жер', 'қол', 'тұр', 'бер'}
428
+ for doc in documents:
429
+ doc_type = doc.metadata.get('doc_type', '').lower()
430
+ lines = doc.page_content.replace('\r\n', '\n').replace('\r', '\n').split('\n')
431
+ for line in lines:
432
+ line = line.strip()
433
+ if line and " - " in line:
434
+ term = line.split(" - ")[0].strip().lower()
435
+
436
+ if term and (
437
+ doc_type in ['idioms', 'grammar'] or
438
+ (doc_type == 'words' and len(term.split()) > 1) or
439
+ term not in common_words
440
+ ):
441
+ self.known_terms.add(term)
442
+
443
+ print(f"Loaded {len(self.known_terms)} known terms: {list(self.known_terms)[:10]}")
444
+
445
  text_splitter = CharacterTextSplitter(separator=r'\n', chunk_size=2000, chunk_overlap=0)
446
  chunks = text_splitter.split_documents(documents)
447
 
 
454
 
455
  self.vectorstore = Chroma.from_documents(documents=chunks, embedding=embeddings, persist_directory=self.db_name)
456
  print(f"Vectorstore created with {self.vectorstore._collection.count()} documents")
457
+
458
  def setup_llm(self):
459
  """Setup LLM with enhanced system prompt"""
460
  system_prompt = """
461
+ You are a personalized Kazakh language learning assistant with access to a comprehensive knowledge base and user learning history. Your role is to help users learn Kazakh words and idioms while tracking their progress and providing personalized recommendations.
462
+
463
+ Key capabilities:
464
+ 1. **Answer Queries**: Provide accurate definitions and examples for Kazakh words and idioms from your knowledge base
465
+ 2. **Track Learning Progress**: Identify and track when users learn new words or idioms
466
+ 3. **Personalized Responses**: Adapt responses based on user's learning history and progress
467
+ 4. **Progress Reporting**: Provide detailed progress reports when asked
468
+ 5. **Learning Recommendations**: Suggest words/idioms to review or learn next
469
+
470
+ Response Guidelines:
471
+ - For word/idiom queries: Provide definition, usage examples, and related information
472
+ - Always identify the main Kazakh word/idiom being discussed for progress tracking
473
+ - Be encouraging and supportive of the user's learning journey
474
+ - Use simple, clear explanations appropriate for language learners
475
+ - When discussing progress, be specific and motivating
476
+ - Avoid storing definitions as terms; only track the word/idiom itself
477
+ - Normalize terms to lowercase to avoid duplicates due to case differences
478
+
479
+ Format responses naturally in conversational style, not JSON unless specifically requested.
480
+ """
481
 
482
  self.llm = ChatGoogleGenerativeAI(
483
  model="models/gemini-1.5-flash",
484
  temperature=0.7,
485
+ model_kwargs={"system_instruction": system_prompt}
486
  )
487
 
488
+ def normalize_term(self, term: str) -> str:
489
+ """Normalize term by converting to lowercase and removing extra spaces"""
490
+ return ' '.join(term.lower().strip().split())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
 
492
  def extract_kazakh_terms(self, message: str, response: str) -> List[Tuple[str, str, str]]:
493
  """Extract meaningful Kazakh terms using document metadata to determine category"""
494
  terms = []
495
+ seen_terms = set()
496
 
497
  try:
498
  retrieved_docs = self.vectorstore.similarity_search(message, k=5)
499
 
500
+ response_normalized = self.normalize_term(response)
501
+ message_normalized = self.normalize_term(message)
502
 
503
+ is_multi_term_query = any(keyword in message_normalized for keyword in ['мысал', 'тіркестер', 'пример'])
504
+
505
+ common_words = {'бас', 'сөз', 'адам', 'жол', 'күн', 'су', 'жер', 'қол', 'тұр', 'бер'}
506
+
507
+ for known_term in self.known_terms:
508
+ normalized_known_term = self.normalize_term(known_term)
509
+ if normalized_known_term in response_normalized and normalized_known_term not in seen_terms:
 
 
 
 
 
 
 
 
510
 
511
+ if normalized_known_term in common_words and not (
512
+ normalized_known_term in message_normalized or is_multi_term_query
513
+ ):
514
+ print(f"Skipped common term: {known_term}")
515
+ continue
516
+
517
+ if normalized_known_term in message_normalized or any(
518
+ normalized_known_term in self.normalize_term(doc.page_content) for doc in retrieved_docs
519
+ ):
520
+ category = "idiom"
521
+ definition = ""
522
 
523
+ for doc in retrieved_docs:
524
+ if normalized_known_term in self.normalize_term(doc.page_content):
525
+ doc_type = doc.metadata.get('doc_type', '').lower()
526
+ if 'idiom' in doc_type or 'тіркес' in doc_type:
527
+ category = "idiom"
528
+ elif 'grammar' in doc_type:
529
+ category = "grammar"
530
+ else:
531
+ category = "word"
532
+ definition = self.extract_clean_definition(normalized_known_term, doc.page_content, response)
533
+ break
534
+
535
+ if definition and len(normalized_known_term.split()) <= 10:
536
+ terms.append((known_term, category, definition))
537
+ seen_terms.add(normalized_known_term)
538
+ print(f"Added term: {known_term}, category: {category}, definition: {definition}")
539
+
540
+ if not is_multi_term_query and normalized_known_term not in message_normalized:
541
+ return terms
542
 
543
+ if not terms and not is_multi_term_query:
544
+ kazakh_phrases = re.findall(
545
+ r'[А-Яа-яӘәҒғҚқҢңӨөҰұҮүҺһІі]+(?:[\s\-]+[А-Яа-яӘәҒғҚқҢңӨөҰұҮүҺһІі]+)*',
546
+ response
547
+ )
548
+
549
+ for phrase in kazakh_phrases:
550
+ normalized_phrase = self.normalize_term(phrase)
551
+
552
+ if normalized_phrase in seen_terms:
553
+ continue
554
+
555
+ if len(normalized_phrase) <= 2 or len(normalized_phrase) > 100:
556
+ print(f"Skipped phrase {normalized_phrase}: Invalid length")
557
+ continue
558
+
559
+ skip_words = ['деген', 'деп', 'берілген', 'мәтінде', 'мағынасы', 'дегеннің',
560
+ 'түсіндірілген', 'келтірілген', 'болып', 'табылады', 'ауруы',
561
+ 'мынадай', 'тақырыбына', 'тіркестер', 'арналған', 'байланысты']
562
+
563
+ if any(skip in normalized_phrase for skip in skip_words):
564
+ print(f"Skipped phrase {normalized_phrase}: Contains skip word")
565
+ continue
566
+
567
+ if normalized_phrase in common_words and normalized_phrase not in message_normalized:
568
+ print(f"Skipped common phrase: {normalized_phrase}")
569
+ continue
570
+
571
+ if normalized_phrase not in self.known_terms:
572
+ print(f"Warning: {normalized_phrase} not in known_terms, but processing anyway")
573
+
574
+ category = "word"
575
+ definition = ""
576
+
577
+ for doc in retrieved_docs:
578
+ if normalized_phrase in self.normalize_term(doc.page_content):
579
+ doc_type = doc.metadata.get('doc_type', '').lower()
580
+ if 'idiom' in doc_type or 'тіркес' in doc_type:
581
+ category = "idiom"
582
+ elif 'grammar' in doc_type:
583
+ category = "grammar"
584
+ else:
585
+ category = "word"
586
+
587
+ definition = self.extract_clean_definition(normalized_phrase, doc.page_content, response)
588
+ break
589
+
590
+ if definition and len(normalized_phrase.split()) <= 6:
591
+ if not any(normalized_phrase.startswith(q) for q in ['қалай', 'қандай', 'қайда', 'неше', 'қашан']):
592
+ terms.append((phrase, category, definition))
593
+ seen_terms.add(normalized_phrase)
594
+ print(f"Added term: {phrase}, category: {category}, definition: {definition}")
595
+ break
596
+
597
  except Exception as e:
598
  print(f"Error extracting terms: {e}")
599
+
600
  return terms
601
 
602
  def extract_clean_definition(self, term: str, doc_content: str, response: str) -> str:
603
+ """Extract clean definition for a term, avoiding storing definitions as terms"""
604
+ normalized_term = self.normalize_term(term)
605
+
606
  sentences = response.split('.')
607
  for sentence in sentences:
608
+ sentence = sentence.strip()
609
+ if normalized_term in self.normalize_term(sentence) and len(sentence) > 10 and len(sentence) < 150:
610
+ if not any(word in sentence.lower() for word in ['деген не', 'қалай аталады', 'нені білдіреді']):
611
+ return sentence
 
612
 
613
  doc_sentences = doc_content.split('.')
614
  for sentence in doc_sentences:
615
+ sentence = sentence.strip()
616
+ if normalized_term in self.normalize_term(sentence) and len(sentence) > 10 and len(sentence) < 150:
617
+ return sentence
 
618
 
619
  return f"Definition for {term}"
620
 
621
+ def get_user_memory(self, user_id: str):
622
+ """Get or create conversation memory for a specific user"""
623
+ if user_id not in self.user_memories:
624
+ self.user_memories[user_id] = ConversationBufferMemory(
625
+ memory_key='chat_history',
626
+ return_messages=True,
627
+ max_token_limit=10000
628
+ )
629
+ return self.user_memories[user_id]
630
+
631
+ def get_user_chain(self, user_id: str):
632
+ """Get or create conversation chain for a specific user"""
633
+ memory = self.get_user_memory(user_id)
634
+ retriever = self.vectorstore.as_retriever()
635
+ return ConversationalRetrievalChain.from_llm(
636
+ llm=self.llm,
637
+ retriever=retriever,
638
+ memory=memory
639
+ )
640
+
641
+ def process_message(self, message: str, user_id: str = "default_user", session_token: str = None, use_direct_gemini: bool = False, target_language: str = "English") -> str:
642
+ """Process user message with proper user session management and toggle for direct Gemini"""
643
 
644
  if session_token and not self.tracker.validate_session(user_id, session_token):
645
  return "Session expired. Please login again."
 
656
  return self.get_recommendations(user_id)
657
  elif message.lower().startswith('/review'):
658
  return self.get_review_words(user_id)
659
+ elif message.lower().startswith('/mastered'):
660
+ return self.get_mastered_words(user_id)
661
  elif message.lower().startswith('/help'):
662
  return self.get_help_message()
663
 
664
+ if use_direct_gemini:
665
+ return self.process_direct_gemini(message, user_id, target_language)
666
+
667
  conversation_chain = self.get_user_chain(user_id)
668
  result = conversation_chain.invoke({"question": message})
669
  response = result["answer"]
 
742
 
743
  return response
744
 
745
+ def get_mastered_words(self, user_id: str) -> str:
746
+ """Get words that have been mastered (mastery level > 0) for specific user"""
747
+ mastered_words = self.tracker.get_mastered_words(user_id, 10)
748
+
749
+ if not mastered_words:
750
+ return "Сізде әзірге меңгерілген сөздер жоқ. Терминдерді қайталауды жалғастырыңыз, сонда олар осында пайда болады! 🌟\n\nYou haven't mastered any words yet. Keep reviewing terms, and they'll appear here! 🌟"
751
+
752
+ response = "🏆 **Меңгерілген сөздер / Mastered Words**:\n\n"
753
+ for word_info in mastered_words:
754
+ emoji = "📝" if word_info['category'] == "word" else "🎭"
755
+
756
+ mastery_stars = "🟊" * word_info['mastery_level'] + "⬜" * (5 - word_info['mastery_level'])
757
+ response += f"{emoji} **{word_info['word']}** - {mastery_stars} (Кездесу саны / Encounters: {word_info['encounter_count']})\n"
758
+
759
+ definition_preview = word_info['definition'][:80] + "..." if len(word_info['definition']) > 80 else word_info['definition']
760
+ response += f" {definition_preview}\n\n"
761
+
762
+ return response
763
+
764
  def get_help_message(self) -> str:
765
  """Get help message with available commands"""
766
  return """
 
770
  - `/progress` - View your detailed learning progress
771
  - `/recommendations` - Get personalized learning suggestions
772
  - `/review` - See words that need review
773
+ - `/mastered` - See words you've mastered (mastery level > 0)
774
  - `/help` - Show this help message
775
 
776
  **How to Use**:
 
791
  """Create a session token for user authentication"""
792
  session_token = self.tracker.create_user_session(user_id)
793
  return session_token
794
+
795
+ def process_direct_gemini(self, message: str, user_id: str, target_language: str = "English") -> str:
796
+ """Process message using direct Gemini with grammar-focused prompt"""
797
+ try:
798
+
799
+ direct_prompt = """
800
+ You are a Kazakh language teacher specializing in grammar and vocabulary. Your role is to teach Kazakh grammar and words in the user's requested language (Kazakh, Russian, or English). Provide clear, concise explanations tailored to language learners, including examples and practical usage. If the user doesn't specify a language, default to English. Do not rely on external knowledge bases; use your internal knowledge to generate accurate and educational responses. Be encouraging and supportive, and adapt explanations to the user's proficiency level if known.
801
+ """
802
+ direct_llm = ChatGoogleGenerativeAI(
803
+ model="models/gemini-1.5-flash",
804
+ temperature=0.7,
805
+ model_kwargs={"system_instruction": direct_prompt}
806
+ )
807
+
808
+ message_lower = message.lower()
809
+ if any(keyword in message_lower for keyword in ['kazakh', 'қазақша', 'қазақ тілінде']):
810
+ target_language = "Kazakh"
811
+ elif any(keyword in message_lower for keyword in ['russian', 'русский', 'орысша']):
812
+ target_language = "Russian"
813
+
814
+ modified_message = f"Explain in {target_language}: {message}"
815
+ response = direct_llm.invoke(modified_message).content
816
+ return response
817
+ except Exception as e:
818
+ return f"Error processing direct Gemini request: {str(e)}"
819
 
820
  assistant = PersonalizedKazakhAssistant()
821
 
822
+ def chat_interface(message, history, use_direct_gemini, target_language):
823
+ """Chat interface for Gradio with toggle for direct Gemini mode"""
 
824
  try:
 
 
825
  web_user_id = "web_user_default" # Consistent ID
826
+ response = assistant.process_message(message, web_user_id, use_direct_gemini=use_direct_gemini, target_language=target_language)
827
  return response
828
  except Exception as e:
829
  return f"Sorry, I encountered an error: {str(e)}. Please try again."
830
+
 
831
  def api_login(user_id: str) -> dict:
832
  """API endpoint for user login/session creation"""
833
  try:
 
844
  "error": str(e)
845
  }
846
 
847
+ def api_chat(message: str, user_id: str, session_token: str = None, use_direct_gemini: bool = False, target_language: str = "English") -> dict:
848
+ """API endpoint for chat functionality with proper user session and direct Gemini toggle"""
849
  try:
850
+ response = assistant.process_message(message, user_id, session_token, use_direct_gemini, target_language)
851
  return {
852
  "success": True,
853
  "response": response,
 
923
  "error": str(e)
924
  }
925
 
926
+ def api_mastered_words(user_id: str, session_token: str = None) -> dict:
927
+ """API endpoint for mastered words with session validation"""
928
+ try:
929
+ if session_token and not assistant.tracker.validate_session(user_id, session_token):
930
+ return {"success": False, "error": "Invalid session"}
931
+
932
+ mastered_text = assistant.get_mastered_words(user_id)
933
+ mastered_data = assistant.tracker.get_mastered_words(user_id, 10)
934
+
935
+ return {
936
+ "success": True,
937
+ "mastered_text": mastered_text,
938
+ "mastered_data": mastered_data,
939
+ "user_id": user_id
940
+ }
941
+ except Exception as e:
942
+ return {
943
+ "success": False,
944
+ "error": str(e)
945
+ }
946
+
947
  with gr.Blocks(title="🇰🇿 Kazakh Learning API") as demo:
 
948
  gr.Markdown("# 🇰🇿 Personalized Kazakh Learning Assistant")
949
  gr.Markdown("### Multi-User Chat Interface + API Endpoints for Mobile Integration")
950
 
951
  with gr.Tab("💬 Chat Interface"):
952
+ gr.Markdown("Toggle **Direct Gemini Mode** to learn Kazakh grammar without RAG. Select the language for explanations.")
953
+ with gr.Row():
954
+ use_direct_gemini = gr.Checkbox(label="Direct Gemini Mode (No RAG/Tracking)", value=False)
955
+ target_language = gr.Dropdown(
956
+ label="Explanation Language",
957
+ choices=["English", "Kazakh", "Russian"],
958
+ value="English"
959
+ )
960
  chat_interface = gr.ChatInterface(
961
+ fn=chat_interface,
962
+ additional_inputs=[use_direct_gemini, target_language],
963
  type="messages",
964
  examples=[
965
+ ["сәлем деген не?", False, "English"],
966
+ ["күләпара не үшін керек?", False, "English"],
967
+ ["/progress", False, "English"],
968
+ ["/recommendations", False, "English"],
969
+ ["/review", False, "English"],
970
+ ["/mastered", False, "English"],
971
+ ["Explain Kazakh noun cases in Russian", True, "Russian"],
972
+ ["Teach me Kazakh verb conjugation in English", True, "English"]
973
  ]
974
  )
975
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  with gr.Tab("📖 API Documentation"):
977
  gr.Markdown("""
978
  ## API Endpoints for Flutter Integration
 
979
  ### Base URL: `https://huggingface.co/spaces/GuestUser33/kazakh-learning-api`
980
+
981
  ### Authentication Flow:
982
+ 1. **Login** to get a session token
983
  2. **Use session token** for subsequent API calls
984
  3. **Session tokens expire** after inactivity
985
+
986
  ### Available Endpoints:
987
+
988
  #### 1. Login API
989
  ```
990
  POST /api/predict
991
  Content-Type: application/json
992
+
993
  {
994
+ "data": ["user_id"],
995
+ "fn_index": 0
996
  }
997
  ```
998
+ **Response**: `{"success": true, "session_token": "uuid", "user_id": "user_id", "message": "Login successful"}`
999
+
1000
  #### 2. Chat API
1001
  ```
1002
  POST /api/predict
1003
  Content-Type: application/json
1004
+
1005
  {
1006
+ "data": ["message", "user_id", "session_token", use_direct_gemini, "target_language"],
1007
+ "fn_index": 1
1008
  }
1009
  ```
1010
+ **Parameters**:
1011
+ - `message`: The user's query (e.g., "сәлем деген не?" or "Explain Kazakh noun cases")
1012
+ - `user_id`: Unique identifier for the user
1013
+ - `session_token`: Session token from login (optional, but required for authenticated sessions)
1014
+ - `use_direct_gemini`: Boolean (`true`/`false`) to toggle Direct Gemini mode for grammar-focused responses without RAG/tracking
1015
+ - `target_language`: Language for responses (`English`, `Kazakh`, or `Russian`)
1016
+
1017
+ **Response**: `{"success": true, "response": "response_text", "user_id": "user_id"}`
1018
+
1019
+ #### 3. Progress API
1020
  ```
1021
  POST /api/predict
1022
  Content-Type: application/json
1023
+
1024
  {
1025
+ "data": ["user_id", "session_token"],
1026
+ "fn_index": 2
1027
  }
1028
  ```
1029
+ **Response**: `{"success": true, "progress_text": "progress_report", "progress_data": {...}, "user_id": "user_id"}`
1030
+
1031
  #### 4. Recommendations API
1032
  ```
1033
  POST /api/predict
1034
  Content-Type: application/json
1035
+
1036
  {
1037
+ "data": ["user_id", "session_token"],
1038
+ "fn_index": 3
1039
  }
1040
  ```
1041
+ **Response**: `{"success": true, "recommendations_text": "recommendations", "recommendations_list": [...], "user_id": "user_id"}`
1042
+
1043
  #### 5. Review Words API
1044
  ```
1045
  POST /api/predict
1046
  Content-Type: application/json
1047
+
1048
  {
1049
+ "data": ["user_id", "session_token"],
1050
+ "fn_index": 4
1051
  }
1052
  ```
1053
+ **Response**: `{"success": true, "review_text": "review_words", "review_data": [...], "user_id": "user_id"}`
1054
+
1055
+ #### 6. Mastered Words API
1056
+ ```
1057
+ POST /api/predict
1058
+ Content-Type: application/json
1059
+
1060
+ {
1061
+ "data": ["user_id", "session_token"],
1062
+ "fn_index": 5
1063
+ }
1064
+ ```
1065
+ **Response**: `{"success": true, "mastered_text": "mastered_words", "mastered_data": [...], "user_id": "user_id"}`
1066
+
1067
  ### Flutter Integration Example:
1068
  ```dart
1069
+ import 'dart:convert';
1070
+ import 'package:http/http.dart' as http;
1071
+
1072
  class KazakhLearningAPI {
1073
+ static const String baseUrl = 'https://huggingface.co/spaces/GuestUser33/kazakh-learning-api';
1074
+ String? sessionToken;
1075
+ String? userId;
1076
+
1077
+ // Login and get session token
1078
+ Future<bool> login(String userId) async {
1079
  final response = await http.post(
1080
+ Uri.parse('$baseUrl/api/predict'),
1081
+ headers: {'Content-Type': 'application/json'},
1082
+ body: jsonEncode({
1083
  'data': [userId],
1084
  'fn_index': 0
1085
+ }),
1086
  );
1087
+
1088
  if (response.statusCode == 200) {
1089
+ final result = jsonDecode(response.body);
1090
+ if (result['data'][0]['success']) {
1091
  this.userId = userId;
1092
  this.sessionToken = result['data'][0]['session_token'];
1093
  return true;
1094
+ }
1095
  }
1096
  return false;
1097
+ }
1098
+
1099
+ // Send chat message
1100
+ Future<String?> sendMessage(
1101
+ String message, {
1102
+ bool useDirectGemini = false,
1103
+ String targetLanguage = 'English',
1104
+ }) async {
1105
  if (sessionToken == null) return null;
1106
+
1107
  final response = await http.post(
1108
+ Uri.parse('$baseUrl/api/predict'),
1109
+ headers: {'Content-Type': 'application/json'},
1110
+ body: jsonEncode({
1111
+ 'data': [message, userId, sessionToken, useDirectGemini, targetLanguage],
1112
  'fn_index': 1
1113
+ }),
1114
  );
1115
+
1116
  if (response.statusCode == 200) {
1117
+ final result = jsonDecode(response.body);
1118
+ if (result['data'][0]['success']) {
1119
  return result['data'][0]['response'];
1120
+ }
1121
  }
1122
  return null;
1123
+ }
1124
+
1125
+ // Get user progress
1126
+ Future<Map<String, dynamic>?> getProgress() async {
1127
  if (sessionToken == null) return null;
1128
+
1129
  final response = await http.post(
1130
+ Uri.parse('$baseUrl/api/predict'),
1131
+ headers: {'Content-Type': 'application/json'},
1132
+ body: jsonEncode({
1133
  'data': [userId, sessionToken],
1134
  'fn_index': 2
1135
+ }),
1136
  );
1137
+
1138
  if (response.statusCode == 200) {
1139
+ final result = jsonDecode(response.body);
1140
+ if (result['data'][0]['success']) {
1141
  return result['data'][0]['progress_data'];
1142
+ }
1143
  }
1144
  return null;
1145
+ }
1146
+
1147
+ // Get recommendations
1148
+ Future<List<String>?> getRecommendations() async {
1149
+ if (sessionToken == null) return null;
1150
+
1151
+ final response = await http.post(
1152
+ Uri.parse('$baseUrl/api/predict'),
1153
+ headers: {'Content-Type': 'application/json'},
1154
+ body: jsonEncode({
1155
+ 'data': [userId, sessionToken],
1156
+ 'fn_index': 3
1157
+ }),
1158
+ );
1159
+
1160
+ if (response.statusCode == 200) {
1161
+ final result = jsonDecode(response.body);
1162
+ if (result['data'][0]['success']) {
1163
+ return List<String>.from(result['data'][0]['recommendations_list']);
1164
+ }
1165
+ }
1166
+ return null;
1167
+ }
1168
+
1169
+ // Get words to review
1170
+ Future<List<dynamic>?> getReviewWords() async {
1171
+ if (sessionToken == null) return null;
1172
+
1173
+ final response = await http.post(
1174
+ Uri.parse('$baseUrl/api/predict'),
1175
+ headers: {'Content-Type': 'application/json'},
1176
+ body: jsonEncode({
1177
+ 'data': [userId, sessionToken],
1178
+ 'fn_index': 4
1179
+ }),
1180
+ );
1181
+
1182
+ if (response.statusCode == 200) {
1183
+ final result = jsonDecode(response.body);
1184
+ if (result['data'][0]['success']) {
1185
+ return result['data'][0]['review_data'];
1186
+ }
1187
+ }
1188
+ return null;
1189
+ }
1190
+
1191
+ // Get mastered words
1192
+ Future<List<dynamic>?> getMasteredWords() async {
1193
+ if (sessionToken == null) return null;
1194
+
1195
+ final response = await http.post(
1196
+ Uri.parse('$baseUrl/api/predict'),
1197
+ headers: {'Content-Type': 'application/json'},
1198
+ body: jsonEncode({
1199
+ 'data': [userId, sessionToken],
1200
+ 'fn_index': 5
1201
+ }),
1202
+ );
1203
+
1204
+ if (response.statusCode == 200) {
1205
+ final result = jsonDecode(response.body);
1206
+ if (result['data'][0]['success']) {
1207
+ return result['data'][0]['mastered_data'];
1208
+ }
1209
+ }
1210
+ return null;
1211
+ }
1212
  }
1213
  ```
1214
+
1215
  ### Key Features:
1216
  - ✅ **Multi-User Support**: Each user has separate learning progress
1217
  - ✅ **Session Management**: Secure session tokens for authentication
1218
+ - ✅ **Personalized Tracking**: Individual progress tracking per user (in RAG mode)
1219
+ - ✅ **Direct Gemini Mode**: Toggle for grammar-focused responses without RAG/tracking
1220
+ - ✅ **Multi-Language Support**: Responses in English, Kazakh, or Russian
1221
  - ✅ **API Ready**: All endpoints ready for mobile app integration
1222
  - ✅ **Session Validation**: Automatic session validation and expiry
1223
+
1224
  ### Usage Notes:
1225
  - Always call **login** first to get a session token
1226
  - Include **session_token** in all subsequent API calls
1227
+ - Use `use_direct_gemini: true` for grammar/vocabulary lessons without tracking
1228
+ - Specify `target_language` (`English`, `Kazakh`, `Russian`) for Direct Gemini mode
1229
  - Handle **session expiry** by re-logging in
1230
+ - Use **unique user_id** for each user (e.g., email, username)
1231
+ - Commands like `/progress`, `/recommendations`, `/review`, `/mastered` are only available in RAG mode (`use_direct_gemini: false`)
1232
+ """)
1233
 
1234
  if __name__ == "__main__":
1235
  demo.launch()