bat-6 commited on
Commit
809b701
·
1 Parent(s): e0307a3

feat: implement recommendation engine architecture with chatbot logic, intent classification, and project validation modules.

Browse files
Files changed (29) hide show
  1. src/recommendation_engine/__pycache__/__init__.cpython-311.pyc +0 -0
  2. src/recommendation_engine/__pycache__/__init__.cpython-313.pyc +0 -0
  3. src/recommendation_engine/__pycache__/chatbot_engine.cpython-311.pyc +0 -0
  4. src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc +0 -0
  5. src/recommendation_engine/__pycache__/command_handler.cpython-313.pyc +0 -0
  6. src/recommendation_engine/__pycache__/config.cpython-313.pyc +0 -0
  7. src/recommendation_engine/__pycache__/context_builder.cpython-311.pyc +0 -0
  8. src/recommendation_engine/__pycache__/context_builder.cpython-313.pyc +0 -0
  9. src/recommendation_engine/__pycache__/feature_generator.cpython-313.pyc +0 -0
  10. src/recommendation_engine/__pycache__/full_project_generator.cpython-313.pyc +0 -0
  11. src/recommendation_engine/__pycache__/idea_generator.cpython-313.pyc +0 -0
  12. src/recommendation_engine/__pycache__/llm_client.cpython-313.pyc +0 -0
  13. src/recommendation_engine/__pycache__/llm_router.cpython-311.pyc +0 -0
  14. src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc +0 -0
  15. src/recommendation_engine/__pycache__/memory_store.cpython-311.pyc +0 -0
  16. src/recommendation_engine/__pycache__/memory_store.cpython-313.pyc +0 -0
  17. src/recommendation_engine/__pycache__/novelty_checker.cpython-313.pyc +0 -0
  18. src/recommendation_engine/__pycache__/prompt_builder.cpython-313.pyc +0 -0
  19. src/recommendation_engine/__pycache__/response_formatter.cpython-313.pyc +0 -0
  20. src/recommendation_engine/__pycache__/state_manager.cpython-313.pyc +0 -0
  21. src/recommendation_engine/__pycache__/validator.cpython-313.pyc +0 -0
  22. src/recommendation_engine/chatbot_engine.py +668 -262
  23. src/recommendation_engine/config.py +5 -7
  24. src/recommendation_engine/context_builder.py +39 -11
  25. src/recommendation_engine/idea_generator.py +91 -11
  26. src/recommendation_engine/llm_client.py +5 -0
  27. src/recommendation_engine/memory_store.py +16 -4
  28. src/recommendation_engine/prompt_builder.py +19 -14
  29. src/recommendation_engine/test.py +25 -0
src/recommendation_engine/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/__init__.cpython-311.pyc and b/src/recommendation_engine/__pycache__/__init__.cpython-311.pyc differ
 
src/recommendation_engine/__pycache__/__init__.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/__init__.cpython-313.pyc and b/src/recommendation_engine/__pycache__/__init__.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/chatbot_engine.cpython-311.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/chatbot_engine.cpython-311.pyc and b/src/recommendation_engine/__pycache__/chatbot_engine.cpython-311.pyc differ
 
src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc and b/src/recommendation_engine/__pycache__/chatbot_engine.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/command_handler.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/command_handler.cpython-313.pyc and b/src/recommendation_engine/__pycache__/command_handler.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/config.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/config.cpython-313.pyc and b/src/recommendation_engine/__pycache__/config.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/context_builder.cpython-311.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/context_builder.cpython-311.pyc and b/src/recommendation_engine/__pycache__/context_builder.cpython-311.pyc differ
 
src/recommendation_engine/__pycache__/context_builder.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/context_builder.cpython-313.pyc and b/src/recommendation_engine/__pycache__/context_builder.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/feature_generator.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/feature_generator.cpython-313.pyc and b/src/recommendation_engine/__pycache__/feature_generator.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/full_project_generator.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/full_project_generator.cpython-313.pyc and b/src/recommendation_engine/__pycache__/full_project_generator.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/idea_generator.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/idea_generator.cpython-313.pyc and b/src/recommendation_engine/__pycache__/idea_generator.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/llm_client.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/llm_client.cpython-313.pyc and b/src/recommendation_engine/__pycache__/llm_client.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/llm_router.cpython-311.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/llm_router.cpython-311.pyc and b/src/recommendation_engine/__pycache__/llm_router.cpython-311.pyc differ
 
src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc and b/src/recommendation_engine/__pycache__/llm_router.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/memory_store.cpython-311.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/memory_store.cpython-311.pyc and b/src/recommendation_engine/__pycache__/memory_store.cpython-311.pyc differ
 
src/recommendation_engine/__pycache__/memory_store.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/memory_store.cpython-313.pyc and b/src/recommendation_engine/__pycache__/memory_store.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/novelty_checker.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/novelty_checker.cpython-313.pyc and b/src/recommendation_engine/__pycache__/novelty_checker.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/prompt_builder.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/prompt_builder.cpython-313.pyc and b/src/recommendation_engine/__pycache__/prompt_builder.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/response_formatter.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/response_formatter.cpython-313.pyc and b/src/recommendation_engine/__pycache__/response_formatter.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/state_manager.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/state_manager.cpython-313.pyc and b/src/recommendation_engine/__pycache__/state_manager.cpython-313.pyc differ
 
src/recommendation_engine/__pycache__/validator.cpython-313.pyc CHANGED
Binary files a/src/recommendation_engine/__pycache__/validator.cpython-313.pyc and b/src/recommendation_engine/__pycache__/validator.cpython-313.pyc differ
 
src/recommendation_engine/chatbot_engine.py CHANGED
@@ -26,12 +26,125 @@ from src.recommendation_engine.full_project_generator import (
26
 
27
  import re
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def extract_number(text: str, default=5):
30
 
31
  nums = re.findall(r"\d+", text)
32
 
33
  return min(int(nums[0]), 20) if nums else default
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  def is_weak_project_title(title: str) -> bool:
36
 
37
  if not title:
@@ -117,9 +230,6 @@ def looks_like_real_project_title(title: str) -> bool:
117
 
118
  lowered = title.lower()
119
 
120
-
121
-
122
-
123
  question_starts = (
124
  "how ",
125
  "what ",
@@ -135,7 +245,9 @@ def looks_like_real_project_title(title: str) -> bool:
135
  "does "
136
  )
137
 
138
-
 
 
139
 
140
  for p in nonsense_patterns:
141
  if p in lowered:
@@ -188,7 +300,24 @@ def looks_like_real_project_title(title: str) -> bool:
188
  "system",
189
  "platform",
190
  "application",
191
- "app"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
 
194
  if not any(
@@ -258,7 +387,12 @@ def is_gibberish_text(text: str) -> bool:
258
  "ux",
259
  "vr",
260
  "ar",
261
- "iot"
 
 
 
 
 
262
  }
263
 
264
  if text in allowed_short:
@@ -352,6 +486,30 @@ def is_project_related(text: str) -> bool:
352
  for keyword in keywords
353
  )
354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  def chatbot(user_id: str, user_input: str):
356
 
357
  text = user_input.lower().strip()
@@ -374,11 +532,13 @@ def chatbot(user_id: str, user_input: str):
374
  "ideas"
375
  ]
376
 
377
- if any(x in text for x in explicit_idea_requests):
378
-
379
- state = get_user_memory(user_id).get("state") or default_state()
380
-
381
- state["waiting_for_domain"] = True
 
 
382
 
383
  save_user_memory(user_id, {
384
  "history": get_user_memory(user_id).get("history", []),
@@ -387,9 +547,10 @@ def chatbot(user_id: str, user_input: str):
387
 
388
  domain_list = "\n".join(f"- {d}" for d in DOMAIN_KEYWORDS.keys() if d != "Others")
389
  return (
390
- f"Here are the available domains:\n\n"
391
  f"{domain_list}\n\n"
392
- f"If your domain doesn't match any of these, you can choose 'Others'."
 
393
  )
394
 
395
 
@@ -403,12 +564,12 @@ def chatbot(user_id: str, user_input: str):
403
  if is_gibberish_text(text):
404
 
405
  return (
406
- "⚠️ I could not understand your request.\n\n"
407
- "Try something meaningful like:\n"
408
- "- generate AI project ideas\n"
409
- "- generate features for smart hospital system\n"
410
- "- improve my project\n"
411
- "- suggest technologies for fintech app"
412
  )
413
 
414
 
@@ -425,6 +586,274 @@ def chatbot(user_id: str, user_input: str):
425
 
426
  state = memory.get("state") or default_state()
427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
  state.setdefault("menu_mode", False)
430
  state.setdefault("selected_option", None)
@@ -450,18 +879,12 @@ def chatbot(user_id: str, user_input: str):
450
  })
451
 
452
  return (
453
- "👋 Welcome!\n\n"
454
- "I can help you with:\n"
455
- " Graduation project ideas\n"
456
- " Smart feature generation\n"
457
- " Project improvement\n"
458
- " Technology suggestions\n"
459
- "• Project descriptions\n\n"
460
- "Try saying things like:\n"
461
- "- give me AI project ideas\n"
462
- "- generate features for smart hospital system\n"
463
- "- improve my graduation project\n"
464
- "- suggest technologies for fintech app"
465
  )
466
 
467
 
@@ -520,39 +943,28 @@ def chatbot(user_id: str, user_input: str):
520
  result,
521
  mode="merge"
522
  )
 
 
 
 
523
 
524
  response = f"""
525
  📦 Full Project Generated
526
 
527
- 📌 Title:
528
  {state.get("project_title")}
529
 
530
- 📄 Abstract:
531
- {state.get("abstract")}
532
-
533
- 📄 Description:
534
- {state.get("description")}
535
-
536
- ⚙️ Features:
537
- {chr(10).join("- " + x for x in state.get("features", []))}
538
 
539
  🛠 Technologies:
540
- {chr(10).join("- " + x for x in state.get("technologies", []))}
541
-
542
- 🎯 Objectives:
543
- {chr(10).join("- " + x for x in state.get("objectives", []))}
544
-
545
- ⚡ Methodology:
546
- {state.get("methodology")}
547
 
548
- 🚀 Future Work:
549
- {chr(10).join("- " + x for x in state.get("future_work", []))}
550
-
551
- 📂 Category:
552
- {state.get("category")}
553
 
554
- 🏷 Keywords:
555
- {", ".join(state.get("keywords", []))}
556
 
557
  ❗ Problem Statement:
558
  {state.get("problem_statement")}
@@ -560,9 +972,13 @@ def chatbot(user_id: str, user_input: str):
560
  💡 Proposed Solution:
561
  {state.get("proposed_solution")}
562
 
563
- 🤖 AI Summary:
564
- {state.get("ai_summary")}
565
- """
 
 
 
 
566
 
567
  return finalize_response(
568
  user_input,
@@ -583,12 +999,13 @@ def chatbot(user_id: str, user_input: str):
583
  })
584
 
585
  return (
586
- "🎯 What domain do you want?\n\n"
587
- "Examples:\n"
588
  "- AI\n"
589
- "- healthcare\n"
590
- "- education\n"
591
- "- fintech"
 
592
  )
593
 
594
 
@@ -611,9 +1028,10 @@ def chatbot(user_id: str, user_input: str):
611
 
612
  domain_list = "\n".join(f"- {d}" for d in DOMAIN_KEYWORDS.keys() if d != "Others")
613
  return (
614
- f"Here are the available domains:\n\n"
615
  f"{domain_list}\n\n"
616
- f"If your domain doesn't match any of these, you can choose 'Others'."
 
617
  )
618
 
619
  elif text == "2":
@@ -628,10 +1046,11 @@ def chatbot(user_id: str, user_input: str):
628
  })
629
 
630
  return (
631
- "⚠️ Please provide the project title first.\n\n"
 
632
  "Example:\n"
633
- "- AI-powered smart library recommendation system\n\n"
634
- "👉 You can also ask me to generate an idea first."
635
  )
636
 
637
  save_user_memory(user_id, {
@@ -726,9 +1145,10 @@ def chatbot(user_id: str, user_input: str):
726
  })
727
 
728
  return (
729
- f"💬 Chat mode enabled for:\n"
730
- f"{state.get('project_title')}\n\n"
731
- "You can now discuss or improve the project."
 
732
  )
733
 
734
 
@@ -760,9 +1180,10 @@ def chatbot(user_id: str, user_input: str):
760
  })
761
 
762
  return (
763
- f"💬 Chat mode enabled for:\n"
764
- f"{state.get('project_title')}\n\n"
765
- "You can now discuss or improve the project."
 
766
  )
767
 
768
 
@@ -810,39 +1231,28 @@ def chatbot(user_id: str, user_input: str):
810
  result,
811
  mode="merge"
812
  )
 
 
 
 
813
 
814
  response = f"""
815
  📦 Full Project Generated
816
 
817
- 📌 Title:
818
  {state.get("project_title")}
819
 
820
- 📄 Abstract:
821
- {state.get("abstract")}
822
-
823
- 📄 Description:
824
- {state.get("description")}
825
-
826
- ⚙️ Features:
827
- {chr(10).join("- " + x for x in state.get("features", []))}
828
 
829
  🛠 Technologies:
830
- {chr(10).join("- " + x for x in state.get("technologies", []))}
831
-
832
- 🎯 Objectives:
833
- {chr(10).join("- " + x for x in state.get("objectives", []))}
834
-
835
- ⚡ Methodology:
836
- {state.get("methodology")}
837
 
838
- 🚀 Future Work:
839
- {chr(10).join("- " + x for x in state.get("future_work", []))}
840
-
841
- 📂 Category:
842
- {state.get("category")}
843
 
844
- 🏷 Keywords:
845
- {", ".join(state.get("keywords", []))}
846
 
847
  ❗ Problem Statement:
848
  {state.get("problem_statement")}
@@ -850,8 +1260,12 @@ def chatbot(user_id: str, user_input: str):
850
  💡 Proposed Solution:
851
  {state.get("proposed_solution")}
852
 
853
- 🤖 AI Summary:
854
- {state.get("ai_summary")}
 
 
 
 
855
  """
856
 
857
  return finalize_response(
@@ -865,73 +1279,30 @@ def chatbot(user_id: str, user_input: str):
865
 
866
 
867
 
868
- if state.get("weak_title_candidate"):
869
-
870
- improve_words = [
871
- "1",
872
- "make it descriptive",
873
- "improve title",
874
- "rewrite title",
875
- "make better"
876
- ]
877
-
878
- if any(w in text for w in improve_words):
879
 
880
- weak_title = state.get("weak_title_candidate")
881
-
882
- prompt = f"""
883
- You are a senior software architect.
884
-
885
- Convert this weak graduation project title
886
- into a professional and descriptive title.
887
-
888
- Weak title:
889
- {weak_title}
890
-
891
- Rules:
892
- - Keep original meaning
893
- - Make it specific
894
- - Make it professional
895
- - Keep it concise
896
- - Return ONLY the improved title
897
- """
898
 
899
- improved_title = generate_text(
900
- prompt,
901
- task="chat"
902
- ).strip()
903
 
904
- state["weak_title_candidate"] = None
905
- state["project_title"] = improved_title
906
- state["waiting_for_project_action"] = True
907
 
 
 
 
908
  save_user_memory(user_id, {
909
  "history": history,
910
  "state": state
911
  })
912
-
913
  return (
914
- f"📌 Improved Project Title:\n"
915
- f"{improved_title}\n\n"
916
- "Choose what you want next:\n"
917
- "1️⃣ Generate features\n"
918
- "2️⃣ Talk with chatbot about the idea\n\n"
919
- "👉 You can also say:\n"
920
- "- generate features\n"
921
- "- discuss project"
922
  )
923
-
924
 
925
-
926
-
927
-
928
- if state.get("waiting_for_feature_title"):
929
-
930
- state["waiting_for_feature_title"] = False
931
-
932
- possible_title = user_input.strip()
933
-
934
-
935
  if is_weak_project_title(possible_title):
936
 
937
  state["weak_title_candidate"] = possible_title
@@ -946,9 +1317,7 @@ def chatbot(user_id: str, user_input: str):
946
  "Examples:\n"
947
  "- AI-powered smart library recommendation system\n"
948
  "- Smart hospital emergency response platform\n\n"
949
- "Choose:\n"
950
- "1️⃣ Make the title more descriptive\n"
951
- "2️⃣ Enter another title"
952
  )
953
 
954
 
@@ -1014,8 +1383,9 @@ def chatbot(user_id: str, user_input: str):
1014
 
1015
  if text.startswith(pattern + " "):
1016
 
1017
- pattern_idx = user_input.lower().find(pattern)
1018
- extracted_title = user_input[pattern_idx + len(pattern):].strip() if pattern_idx != -1 else ""
 
1019
 
1020
  if (
1021
  extracted_title
@@ -1233,62 +1603,87 @@ Choose what you want:
1233
 
1234
  if state.get("waiting_for_niche_domain"):
1235
  text_lower = user_input.strip().lower()
1236
- more_words = ["more", "other", "another", "different", "none", "generate more"]
1237
 
1238
- if any(w in text_lower for w in more_words) and len(text_lower.split()) < 5:
1239
- previous_domains = state.get("suggested_domains", [])
1240
- niche_domains = generate_list(build_niche_domains_prompt(previous_domains), task="idea")
1241
-
1242
- state["suggested_domains"] = previous_domains + niche_domains
1243
-
1244
- domain_list_str = "\n".join(f"- {d}" for d in niche_domains)
1245
- response = (
1246
- f"Here are some MORE niche and cutting-edge domain ideas:\n\n"
1247
- f"{domain_list_str}\n\n"
1248
- f"Which one would you like to explore? (Or type your own!)"
1249
- )
1250
- save_user_memory(user_id, {"history": history, "state": state})
1251
- return finalize_response(user_input, response, history, state, user_id)
1252
- else:
1253
- domain = user_input.strip()
1254
  state["waiting_for_niche_domain"] = False
1255
  state["suggested_domains"] = []
1256
- analysis["domain"] = domain
1257
  analysis["intent"] = "idea"
1258
  if "project_title" in analysis:
1259
  del analysis["project_title"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1260
 
1261
  elif state.get("waiting_for_domain"):
1262
-
1263
- detected = extract_domain(user_input)
1264
-
1265
- if detected == "Others":
1266
- state["waiting_for_domain"] = False
1267
- state["waiting_for_niche_domain"] = True
1268
-
1269
- niche_domains = generate_list(build_niche_domains_prompt([]), task="idea")
1270
- state["suggested_domains"] = niche_domains
1271
-
1272
- domain_list_str = "\n".join(f"- {d}" for d in niche_domains)
1273
- response = (
1274
- f"You selected 'Others'. Here are some highly niche and cutting-edge domain ideas generated just for you:\n\n"
1275
- f"{domain_list_str}\n\n"
1276
- f"Which one would you like to explore? (Or type your own!)"
1277
- )
1278
- save_user_memory(user_id, {"history": history, "state": state})
1279
- return finalize_response(user_input, response, history, state, user_id)
1280
-
1281
- elif detected:
1282
  state["waiting_for_domain"] = False
1283
- analysis["domain"] = detected
 
1284
  analysis["intent"] = "idea"
1285
  if "project_title" in analysis:
1286
  del analysis["project_title"]
1287
  else:
1288
- return (
1289
- "⚠️ Please enter a valid domain "
1290
- "(AI, healthcare, fintech...)"
1291
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
 
1293
  intent = analysis.get("intent", "chat")
1294
 
@@ -1296,6 +1691,16 @@ Choose what you want:
1296
 
1297
  user_project = analysis.get("project_title")
1298
 
 
 
 
 
 
 
 
 
 
 
1299
 
1300
 
1301
 
@@ -1363,6 +1768,7 @@ Choose what you want:
1363
 
1364
  if (
1365
  domain
 
1366
  and intent == "chat"
1367
  and len(text.split()) <= 3
1368
  and not any(cmd in text for cmd in full_project_commands)
@@ -1446,9 +1852,15 @@ Choose what you want:
1446
 
1447
  if user_project:
1448
 
1449
-
1450
-
1451
-
 
 
 
 
 
 
1452
  if is_weak_project_title(user_project):
1453
 
1454
  state["weak_title_candidate"] = user_project
@@ -1464,11 +1876,7 @@ Choose what you want:
1464
  "- AI-powered smart library recommendation system\n"
1465
  "- Smart hospital emergency response platform\n"
1466
  "- Blockchain-secured academic certificate verification system\n\n"
1467
- "Choose:\n"
1468
- "1️⃣ Make the title more descriptive\n"
1469
- "2️⃣ Enter another title\n\n"
1470
- "👉 You can also say:\n"
1471
- "- make it descriptive"
1472
  )
1473
 
1474
  state = default_state()
@@ -1521,10 +1929,11 @@ Choose what you want:
1521
  ):
1522
 
1523
  return (
1524
- "Please give me the project title first.\n\n"
 
1525
  "Example:\n"
1526
- "- AI-powered smart library recommendation system\n"
1527
- "- Smart hospital emergency response platform"
1528
  )
1529
 
1530
 
@@ -1542,9 +1951,10 @@ Choose what you want:
1542
 
1543
  domain_list = "\n".join(f"- {d}" for d in DOMAIN_KEYWORDS.keys() if d != "Others")
1544
  return (
1545
- f"Here are the available domains:\n\n"
1546
  f"{domain_list}\n\n"
1547
- f"If your domain doesn't match any of these, you can choose 'Others'."
 
1548
  )
1549
 
1550
  top_k = analysis.get("number") or extract_number(
@@ -1564,6 +1974,20 @@ Choose what you want:
1564
  state["domain"] = domain
1565
  state["last_action"] = "idea"
1566
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1567
  response = format_response("idea", "", state)
1568
 
1569
  return finalize_response(
@@ -1591,8 +2015,10 @@ Choose what you want:
1591
  if not state.get("project_title"):
1592
 
1593
  return (
1594
- "Please give me the project title first."
1595
- )
 
 
1596
 
1597
  top_k = analysis.get("number") or extract_number(
1598
  user_input,
@@ -1650,13 +2076,14 @@ Choose what you want:
1650
  if not state.get("project_title"):
1651
 
1652
  return (
1653
- "Please generate project idea first."
1654
  )
1655
 
1656
  if not state.get("features"):
1657
 
1658
  return (
1659
- "Please generate features first."
 
1660
  )
1661
 
1662
  result = generate_full_project(
@@ -1675,12 +2102,18 @@ Choose what you want:
1675
  result,
1676
  mode="merge"
1677
  )
 
 
 
 
1678
 
1679
  response = f"""
1680
- 📦 Full Project Generated
1681
 
1682
- 📌 Title:
1683
- {state.get("project_title")}
 
 
1684
 
1685
  📄 Abstract:
1686
  {state.get("abstract")}
@@ -1688,32 +2121,18 @@ Choose what you want:
1688
  📄 Description:
1689
  {state.get("description")}
1690
 
1691
- 🛠 Technologies:
1692
- {chr(10).join("- " + x for x in state.get("technologies", []))}
1693
-
1694
- 🎯 Objectives:
1695
- {chr(10).join("- " + x for x in state.get("objectives", []))}
1696
-
1697
- ⚡ Methodology:
1698
- {state.get("methodology")}
1699
-
1700
- 🚀 Future Work:
1701
- {chr(10).join("- " + x for x in state.get("future_work", []))}
1702
-
1703
- 📂 Category:
1704
- {state.get("category")}
1705
-
1706
- 🏷 Keywords:
1707
- {", ".join(state.get("keywords", []))}
1708
-
1709
  ❗ Problem Statement:
1710
  {state.get("problem_statement")}
1711
 
1712
  💡 Proposed Solution:
1713
  {state.get("proposed_solution")}
1714
 
1715
- 🤖 AI Summary:
1716
- {state.get("ai_summary")}
 
 
 
 
1717
  """
1718
 
1719
  return finalize_response(
@@ -1755,20 +2174,7 @@ Choose what you want:
1755
  ):
1756
 
1757
  return (
1758
- "⚠️ This seems unrelated to the current project.\n\n"
1759
- f"Current project:\n"
1760
- f"{state.get('project_title')}\n\n"
1761
- "Do you want to:\n"
1762
- "1️⃣ Start a new project\n"
1763
- "2️⃣ Continue discussing the current project"
1764
- )
1765
-
1766
- system_prompt = build_chat_prompt(state)
1767
-
1768
- formatted_history = "\n".join([
1769
- f"{m['role']}: {m['content']}"
1770
- for m in history[-6:]
1771
- ])
1772
 
1773
  full_prompt = f"""
1774
  {system_prompt}
@@ -1803,12 +2209,12 @@ User:
1803
  if not is_project_related(text):
1804
 
1805
  return (
1806
- "⚠️ This chatbot is specialized for graduation projects only.\n\n"
1807
- "Try things like:\n"
1808
- "- generate AI project ideas\n"
1809
- "- generate features for smart hospital system\n"
1810
- "- suggest technologies for fintech app\n"
1811
- "- improve my graduation project"
1812
  )
1813
 
1814
 
 
26
 
27
  import re
28
 
29
+ # ─────────────────────────────────────────────
30
+ # Project Idea Validator + Categorizer
31
+ # ─────────────────────────────────────────────
32
+ def validate_and_categorize_project(title: str, abstract: str = "") -> dict:
33
+ """
34
+ Uses Gemini to:
35
+ 1. Verify whether the title is a valid graduation project idea.
36
+ 2. Assign it to the best-matching domain from the known list.
37
+
38
+ Returns:
39
+ {
40
+ "is_valid": bool,
41
+ "domain": str | None,
42
+ "reason": str
43
+ }
44
+ """
45
+ known_domains = [d for d in DOMAIN_KEYWORDS.keys() if d != "Others"]
46
+ domain_list_str = "\n".join(f"- {d}" for d in known_domains)
47
+
48
+ prompt = f"""
49
+ You are an expert academic advisor evaluating graduation project ideas.
50
+
51
+ Project Title: "{title}"
52
+ {"Abstract: " + abstract[:400] if abstract else ""}
53
+
54
+ Task 1 – Validity Check:
55
+ Is this a valid, feasible graduation project idea for a university student?
56
+ - It must be a technical or academic topic (not a random phrase, celebrity name, or nonsense)
57
+ - It should be specific enough to build something real
58
+ Answer: YES or NO
59
+
60
+ Task 2 – Domain Classification:
61
+ If valid, which ONE of the following domains best fits this project?
62
+ {domain_list_str}
63
+
64
+ Return your answer in this EXACT format (two lines only):
65
+ VALID: YES
66
+ DOMAIN: <domain name from the list above>
67
+
68
+ If invalid:
69
+ VALID: NO
70
+ DOMAIN: None
71
+ REASON: <one sentence why>
72
+ """
73
+ try:
74
+ raw = generate_text(prompt, task="intent").strip()
75
+ lines = {line.split(":", 1)[0].strip().upper(): line.split(":", 1)[1].strip()
76
+ for line in raw.splitlines() if ":" in line}
77
+
78
+ is_valid = lines.get("VALID", "NO").upper() == "YES"
79
+ domain = lines.get("DOMAIN", "").strip()
80
+ reason = lines.get("REASON", "")
81
+
82
+ if domain == "None" or domain not in known_domains:
83
+ domain = None
84
+ return {"is_valid": is_valid, "domain": domain, "reason": reason}
85
+
86
+ except Exception:
87
+ return {"is_valid": True, "domain": None, "reason": ""}
88
+
89
+
90
  def extract_number(text: str, default=5):
91
 
92
  nums = re.findall(r"\d+", text)
93
 
94
  return min(int(nums[0]), 20) if nums else default
95
 
96
+ def classify_input_context(user_input: str) -> str:
97
+ prompt = f"""
98
+ You are an advanced AI assistant classifying user input for a graduation project recommendation engine.
99
+ Analyze the user's input and classify its intent and context into exactly ONE of the following categories:
100
+
101
+ 1. greeting: A greeting like "hello", "hi", "hey".
102
+ 2. confirmation_yes: A positive response/agreement like "yes", "yeah", "yep", "sure".
103
+ 3. confirmation_no: A negative response/disagreement like "no", "nope", "not really", "la".
104
+ 4. idea_request: Asking to suggest, recommend, or generate project ideas (e.g., "give me AI ideas", "ideas for healthcare").
105
+ 5. feature_request: Asking to generate or suggest features for a project (e.g., "generate features", "suggest features").
106
+ 6. full_project_request: Asking to generate a full project specification or complete project (e.g., "generate full project", "complete project").
107
+ 7. project_title: Providing a potential graduation project title to start or evaluate (e.g., "Smart hospital system", "AI-based library portal").
108
+ 8. general_chat: General questions, questions about coding, concepts, general knowledge, or any explanation requests that are NOT a project title (e.g., "what is front end", "what is graduation project", "explain blockchain", "tell me about world cup", "i want know what is graduation project").
109
+
110
+ User input: "{user_input}"
111
+
112
+ Return ONLY the classification name as a single word from: greeting, confirmation_yes, confirmation_no, idea_request, feature_request, full_project_request, project_title, general_chat.
113
+ Do not include any other text or punctuation.
114
+ """
115
+ try:
116
+ res = generate_text(prompt, task="intent").strip().lower()
117
+ res = res.split()[0].strip().replace(".", "").replace(",", "")
118
+ return res
119
+ except Exception as e:
120
+ return "general_chat"
121
+
122
+ def is_valid_graduation_project_domain(domain: str) -> bool:
123
+ # 1. Quick local validation for standard domains
124
+ extracted = extract_domain(domain)
125
+ if extracted and extracted.lower() != "others":
126
+ return True
127
+
128
+ # 2. Fall back to LLM validation
129
+ prompt = f"""
130
+ Determine if the following domain/field is a valid academic, engineering, scientific, or technology domain suitable for a university graduation project (e.g., Computer Science, Engineering, Medicine, Business, Agriculture, Biology, etc.).
131
+
132
+ Domain to evaluate: "{domain}"
133
+
134
+ Rules:
135
+ - If it is a valid field of study, technology, or academic discipline (e.g., "artificial intelligence", "robotics", "bioinformatics", "educational games") -> Return YES
136
+ - If it is unrelated to academic/technology graduation projects, or contains names of celebrities, sports teams, food, pop culture, or random questions (e.g., "messi", "fc barcelona", "pizza", "what is this") -> Return NO
137
+
138
+ Return ONLY "YES" or "NO".
139
+ """
140
+ try:
141
+ res = generate_text(prompt, task="intent").strip().upper()
142
+ if not res:
143
+ return True
144
+ return "YES" in res
145
+ except Exception:
146
+ return True
147
+
148
  def is_weak_project_title(title: str) -> bool:
149
 
150
  if not title:
 
230
 
231
  lowered = title.lower()
232
 
 
 
 
233
  question_starts = (
234
  "how ",
235
  "what ",
 
245
  "does "
246
  )
247
 
248
+ for qs in question_starts:
249
+ if lowered.startswith(qs):
250
+ return False
251
 
252
  for p in nonsense_patterns:
253
  if p in lowered:
 
300
  "system",
301
  "platform",
302
  "application",
303
+ "app",
304
+ "website",
305
+ "portal",
306
+ "tool",
307
+ "game",
308
+ "generator",
309
+ "engine",
310
+ "software",
311
+ "database",
312
+ "model",
313
+ "chatbot",
314
+ "chat",
315
+ "assistant",
316
+ "network",
317
+ "api",
318
+ "mobile",
319
+ "web",
320
+ "smart"
321
  }
322
 
323
  if not any(
 
387
  "ux",
388
  "vr",
389
  "ar",
390
+ "iot",
391
+ "no",
392
+ "la",
393
+ "n",
394
+ "y",
395
+ "ok"
396
  }
397
 
398
  if text in allowed_short:
 
486
  for keyword in keywords
487
  )
488
 
489
+ def is_general_question_or_unrelated_chat(text: str) -> bool:
490
+ lowered = text.strip().lower()
491
+
492
+ # Ends with question mark
493
+ if lowered.endswith("?"):
494
+ return True
495
+
496
+ # Starts with common question words
497
+ question_starts = (
498
+ "how ", "what ", "why ", "when ", "where ", "can ", "could ", "should ",
499
+ "is ", "are ", "do ", "does ", "explain ", "tell me ", "show me ", "describe "
500
+ )
501
+ if lowered.startswith(question_starts):
502
+ return True
503
+
504
+ # Contains common question phrases
505
+ question_phrases = (
506
+ "what is", "what's", "tell me about", "can you", "could you", "how to", "how do"
507
+ )
508
+ if any(phrase in lowered for phrase in question_phrases):
509
+ return True
510
+
511
+ return False
512
+
513
  def chatbot(user_id: str, user_input: str):
514
 
515
  text = user_input.lower().strip()
 
532
  "ideas"
533
  ]
534
 
535
+ if any(x in text for x in explicit_idea_requests) or text in ["change domain", "new domain", "reset domain", "reset", "restart"]:
536
+ memory = get_user_memory(user_id)
537
+ current_state = memory.get("state") or default_state()
538
+
539
+ if not current_state.get("waiting_for_niche_domain"):
540
+ state = default_state()
541
+ state["waiting_for_domain"] = True
542
 
543
  save_user_memory(user_id, {
544
  "history": get_user_memory(user_id).get("history", []),
 
547
 
548
  domain_list = "\n".join(f"- {d}" for d in DOMAIN_KEYWORDS.keys() if d != "Others")
549
  return (
550
+ f"Which domain is your project in? 📚\n\n"
551
  f"{domain_list}\n\n"
552
+ f"💡 Just type one of the domains above (e.g. **AI** or **Healthcare**)\n"
553
+ f"If your domain isn't listed, type **Others** to see more options."
554
  )
555
 
556
 
 
564
  if is_gibberish_text(text):
565
 
566
  return (
567
+ "Hmm, I didn't quite catch that 🤔\n\n"
568
+ "Here are some things you can try:\n"
569
+ "- Type **idea** → get graduation project ideas\n"
570
+ "- Type your project title get smart features\n"
571
+ "- Type **generate full project** → complete project spec\n\n"
572
+ "💡 Tip: Start with **idea** if you're not sure!"
573
  )
574
 
575
 
 
586
 
587
  state = memory.get("state") or default_state()
588
 
589
+ # Clear weak_title_candidate if user is submitting a new input
590
+ if state.get("weak_title_candidate"):
591
+ state["weak_title_candidate"] = None
592
+
593
+ words = text.split()
594
+ INAPPROPRIATE_KEYWORDS_QUICK = {
595
+ "fuck", "shit", "bitch", "ass", "dick", "cock", "pussy",
596
+ "porn", "sex", "naked", "nude", "rape", "kill", "murder",
597
+ "bastard", "whore", "slut", "cunt", "nigga", "nigger"
598
+ }
599
+ if any(w.lower() in INAPPROPRIATE_KEYWORDS_QUICK for w in words):
600
+ return (
601
+ "I can only help with graduation project topics 🎓\n\n"
602
+ "Please keep it academic! Type **idea** to get started."
603
+ )
604
+
605
+ # Early rejection of unrelated single-word inputs (like 'dia', 'diea') when not in an active flow
606
+ is_in_active_flow = (
607
+ state.get("waiting_for_domain") or
608
+ state.get("waiting_for_niche_domain") or
609
+ state.get("waiting_for_full_project_domain") or
610
+ state.get("waiting_for_full_project_selection") or
611
+ state.get("waiting_for_feature_title") or
612
+ state.get("waiting_for_title_confirmation") or
613
+ state.get("waiting_for_project_idea_confirm") or
614
+ state.get("weak_title_candidate")
615
+ )
616
+
617
+ if not is_in_active_flow:
618
+ if len(words) == 1:
619
+ allowed_single_words = {
620
+ "hi", "hello", "hey", "welcome",
621
+ "yes", "no", "y", "n", "ok", "yeah", "yep", "nope", "sure", "la",
622
+ "idea", "ideas", "suggest", "generate", "others", "other",
623
+ "exit", "quit", "clear", "reset", "restart", "change", "new", "discuss", "features", "feature", "chat",
624
+ "1", "2", "3"
625
+ }
626
+ for d, d_words in DOMAIN_KEYWORDS.items():
627
+ allowed_single_words.add(d.lower())
628
+ for w in d_words:
629
+ allowed_single_words.update(w.lower().split())
630
+
631
+ if words[0] not in allowed_single_words:
632
+ word = words[0]
633
+ # Otherwise treat it as a short/weak project title and guide the user
634
+ state["weak_title_candidate"] = user_input.strip()
635
+ save_user_memory(user_id, {
636
+ "history": get_user_memory(user_id).get("history", []),
637
+ "state": state
638
+ })
639
+ return (
640
+ "📝 Your project title seems too short!\n\n"
641
+ "A good graduation project title should be descriptive. For example:\n"
642
+ "- AI-powered smart library recommendation system\n"
643
+ "- Smart hospital emergency response platform\n"
644
+ "- Blockchain-secured academic certificate verification\n\n"
645
+ "Please enter a more descriptive title.\n\n"
646
+ "💡 Or type **idea** to browse ready-made project ideas!"
647
+ )
648
+
649
+ state.setdefault("waiting_for_title_confirmation", False)
650
+ state.setdefault("pending_title", None)
651
+
652
+ context = classify_input_context(user_input)
653
+
654
+ # Intercept general questions / unrelated chat if no project is active yet
655
+ is_in_domain_or_selection_state = (
656
+ state.get("waiting_for_domain") or
657
+ state.get("waiting_for_niche_domain") or
658
+ state.get("waiting_for_full_project_domain") or
659
+ state.get("waiting_for_full_project_selection") or
660
+ state.get("waiting_for_feature_title") or
661
+ state.get("weak_title_candidate")
662
+ )
663
+
664
+ if not state.get("project_title") and not state.get("waiting_for_title_confirmation"):
665
+ should_intercept = False
666
+ if is_general_question_or_unrelated_chat(user_input):
667
+ should_intercept = True
668
+ elif context == "general_chat" and not is_in_domain_or_selection_state:
669
+ should_intercept = True
670
+
671
+ if should_intercept:
672
+ if context not in ["greeting", "confirmation_yes", "confirmation_no", "idea_request", "feature_request", "full_project_request"]:
673
+ # ── Content Safety Pre-Filter ──────────────────────────────────
674
+ # Reject clearly inappropriate/vulgar inputs before asking confirmation
675
+ INAPPROPRIATE_KEYWORDS = {
676
+ "fuck", "shit", "bitch", "ass", "dick", "cock", "pussy",
677
+ "porn", "sex", "naked", "nude", "rape", "kill", "murder",
678
+ "bastard", "whore", "slut", "cunt", "nigga", "nigger",
679
+ "idiot", "stupid", "dumb", "hate"
680
+ }
681
+ input_words = set(user_input.lower().split())
682
+ if input_words & INAPPROPRIATE_KEYWORDS:
683
+ save_user_memory(user_id, {
684
+ "history": history,
685
+ "state": state
686
+ })
687
+ return (
688
+ "I can only help with graduation project topics 🎓\n\n"
689
+ "Please keep it academic! Try:\n"
690
+ "- Type **idea** to browse project ideas\n"
691
+ "- Type a real project title to get features"
692
+ )
693
+ # ──────────────────────────────────────────────────────────────
694
+ state["waiting_for_title_confirmation"] = True
695
+ state["pending_title"] = user_input.strip()
696
+ save_user_memory(user_id, {
697
+ "history": history,
698
+ "state": state
699
+ })
700
+ return (
701
+ f"Just to confirm — is this your graduation project title?\n\n"
702
+ f"📝 \"{user_input.strip()}\"\n\n"
703
+ "Reply **yes** to continue with it, or **no** to go back."
704
+ )
705
+
706
+ YES_WORDS = {"yes", "yep", "yeah", "sure", "y", "ok", "okay", "correct", "right", "yup", "of course", "absolutely"}
707
+ NO_WORDS = {"no", "nope", "n", "nah", "not really", "la", "negative"}
708
+
709
+ # ── Handle: User confirms the generated project idea ──────────────────────
710
+ if state.get("waiting_for_project_idea_confirm"):
711
+ raw_lower = user_input.strip().lower()
712
+ is_yes = context == "confirmation_yes" or raw_lower in YES_WORDS
713
+ is_no = context == "confirmation_no" or raw_lower in NO_WORDS
714
+
715
+ state["waiting_for_project_idea_confirm"] = False
716
+
717
+ if is_yes:
718
+ title = state.get("project_title", "")
719
+ abstract = state.get("abstract", "")
720
+
721
+ save_user_memory(user_id, {"history": history, "state": state})
722
+
723
+ validation = validate_and_categorize_project(title, abstract)
724
+
725
+ if validation["is_valid"]:
726
+ domain = validation.get("domain")
727
+ if domain:
728
+ state["domain"] = domain
729
+ state["category"] = domain
730
+ domain_line = f"📂 Domain: **{domain}**" if domain else "📂 Domain: (could not classify — please specify)"
731
+ save_user_memory(user_id, {"history": history, "state": state})
732
+ return (
733
+ f"✅ Great choice! Your project idea has been validated and categorized.\n\n"
734
+ f"📌 Title: {title}\n"
735
+ f"{domain_line}\n\n"
736
+ f"What would you like to do next?\n"
737
+ f"1️⃣ Generate features\n"
738
+ f"2️⃣ Talk with chatbot about the idea\n\n"
739
+ f"👉 You can also say:\n"
740
+ f"- generate features\n"
741
+ f"- discuss project"
742
+ )
743
+ else:
744
+ reason = validation.get("reason", "")
745
+ save_user_memory(user_id, {"history": history, "state": state})
746
+ return (
747
+ f"❌ Hmm, this doesn't seem like a valid graduation project idea.\n\n"
748
+ f"{'Reason: ' + reason + chr(10) + chr(10) if reason else ''}"
749
+ f"💡 Try typing **idea** to browse real project ideas, or enter a proper project title."
750
+ )
751
+
752
+ elif is_no:
753
+ save_user_memory(user_id, {"history": history, "state": state})
754
+ return (
755
+ "No problem! 😊\n\n"
756
+ "Type **idea** to start fresh with new project ideas, or paste a different project title anytime."
757
+ )
758
+
759
+ else:
760
+ # Re-ask
761
+ state["waiting_for_project_idea_confirm"] = True
762
+ save_user_memory(user_id, {"history": history, "state": state})
763
+ return (
764
+ f"Just checking — would you like to use this as your graduation project idea?\n\n"
765
+ f"📌 \"{state.get('project_title')}\"\n\n"
766
+ "Reply **yes** to confirm and categorize it, or **no** to try something else."
767
+ )
768
+
769
+ if state.get("waiting_for_title_confirmation"):
770
+ raw_lower = user_input.strip().lower()
771
+
772
+ # Reliable raw-text check first — don't fully trust LLM classification for yes/no
773
+ is_yes = context == "confirmation_yes" or raw_lower in YES_WORDS
774
+ is_no = context == "confirmation_no" or raw_lower in NO_WORDS
775
+
776
+ if is_yes:
777
+ project_title = state.get("pending_title", "")
778
+ state["waiting_for_title_confirmation"] = False
779
+ state["pending_title"] = None
780
+
781
+ # Validate the title is actually a real graduation project idea
782
+ validation = validate_and_categorize_project(project_title)
783
+
784
+ if not validation["is_valid"]:
785
+ reason = validation.get("reason", "")
786
+ state["waiting_for_title_confirmation"] = False
787
+ save_user_memory(user_id, {
788
+ "history": history,
789
+ "state": state
790
+ })
791
+ return (
792
+ f"❌ That doesn't look like a valid graduation project title.\n\n"
793
+ f"{'Reason: ' + reason + chr(10) + chr(10) if reason else ''}"
794
+ f"💡 Try typing **idea** to browse real project ideas, or enter a proper project title like:\n"
795
+ f" AI-powered smart library recommendation system"
796
+ )
797
+
798
+ domain = validation.get("domain")
799
+ state = default_state()
800
+ state["project_title"] = project_title
801
+ state["waiting_for_project_action"] = True
802
+ if domain:
803
+ state["domain"] = domain
804
+ state["category"] = domain
805
+
806
+ save_user_memory(user_id, {
807
+ "history": history,
808
+ "state": state
809
+ })
810
+
811
+ domain_line = f"\n📂 Categorized under: **{domain}**" if domain else ""
812
+ return (
813
+ f"📌 Project confirmed!{domain_line}\n\n"
814
+ f"{project_title}\n\n"
815
+ "Choose what you want next:\n"
816
+ "1️⃣ Generate features\n"
817
+ "2️⃣ Talk with chatbot about the idea\n"
818
+ "3️⃣ Generate full project\n\n"
819
+ "👉 You can also say:\n"
820
+ "- generate features\n"
821
+ "- discuss project\n"
822
+ "- generate full project"
823
+ )
824
+
825
+ elif is_no:
826
+ state["waiting_for_title_confirmation"] = False
827
+ state["pending_title"] = None
828
+ save_user_memory(user_id, {
829
+ "history": history,
830
+ "state": state
831
+ })
832
+ return (
833
+ "No problem! 😊\n\n"
834
+ "I'm here to help with graduation projects. Try:\n"
835
+ "- Type **idea** to get project ideas\n"
836
+ "- Paste your project title to get features\n"
837
+ "- Type **generate full project** for a complete spec"
838
+ )
839
+
840
+ elif context == "greeting":
841
+ state["waiting_for_title_confirmation"] = False
842
+ state["pending_title"] = None
843
+
844
+ else:
845
+ # Still waiting — re-ask clearly
846
+ pending = state.get("pending_title", "")
847
+ save_user_memory(user_id, {
848
+ "history": history,
849
+ "state": state
850
+ })
851
+ return (
852
+ f"Just to confirm — is this your graduation project title?\n\n"
853
+ f"📝 \"{pending}\"\n\n"
854
+ "Reply **yes** to continue with it, or **no** to go back."
855
+ )
856
+
857
 
858
  state.setdefault("menu_mode", False)
859
  state.setdefault("selected_option", None)
 
879
  })
880
 
881
  return (
882
+ "👋 Welcome! I'm your Graduation Project Assistant 🎓\n\n"
883
+ "Here's how to get started:\n"
884
+ "1️⃣ Type **idea** → I'll suggest graduation project ideas by domain\n"
885
+ "2️⃣ Type your project title → I'll generate smart features for it\n"
886
+ "3️⃣ Type **generate full project** → get a complete project specification\n\n"
887
+ " Not sure? Just type **idea** and I'll guide you step by step!"
 
 
 
 
 
 
888
  )
889
 
890
 
 
943
  result,
944
  mode="merge"
945
  )
946
+ if state.get("domain"):
947
+ state["category"] = state.get("domain")
948
+
949
+ state["waiting_for_project_idea_confirm"] = True
950
 
951
  response = f"""
952
  📦 Full Project Generated
953
 
954
+ 📌 Project Title:
955
  {state.get("project_title")}
956
 
957
+ 📂 Category:
958
+ {state.get("category")}
 
 
 
 
 
 
959
 
960
  🛠 Technologies:
961
+ {", ".join(state.get("technologies", []))}
 
 
 
 
 
 
962
 
963
+ 📄 Abstract:
964
+ {state.get("abstract")}
 
 
 
965
 
966
+ 📄 Detailed Description:
967
+ {state.get("description")}
968
 
969
  ❗ Problem Statement:
970
  {state.get("problem_statement")}
 
972
  💡 Proposed Solution:
973
  {state.get("proposed_solution")}
974
 
975
+ 🎯 Objectives:
976
+ {chr(10).join("- " + x for x in state.get("objectives", []))}
977
+
978
+ ──────────────────────
979
+ 📍 Would you like to use this as your graduation project idea?
980
+ Reply **yes** to validate & categorize it, or **no** to explore other ideas.
981
+ """
982
 
983
  return finalize_response(
984
  user_input,
 
999
  })
1000
 
1001
  return (
1002
+ "🎯 What domain should the full project be in?\n\n"
1003
+ "Just type one, for example:\n"
1004
  "- AI\n"
1005
+ "- Healthcare\n"
1006
+ "- Education\n"
1007
+ "- Fintech\n"
1008
+ "- Cybersecurity"
1009
  )
1010
 
1011
 
 
1028
 
1029
  domain_list = "\n".join(f"- {d}" for d in DOMAIN_KEYWORDS.keys() if d != "Others")
1030
  return (
1031
+ f"Which domain is your project in? 📚\n\n"
1032
  f"{domain_list}\n\n"
1033
+ f"💡 Just type one of the domains above (e.g. **AI** or **Healthcare**)\n"
1034
+ f"If your domain isn't listed, type **Others** to see more options."
1035
  )
1036
 
1037
  elif text == "2":
 
1046
  })
1047
 
1048
  return (
1049
+ "I need a project title first! 📝\n\n"
1050
+ "Type your project title and I'll generate features for it.\n"
1051
  "Example:\n"
1052
+ " AI-powered smart library recommendation system\n\n"
1053
+ "💡 Or type **idea** if you'd like me to suggest one!"
1054
  )
1055
 
1056
  save_user_memory(user_id, {
 
1145
  })
1146
 
1147
  return (
1148
+ f"💬 Let's talk about your project!\n\n"
1149
+ f"📌 {state.get('project_title')}\n\n"
1150
+ "Ask me anything how to improve it, what technologies to use,\n"
1151
+ "or any questions about the idea. I'm here to help! 🚀"
1152
  )
1153
 
1154
 
 
1180
  })
1181
 
1182
  return (
1183
+ f"💬 Let's talk about your project!\n\n"
1184
+ f"📌 {state.get('project_title')}\n\n"
1185
+ "Ask me anything how to improve it, what technologies to use,\n"
1186
+ "or any questions about the idea. I'm here to help! 🚀"
1187
  )
1188
 
1189
 
 
1231
  result,
1232
  mode="merge"
1233
  )
1234
+ if state.get("domain"):
1235
+ state["category"] = state.get("domain")
1236
+
1237
+ state["waiting_for_project_idea_confirm"] = True
1238
 
1239
  response = f"""
1240
  📦 Full Project Generated
1241
 
1242
+ 📌 Project Title:
1243
  {state.get("project_title")}
1244
 
1245
+ 📂 Category:
1246
+ {state.get("category")}
 
 
 
 
 
 
1247
 
1248
  🛠 Technologies:
1249
+ {", ".join(state.get("technologies", []))}
 
 
 
 
 
 
1250
 
1251
+ 📄 Abstract:
1252
+ {state.get("abstract")}
 
 
 
1253
 
1254
+ 📄 Detailed Description:
1255
+ {state.get("description")}
1256
 
1257
  ❗ Problem Statement:
1258
  {state.get("problem_statement")}
 
1260
  💡 Proposed Solution:
1261
  {state.get("proposed_solution")}
1262
 
1263
+ 🎯 Objectives:
1264
+ {chr(10).join("- " + x for x in state.get("objectives", []))}
1265
+
1266
+ ──────────────────────
1267
+ 📍 Would you like to use this as your graduation project idea?
1268
+ Reply **yes** to validate & categorize it, or **no** to explore other ideas.
1269
  """
1270
 
1271
  return finalize_response(
 
1279
 
1280
 
1281
 
1282
+ # (Removed previous weak_title_candidate selection block as option 1 / improve flow was removed)
 
 
 
 
 
 
 
 
 
 
1283
 
1284
+
1285
+
1286
+
1287
+ if state.get("waiting_for_feature_title"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1288
 
1289
+ state["waiting_for_feature_title"] = False
 
 
 
1290
 
1291
+ possible_title = user_input.strip()
 
 
1292
 
1293
+ if not looks_like_real_project_title(possible_title):
1294
+ state["waiting_for_title_confirmation"] = True
1295
+ state["pending_title"] = possible_title
1296
  save_user_memory(user_id, {
1297
  "history": history,
1298
  "state": state
1299
  })
 
1300
  return (
1301
+ f"Just to confirm — is this your graduation project title?\n\n"
1302
+ f"📝 \"{possible_title}\"\n\n"
1303
+ "Reply **yes** to continue with it, or **no** to type a different title."
 
 
 
 
 
1304
  )
 
1305
 
 
 
 
 
 
 
 
 
 
 
1306
  if is_weak_project_title(possible_title):
1307
 
1308
  state["weak_title_candidate"] = possible_title
 
1317
  "Examples:\n"
1318
  "- AI-powered smart library recommendation system\n"
1319
  "- Smart hospital emergency response platform\n\n"
1320
+ "Please enter a more descriptive title."
 
 
1321
  )
1322
 
1323
 
 
1383
 
1384
  if text.startswith(pattern + " "):
1385
 
1386
+ extracted_title = user_input.split(
1387
+ pattern, 1
1388
+ )[1].strip()
1389
 
1390
  if (
1391
  extracted_title
 
1603
 
1604
  if state.get("waiting_for_niche_domain"):
1605
  text_lower = user_input.strip().lower()
1606
+ general_keywords = {"general", "general domain", "other", "others", "random", "anything", "whatever", "surprise me", "any", "mixed"}
1607
 
1608
+ if text_lower in general_keywords:
1609
+ state["waiting_for_domain"] = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1610
  state["waiting_for_niche_domain"] = False
1611
  state["suggested_domains"] = []
1612
+ analysis["domain"] = "general"
1613
  analysis["intent"] = "idea"
1614
  if "project_title" in analysis:
1615
  del analysis["project_title"]
1616
+ else:
1617
+ more_words = ["more", "another", "different", "none", "generate more"]
1618
+ if any(w in text_lower for w in more_words) and len(text_lower.split()) < 5:
1619
+ previous_domains = state.get("suggested_domains", [])
1620
+ niche_domains = generate_list(build_niche_domains_prompt(previous_domains), task="idea")
1621
+
1622
+ state["suggested_domains"] = previous_domains + niche_domains
1623
+
1624
+ domain_list_str = "\n".join(f"- {d}" for d in niche_domains)
1625
+ response = (
1626
+ f"Here are some MORE niche and cutting-edge domain ideas:\n\n"
1627
+ f"{domain_list_str}\n\n"
1628
+ f"Which one would you like to explore? (Or type your own!)"
1629
+ )
1630
+ save_user_memory(user_id, {"history": history, "state": state})
1631
+ return finalize_response(user_input, response, history, state, user_id)
1632
+ else:
1633
+ domain = user_input.strip()
1634
+ if not is_valid_graduation_project_domain(domain):
1635
+ return (
1636
+ "⚠️ That does not seem to be a valid technology or academic domain.\n\n"
1637
+ "Please enter a valid domain (e.g. AI, Healthcare, Fintech, or 'Others')."
1638
+ )
1639
+ state["waiting_for_niche_domain"] = False
1640
+ state["suggested_domains"] = []
1641
+ analysis["domain"] = domain
1642
+ analysis["intent"] = "idea"
1643
+ if "project_title" in analysis:
1644
+ del analysis["project_title"]
1645
 
1646
  elif state.get("waiting_for_domain"):
1647
+ text_lower = user_input.strip().lower()
1648
+ general_keywords = {"general", "general domain", "random", "anything", "whatever", "surprise me", "any", "mixed"}
1649
+
1650
+ if text_lower in general_keywords:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1651
  state["waiting_for_domain"] = False
1652
+ state["waiting_for_niche_domain"] = False
1653
+ analysis["domain"] = "general"
1654
  analysis["intent"] = "idea"
1655
  if "project_title" in analysis:
1656
  del analysis["project_title"]
1657
  else:
1658
+ detected = extract_domain(user_input)
1659
+
1660
+ if detected.lower() == "others":
1661
+ state["waiting_for_domain"] = False
1662
+ state["waiting_for_niche_domain"] = True
1663
+
1664
+ niche_domains = generate_list(build_niche_domains_prompt([]), task="idea")
1665
+ state["suggested_domains"] = niche_domains
1666
+
1667
+ domain_list_str = "\n".join(f"- {d}" for d in niche_domains)
1668
+ response = (
1669
+ f"Great choice! Here are some cutting-edge niche domains to explore 🚀\n\n"
1670
+ f"{domain_list_str}\n\n"
1671
+ f"💡 Pick one from the list above, or type any domain you have in mind!"
1672
+ )
1673
+ save_user_memory(user_id, {"history": history, "state": state})
1674
+ return finalize_response(user_input, response, history, state, user_id)
1675
+
1676
+ elif detected:
1677
+ state["waiting_for_domain"] = False
1678
+ analysis["domain"] = detected
1679
+ analysis["intent"] = "idea"
1680
+ if "project_title" in analysis:
1681
+ del analysis["project_title"]
1682
+ else:
1683
+ return (
1684
+ "⚠️ Please enter a valid domain "
1685
+ "(AI, healthcare, fintech...)"
1686
+ )
1687
 
1688
  intent = analysis.get("intent", "chat")
1689
 
 
1691
 
1692
  user_project = analysis.get("project_title")
1693
 
1694
+ # Override intent using the LLM-derived semantic context when no project is active yet
1695
+ if not state.get("project_title") and not is_in_domain_or_selection_state:
1696
+ if context == "idea_request":
1697
+ intent = "idea"
1698
+ elif context == "feature_request":
1699
+ intent = "feature"
1700
+ elif context in ["general_chat", "project_title"]:
1701
+ intent = "chat"
1702
+ user_project = user_input.strip()
1703
+
1704
 
1705
 
1706
 
 
1768
 
1769
  if (
1770
  domain
1771
+ and domain.lower() != "others"
1772
  and intent == "chat"
1773
  and len(text.split()) <= 3
1774
  and not any(cmd in text for cmd in full_project_commands)
 
1852
 
1853
  if user_project:
1854
 
1855
+ if not looks_like_real_project_title(user_project):
1856
+ state["waiting_for_title_confirmation"] = True
1857
+ state["pending_title"] = user_project
1858
+ save_user_memory(user_id, {
1859
+ "history": history,
1860
+ "state": state
1861
+ })
1862
+ return "Is that a project title?"
1863
+
1864
  if is_weak_project_title(user_project):
1865
 
1866
  state["weak_title_candidate"] = user_project
 
1876
  "- AI-powered smart library recommendation system\n"
1877
  "- Smart hospital emergency response platform\n"
1878
  "- Blockchain-secured academic certificate verification system\n\n"
1879
+ "Please enter a more descriptive title."
 
 
 
 
1880
  )
1881
 
1882
  state = default_state()
 
1929
  ):
1930
 
1931
  return (
1932
+ "I need a project title to generate features! 📝\n\n"
1933
+ "Type your project title and I'll handle the rest.\n"
1934
  "Example:\n"
1935
+ " AI-powered smart library recommendation system\n\n"
1936
+ "💡 Don't have a title yet? Type **idea** to browse project ideas first!"
1937
  )
1938
 
1939
 
 
1951
 
1952
  domain_list = "\n".join(f"- {d}" for d in DOMAIN_KEYWORDS.keys() if d != "Others")
1953
  return (
1954
+ f"Which domain is your project in? 📚\n\n"
1955
  f"{domain_list}\n\n"
1956
+ f"💡 Just type one of the domains above (e.g. **AI** or **Healthcare**)\n"
1957
+ f"If your domain isn't listed, type **Others** to see more options."
1958
  )
1959
 
1960
  top_k = analysis.get("number") or extract_number(
 
1974
  state["domain"] = domain
1975
  state["last_action"] = "idea"
1976
 
1977
+ # Clear project-specific keys to switch back to ideas stage
1978
+ state["project_title"] = ""
1979
+ state["features"] = []
1980
+ state["description"] = ""
1981
+ state["abstract"] = ""
1982
+ state["technologies"] = []
1983
+ state["originality_score"] = None
1984
+ state["context_strength"] = None
1985
+ state["problem_statement"] = ""
1986
+ state["proposed_solution"] = ""
1987
+ state["keywords"] = []
1988
+ state["ai_summary"] = ""
1989
+ state["category"] = ""
1990
+
1991
  response = format_response("idea", "", state)
1992
 
1993
  return finalize_response(
 
2015
  if not state.get("project_title"):
2016
 
2017
  return (
2018
+ "I need a project title to generate features! 📝\n\n"
2019
+ "Just type your project title and I'll generate smart features for it.\n"
2020
+ "💡 No title yet? Type **idea** to get suggestions first!"
2021
+ )
2022
 
2023
  top_k = analysis.get("number") or extract_number(
2024
  user_input,
 
2076
  if not state.get("project_title"):
2077
 
2078
  return (
2079
+ "Let's get started! Please provide a project title or type **idea** to pick one first. 💡"
2080
  )
2081
 
2082
  if not state.get("features"):
2083
 
2084
  return (
2085
+ "Before generating the full project, I need to know what features it has. 📋\n\n"
2086
+ "Type **generate features** to create them quickly!"
2087
  )
2088
 
2089
  result = generate_full_project(
 
2102
  result,
2103
  mode="merge"
2104
  )
2105
+ if state.get("domain"):
2106
+ state["category"] = state.get("domain")
2107
+
2108
+ state["waiting_for_project_idea_confirm"] = True
2109
 
2110
  response = f"""
2111
+ 📦 Project Specification Ready!
2112
 
2113
+ 📌 Title: {state.get("project_title")}
2114
+ 📂 Category: {state.get("category")}
2115
+
2116
+ 🛠 Technologies: {", ".join(state.get("technologies", []))}
2117
 
2118
  📄 Abstract:
2119
  {state.get("abstract")}
 
2121
  📄 Description:
2122
  {state.get("description")}
2123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2124
  ❗ Problem Statement:
2125
  {state.get("problem_statement")}
2126
 
2127
  💡 Proposed Solution:
2128
  {state.get("proposed_solution")}
2129
 
2130
+ 🎯 Objectives:
2131
+ {chr(10).join("- " + x for x in state.get("objectives", []))}
2132
+
2133
+ ──────────────────────
2134
+ 📍 Would you like to use this as your graduation project idea?
2135
+ Reply **yes** to validate & categorize it, or **no** to explore other ideas.
2136
  """
2137
 
2138
  return finalize_response(
 
2174
  ):
2175
 
2176
  return (
2177
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
2178
 
2179
  full_prompt = f"""
2180
  {system_prompt}
 
2209
  if not is_project_related(text):
2210
 
2211
  return (
2212
+ "I'm designed specifically for graduation projects 🎓\n\n"
2213
+ "Here's what I can help with:\n"
2214
+ "- Type **idea** → browse project ideas by domain\n"
2215
+ "- Type your project title get smart features\n"
2216
+ "- Type **generate full project** → complete project specification\n\n"
2217
+ "💡 Start by typing **idea** — it's quick and easy!"
2218
  )
2219
 
2220
 
src/recommendation_engine/config.py CHANGED
@@ -13,13 +13,11 @@ DEBUG_MODE = os.getenv("DEBUG_MODE", "true").lower() == "true"
13
 
14
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
15
 
16
- # Primary model — reads GEMINI_MODEL_NAME from .env first, then PRIMARY_MODEL,
17
- # then falls back to gemini-2.5-pro (smart: advanced reasoning, high quality output).
18
  MODEL_CANDIDATES = [
19
- os.getenv("GEMINI_MODEL_NAME") or os.getenv("PRIMARY_MODEL", "gemini-2.5-pro"),
20
- os.getenv("FAST_MODEL", "gemini-2.5-flash"), # fast fallback
21
- os.getenv("BALANCED_MODEL", "gemini-2.5-flash"), # same tier fallback
22
- os.getenv("QUALITY_MODEL", "gemini-3.1-flash-lite"), # lightweight last resort
23
  ]
24
 
25
  _seen = set()
@@ -50,7 +48,7 @@ DEFAULT_IDEA_COUNT = int(os.getenv("DEFAULT_IDEA_COUNT", 5))
50
  GENERATION_BATCH_SIZE = int(os.getenv("GENERATION_BATCH_SIZE", 10))
51
 
52
  IDEA_DUPLICATE_THRESHOLD = float(
53
- os.getenv("IDEA_DUPLICATE_THRESHOLD", 0.15)
54
  )
55
 
56
  FEATURE_DUPLICATE_THRESHOLD = float(
 
13
 
14
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
15
 
 
 
16
  MODEL_CANDIDATES = [
17
+ os.getenv("PRIMARY_MODEL", "gemini-3.1-flash-lite-preview"),
18
+ os.getenv("FAST_MODEL", "gemini-2.5-flash-lite"),
19
+ os.getenv("BALANCED_MODEL", "gemini-2.5-flash"),
20
+ os.getenv("QUALITY_MODEL", "gemini-2.5-pro"),
21
  ]
22
 
23
  _seen = set()
 
48
  GENERATION_BATCH_SIZE = int(os.getenv("GENERATION_BATCH_SIZE", 10))
49
 
50
  IDEA_DUPLICATE_THRESHOLD = float(
51
+ os.getenv("IDEA_DUPLICATE_THRESHOLD", 0.45)
52
  )
53
 
54
  FEATURE_DUPLICATE_THRESHOLD = float(
src/recommendation_engine/context_builder.py CHANGED
@@ -142,6 +142,21 @@ def extract_domain(text: str) -> str:
142
  return "artificial intelligence"
143
 
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  if text in DOMAIN_KEYWORDS:
146
  return text
147
 
@@ -190,7 +205,11 @@ def extract_domain(text: str) -> str:
190
 
191
  return domain
192
 
193
- return "Others"
 
 
 
 
194
 
195
  @lru_cache(maxsize=100)
196
  def cached_similarity(
@@ -368,7 +387,7 @@ def build_project_context(
368
  "common_features": [],
369
  "unique_features": user_features,
370
  "architecture_hints": build_architecture_hints(domains),
371
- "originality_score": 1.0,
372
  "context_strength": 0.0
373
  }
374
 
@@ -387,13 +406,6 @@ def build_project_context(
387
  if f not in common_features
388
  ]
389
 
390
- originality = float(
391
- results.get(
392
- "originality_score",
393
- pd.Series([1])
394
- ).mean()
395
- )
396
-
397
  hybrid_scores = results.get(
398
  "hybrid_score",
399
  pd.Series([0])
@@ -412,17 +424,33 @@ def build_project_context(
412
  "common_features": common_features,
413
  "unique_features": unique_features,
414
  "architecture_hints": build_architecture_hints(domains),
415
- "originality_score": round(originality, 4),
416
  "context_strength": round(context_strength, 4)
417
  }
418
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  def build_domain_context(
420
  domain: str
421
  ) -> Dict[str, Any]:
422
 
423
  extracted = extract_domain(domain)
424
 
425
- if extracted and extracted != "Others":
426
  domain_clean = extracted
427
  else:
428
  logger.info(
 
142
  return "artificial intelligence"
143
 
144
 
145
+ # Map normalized domain names to their original keys
146
+ normalized_domains = {normalize(d): d for d in DOMAIN_KEYWORDS.keys()}
147
+ if text in normalized_domains:
148
+ return normalized_domains[text]
149
+
150
+ # Check close matches against normalized domain names
151
+ match_domain = get_close_matches(
152
+ text,
153
+ list(normalized_domains.keys()),
154
+ n=1,
155
+ cutoff=0.85
156
+ )
157
+ if match_domain:
158
+ return normalized_domains[match_domain[0]]
159
+
160
  if text in DOMAIN_KEYWORDS:
161
  return text
162
 
 
205
 
206
  return domain
207
 
208
+ others_keywords = DOMAIN_KEYWORDS.get("Others", [])
209
+ if any(ow in text for ow in others_keywords):
210
+ return "Others"
211
+
212
+ return ""
213
 
214
  @lru_cache(maxsize=100)
215
  def cached_similarity(
 
387
  "common_features": [],
388
  "unique_features": user_features,
389
  "architecture_hints": build_architecture_hints(domains),
390
+ "originality_score": 99.0,
391
  "context_strength": 0.0
392
  }
393
 
 
406
  if f not in common_features
407
  ]
408
 
 
 
 
 
 
 
 
409
  hybrid_scores = results.get(
410
  "hybrid_score",
411
  pd.Series([0])
 
424
  "common_features": common_features,
425
  "unique_features": unique_features,
426
  "architecture_hints": build_architecture_hints(domains),
427
+ "originality_score": calibrate_originality(context_strength),
428
  "context_strength": round(context_strength, 4)
429
  }
430
 
431
+ def calibrate_originality(similarity: float) -> float:
432
+ """
433
+ Piecewise linear calibration curve mapping database similarity to originality percentage.
434
+ - S <= 0.45: maps linearly to O in [85.0%, 99.0%]
435
+ - S > 0.45: maps linearly to O in [5.0%, 85.0%]
436
+ """
437
+ s = max(0.0, min(1.0, float(similarity)))
438
+ if s <= 0.45:
439
+ originality = 99.0 - (s / 0.45) * 14.0
440
+ else:
441
+ originality = 85.0 - ((s - 0.45) / 0.55) * 80.0
442
+ return round(originality, 2)
443
+
444
+
445
+
446
+
447
  def build_domain_context(
448
  domain: str
449
  ) -> Dict[str, Any]:
450
 
451
  extracted = extract_domain(domain)
452
 
453
+ if extracted and extracted.lower() != "others":
454
  domain_clean = extracted
455
  else:
456
  logger.info(
src/recommendation_engine/idea_generator.py CHANGED
@@ -28,6 +28,7 @@ def is_duplicate_local(idea: str, existing: Set[str]) -> bool:
28
  return False
29
 
30
  def fallback_by_domain(domain: str) -> List[str]:
 
31
 
32
  domain = (domain or "general").lower()
33
 
@@ -37,31 +38,91 @@ def fallback_by_domain(domain: str) -> List[str]:
37
  "Student performance prediction platform",
38
  "Gamified learning mobile application",
39
  "Automated grading system",
40
- "Virtual classroom engagement analyzer"
 
 
 
41
  ],
42
  "healthcare": [
43
  "AI disease prediction system",
44
  "Smart patient monitoring system",
45
  "Medical diagnosis assistant",
46
  "IoT health tracking device",
47
- "Hospital resource optimization system"
 
 
 
48
  ],
49
  "fintech": [
50
  "Fraud detection AI system",
51
  "Smart expense tracking app",
52
  "Blockchain payment system",
53
  "Credit risk prediction model",
54
- "AI investment advisor"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  ]
56
  }
57
 
58
- return fallback_map.get(domain, [
59
- "AI recommendation system",
60
- "Smart automation platform",
61
- "Data analytics dashboard",
62
- "Intelligent decision support system",
63
- "Predictive analytics engine"
64
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  def generate_ideas(
67
  domain: str,
@@ -172,7 +233,26 @@ def generate_ideas(
172
 
173
  if len(final_ideas) < top_k:
174
 
175
- logger.warning("Using fallback ideas")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  fallback = fallback_by_domain(domain)
178
 
 
28
  return False
29
 
30
  def fallback_by_domain(domain: str) -> List[str]:
31
+ import random
32
 
33
  domain = (domain or "general").lower()
34
 
 
38
  "Student performance prediction platform",
39
  "Gamified learning mobile application",
40
  "Automated grading system",
41
+ "Virtual classroom engagement analyzer",
42
+ "AI-powered language tutor for kids",
43
+ "Adaptive curriculum recommendation engine",
44
+ "Dyslexia assistant mobile application"
45
  ],
46
  "healthcare": [
47
  "AI disease prediction system",
48
  "Smart patient monitoring system",
49
  "Medical diagnosis assistant",
50
  "IoT health tracking device",
51
+ "Hospital resource optimization system",
52
+ "Wearable fall detection for elderly",
53
+ "Computer vision surgery tools tracker",
54
+ "Smart appointment booking and triage chat"
55
  ],
56
  "fintech": [
57
  "Fraud detection AI system",
58
  "Smart expense tracking app",
59
  "Blockchain payment system",
60
  "Credit risk prediction model",
61
+ "AI investment advisor",
62
+ "Automated personal budgeting assistant",
63
+ "Cryptocurrency portfolio analyzer",
64
+ "AI invoice processing portal"
65
+ ],
66
+ "general": [
67
+ "AI-powered agricultural crop disease detection mobile app",
68
+ "Blockchain-secured digital academic certificate verification portal",
69
+ "AR-based indoor navigation assistant for visually impaired students",
70
+ "Smart waste sorting bin using computer vision and mechanical sorting",
71
+ "Decentralized federated learning for collaborative medical image classification",
72
+ "Automated license plate recognition and parking management system",
73
+ "AI-driven smart library book recommendation portal",
74
+ "Smart home energy management and load prediction system",
75
+ "Real-time object detection for warehouse inventory tracking",
76
+ "Autonomous delivery robot path planning simulation"
77
  ]
78
  }
79
 
80
+ ideas = fallback_map.get(domain) or fallback_map.get("general")
81
+ shuffled_ideas = list(ideas)
82
+ random.shuffle(shuffled_ideas)
83
+ return shuffled_ideas
84
+
85
+ def dynamic_fallback_ideas(
86
+ domain: str,
87
+ count: int,
88
+ exclude_set: Set[str],
89
+ previous_ideas: List[str]
90
+ ) -> List[str]:
91
+ logger.info(f"Generating dynamic fallback ideas for domain={domain}")
92
+ prompt = f"""
93
+ You are an advanced AI research and innovation consultant.
94
+ The standard project generator has failed to find unique ideas for the selected domain because of strict originality rules.
95
+
96
+ DOMAIN:
97
+ {domain}
98
+
99
+ EXISTING CONTEXT:
100
+ Please suggest {count} highly creative, unique, yet practical graduation project ideas that do not exist in standard databases.
101
+ Avoid any standard topics (like simple prediction dashboards, basic chatbots, or generic automation).
102
+ Think of combining different domains to create implementable software/hardware systems (e.g., mobile health tracking + edge-inference, edge computing + precision agriculture, secure e-commerce + decentralized identity).
103
+ Ensure the ideas are completely feasible, realistic, and implementable within 6–9 months by a team of undergraduate students. Do NOT suggest highly theoretical, PhD-level, or impossible/sci-fi research topics (no quantum computing algorithms, non-existent sensors, etc.).
104
+
105
+ PREVIOUS IDEAS TO AVOID:
106
+ {", ".join(exclude_set | set(previous_ideas))}
107
+
108
+ FORMAT:
109
+ - One idea per line
110
+ - No numbering
111
+ - No explanations
112
+ - Keep them concise (4-12 words)
113
+ """
114
+ raw_text = generate_text(prompt, task="idea", temperature=0.95)
115
+ if not raw_text:
116
+ return []
117
+ generated = validate_generated_list(text=raw_text, top_k=count)
118
+ generated = clean_ideas(generated)
119
+
120
+ valid_fallbacks = []
121
+ for idea in generated:
122
+ if not is_idea_novel(idea):
123
+ continue
124
+ valid_fallbacks.append(idea)
125
+ return valid_fallbacks
126
 
127
  def generate_ideas(
128
  domain: str,
 
233
 
234
  if len(final_ideas) < top_k:
235
 
236
+ logger.warning("Using dynamic fallback ideas")
237
+ needed = top_k - len(final_ideas)
238
+ dynamic_fallback = dynamic_fallback_ideas(
239
+ domain=domain,
240
+ count=needed * 2,
241
+ exclude_set=final_set,
242
+ previous_ideas=previous_generated_ideas
243
+ )
244
+
245
+ for f in dynamic_fallback:
246
+ normalized = normalize_idea(f)
247
+ if normalized not in final_set:
248
+ final_ideas.append(f)
249
+ final_set.add(normalized)
250
+ if len(final_ideas) >= top_k:
251
+ break
252
+
253
+ if len(final_ideas) < top_k:
254
+
255
+ logger.warning("Using static fallback ideas")
256
 
257
  fallback = fallback_by_domain(domain)
258
 
src/recommendation_engine/llm_client.py CHANGED
@@ -180,6 +180,11 @@ def generate_text(
180
 
181
  provider_error = classify_provider_error(e)
182
  if provider_error:
 
 
 
 
 
183
  raise provider_error
184
 
185
 
 
180
 
181
  provider_error = classify_provider_error(e)
182
  if provider_error:
183
+ if provider_error.status_code == 429 and attempt < MAX_RETRIES - 1:
184
+ sleep_time = (RETRY_DELAY_SECONDS * 5) * (attempt + 1)
185
+ logger.info(f"[LLM 429] Rate limited. Retrying in {sleep_time}s...")
186
+ time.sleep(sleep_time)
187
+ continue
188
  raise provider_error
189
 
190
 
src/recommendation_engine/memory_store.py CHANGED
@@ -22,7 +22,22 @@ def default_state():
22
  "proposed_solution": "",
23
  "keywords": [],
24
  "ai_summary": "",
25
- "category": ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
 
28
  def create_chat(user_id: str):
@@ -79,9 +94,6 @@ def merge_state(old: dict, new: dict):
79
 
80
  for key, value in new.items():
81
 
82
- if value is None:
83
- continue
84
-
85
 
86
  if key in ["project_title", "description"]:
87
  merged[key] = value
 
22
  "proposed_solution": "",
23
  "keywords": [],
24
  "ai_summary": "",
25
+ "category": "",
26
+ "domain": None,
27
+ "last_action": None,
28
+ "waiting_for_domain": False,
29
+ "waiting_for_niche_domain": False,
30
+ "waiting_for_full_project_domain": False,
31
+ "waiting_for_full_project_selection": False,
32
+ "waiting_for_feature_title": False,
33
+ "waiting_for_title_confirmation": False,
34
+ "waiting_for_project_action": False,
35
+ "suggested_domains": [],
36
+ "pending_title": None,
37
+ "weak_title_candidate": None,
38
+ "project_chat_mode": False,
39
+ "menu_mode": False,
40
+ "selected_option": None
41
  }
42
 
43
  def create_chat(user_id: str):
 
94
 
95
  for key, value in new.items():
96
 
 
 
 
97
 
98
  if key in ["project_title", "description"]:
99
  merged[key] = value
src/recommendation_engine/prompt_builder.py CHANGED
@@ -126,32 +126,36 @@ def build_idea_prompt(
126
  previous_ideas = previous_ideas or []
127
 
128
  domain = context.get("domain", "general")
 
129
 
130
  return f"""
131
  You are a senior AI innovation consultant.
132
 
133
  TASK:
134
- Generate {count} HIGHLY ORIGINAL (85%+ Novelty) and unique graduation project ideas.
135
- The ideas MUST NOT be variants of existing common student projects.
136
 
137
  DOMAIN:
138
  {domain}
139
 
140
- PREVIOUS IDEAS:
 
 
 
141
  {list_to_text(previous_ideas, 20)}
142
 
143
  IMPORTANT REQUIREMENTS:
144
- - Ideas must possess HIGH ORIGINALITY. Do not suggest anything that resembles typical final-year student projects.
145
- - Ideas must solve REAL problems using cutting-edge, novel approaches.
146
- - Avoid generic software projects or standard web/mobile applications.
147
- - Ideas must be practical and implementable.
148
- - Prefer deep AI integrations (LLMs, Computer Vision, specialized ML models), advanced automation, or intelligent systems.
149
  - Each idea must represent a COMPLETELY DIFFERENT concept.
150
 
151
  STRICT RULES:
152
- - No repeated concepts or slight rewording.
153
  - Avoid overused ideas like: standard prediction models, generic recommendation engines, basic management dashboards, IoT plant watering, face recognition attendance, or smart traffic lights.
154
- - Push the boundaries of the selected domain to ensure maximum uniqueness.
155
 
156
  FORMAT RULES:
157
  - One idea per line
@@ -161,10 +165,11 @@ FORMAT RULES:
161
  - Prefer 4–12 words
162
 
163
  GOOD IDEA EXAMPLES:
164
- - Smart traffic congestion prediction using drone-swarm analytics
165
- - AI-powered emergency sign language translator with continuous learning
166
- - Blockchain-secured federated learning for medical image sharing
167
- - Adaptive AI-driven learning assistant for dyslexic students
 
168
 
169
  BAD IDEA EXAMPLES:
170
  - AI management system
 
126
  previous_ideas = previous_ideas or []
127
 
128
  domain = context.get("domain", "general")
129
+ existing_titles = context.get("existing_titles", [])
130
 
131
  return f"""
132
  You are a senior AI innovation consultant.
133
 
134
  TASK:
135
+ Generate {count} HIGHLY ORIGINAL (85% to 99% Originality) and unique graduation project ideas.
136
+ The ideas MUST NOT duplicate or be variants of existing database projects or typical student projects.
137
 
138
  DOMAIN:
139
  {domain}
140
 
141
+ EXISTING PROJECTS IN THE DATABASE (DO NOT REPEAT OR DUPLICATE THESE):
142
+ {list_to_text(existing_titles, 15)}
143
+
144
+ PREVIOUS IDEAS SUGGESTED IN THIS SESSION (DO NOT REPEAT THESE):
145
  {list_to_text(previous_ideas, 20)}
146
 
147
  IMPORTANT REQUIREMENTS:
148
+ - Originality & Feasibility Balance: Ideas must possess HIGH ORIGINALITY, but must be highly feasible and implementable. They should be realistic software or software-hardware projects that undergraduate students can build within 6–9 months.
149
+ - Avoid PhD-level, highly theoretical, or "impossible" sci-fi research topics (e.g. do NOT require quantum computing, non-existent sensors, or highly experimental brain-computer interfaces).
150
+ - Ideas must solve REAL problems using cutting-edge, yet practical and available technologies (such as standard web/mobile frameworks, existing ML models, APIs, and affordable IoT hardware).
151
+ - Avoid generic software projects or standard web/mobile applications that lack intelligence.
152
+ - Prefer smart AI integrations (LLMs, Computer Vision, specialized ML models), advanced automation, or intelligent systems.
153
  - Each idea must represent a COMPLETELY DIFFERENT concept.
154
 
155
  STRICT RULES:
156
+ - No repeated concepts or slight rewording of existing projects.
157
  - Avoid overused ideas like: standard prediction models, generic recommendation engines, basic management dashboards, IoT plant watering, face recognition attendance, or smart traffic lights.
158
+ - Push the boundaries of the selected domain to ensure maximum uniqueness without losing feasibility.
159
 
160
  FORMAT RULES:
161
  - One idea per line
 
165
  - Prefer 4–12 words
166
 
167
  GOOD IDEA EXAMPLES:
168
+ - AI-powered agricultural crop disease detection via mobile app and offline edge-inference
169
+ - Blockchain-secured digital academic certificate issuance and verification portal
170
+ - AR-based indoor navigation assistant for visually impaired university students
171
+ - Smart waste sorting bin using computer vision and mechanical sorting
172
+ - Decentralized federated learning for collaborative medical image classification
173
 
174
  BAD IDEA EXAMPLES:
175
  - AI management system
src/recommendation_engine/test.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from src.recommendation_engine.chatbot_engine import chatbot
3
+
4
+ USER_ID = "test_user"
5
+
6
+
7
+
8
+ def interactive():
9
+ print("\n===== INTERACTIVE MODE =====\n")
10
+
11
+ while True:
12
+ user_input = input("YOU: ")
13
+
14
+ if user_input.lower() in ["exit", "quit"]:
15
+ break
16
+
17
+ response = chatbot(USER_ID, user_input)
18
+
19
+ print("\nBOT:")
20
+ print(response)
21
+ print("\n----------------------\n")
22
+
23
+
24
+
25
+ interactive()