saim1309 commited on
Commit
eb656a6
·
verified ·
1 Parent(s): 3784225

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -53
app.py CHANGED
@@ -5,6 +5,7 @@ import re
5
  import os
6
  from datetime import datetime, timedelta
7
  import uuid
 
8
  from typing import Dict
9
 
10
  from config import (
@@ -17,15 +18,16 @@ from utils import (
17
  get_embedding, cosine_similarity, find_top_k_matches,
18
  classify_intent, should_include_email, classify_user_type
19
  )
20
- from scraper import scrape_workshops_from_squarespace
21
  from database import (
22
  fetch_all_embeddings,
23
  fetch_row_by_id,
24
  fetch_all_faq_embeddings,
25
  get_session_state,
26
  update_session_state,
27
- log_question
 
28
  )
 
29
 
30
  # ============================================================================
31
  # CONFIGURATION
@@ -37,8 +39,7 @@ if not OPENAI_API_KEY:
37
  openai.api_key = OPENAI_API_KEY
38
 
39
 
40
- # Store session ID for the conversation
41
- session_id = str(uuid.uuid4())
42
 
43
  # Cache for workshop data and embeddings
44
  workshop_cache = {
@@ -222,7 +223,7 @@ def generate_enriched_links(row):
222
  markdown = f"🎧 [Watch {guest_name}'s episode here]({base_url}) - {short_summary}"
223
  return [markdown]
224
 
225
- def build_enhanced_prompt(user_question, context_results, top_workshops, user_preference=None, user_type='unknown', enriched_podcast_links=None, wants_details=False, current_topic=None, mode="Mode B", is_low_confidence=False):
226
  """Builds the system prompt with strict formatting rules."""
227
 
228
  # Dynamic Links from Structured Knowledge
@@ -278,11 +279,13 @@ def build_enhanced_prompt(user_question, context_results, top_workshops, user_pr
278
  # Mandatory Hyperlink Enforcement
279
  workshop_text = f"We are constantly updating our schedule! You can view and [register for upcoming {label}workshops here]({link})."
280
 
281
- # Handle missing podcast data strictly
 
282
  if not enriched_podcast_links:
283
- single_podcast = "Our latest industry insights are available on YouTube: https://www.youtube.com/@GetSceneStudios"
284
  else:
285
- single_podcast = enriched_podcast_links[0]
 
286
 
287
  # --- EMOTIONAL / SUPPORT MODE CHECK ---
288
  is_emotional = detect_response_type(user_question) == "support"
@@ -442,6 +445,39 @@ CRITICAL: The user is a BEGINNER. You MUST prioritize the Free Online Class abov
442
  elif user_type == 'current_student':
443
  user_type_instruction = "USER TYPE: EXISTING STUDENT. Focus on GSP membership benefits, advanced mentorships (WAM), and specialized recurring workshops."
444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  if mode == "Mode A":
446
  # Recommendation Mode: Existing checklist applies
447
  prompt = f"""{PERSONA_INSTRUCTION}
@@ -462,6 +498,7 @@ CRITICAL INSTRUCTIONS (RECOMMENDATION MODE):
462
  - For each recommendation, add a tiny bit of "mentor advice" on why it helps.
463
  - Use ONLY the provided links - do not invent recommendations.
464
  - **MANDATORY: Use direct hyperlinks.** For ANY mention of signing up, classes, kids programs, the Summit, or the free class, you MUST include the direct [Title](Link) format.
 
465
  - **NEVER say "check our website"** or "visit the link below". Embed the link directly into the relevant part of your mentor advice.
466
  - Focus on clean, readable formatting.{preference_instruction}
467
 
@@ -481,7 +518,7 @@ REQUIRED RESPONSE FORMAT (STRICT):
481
  Here's your path forward:
482
  1. Free Online Class (Mandatory First Step): {free_class_url}
483
  2. Recommended Podcast Episode (For Industry Mindset):
484
- {single_podcast}
485
  3. Recommended Workshop/Next Step:
486
  {workshop_text}{email_contact}
487
 
@@ -503,7 +540,7 @@ CRITICAL INSTRUCTIONS (FRONT DESK MODE):
503
  - Answer the user's question directly using the provided information but keep it punchy—**no essays**.
504
  - **MANDATORY: Provide direct hyperlinks** for ANY mention of registration, classes, kids programs, the Summit, or more information. Use EXACTLY these links as relevant:
505
  - Free Online Class: [{free_class_url}]({free_class_url})
506
- - Recommended for you: {single_podcast}
507
  - Upcoming Workshops: {workshop_text}
508
  - Southeast Actor Summit: [Southeast Actor Summit Registration](https://www.getscenestudios.com/southeast-actor-summit)
509
  - **NEVER say "go to the website"** or "check our site". Always provide the specific hyperlink directly in your answer.
@@ -523,9 +560,9 @@ CRITICAL ROLE GUARD (FINAL AUTHORITY):
523
 
524
  USER'S QUESTION: {user_question}
525
 
526
- REQUIRED RESPONSE FORMAT:
527
  [Routing Question]
528
  [Helpful, punchy response with links]
 
529
  [Next step guidance]{email_contact}"""
530
 
531
  return prompt
@@ -546,6 +583,7 @@ def detect_question_category(question):
546
  'pricing': ['price', 'cost', 'pricing', '$', 'money', 'payment', 'fee'],
547
  'classes': ['class', 'workshop', 'training', 'course', 'learn'],
548
  'membership': ['membership', 'join', 'member', 'gsp', 'plus'],
 
549
  'technical': ['self-tape', 'equipment', 'lighting', 'editing', 'camera']
550
  }
551
 
@@ -726,32 +764,40 @@ def process_question(question: str, current_session_id: str):
726
  top_faqs.append((score, entry_id, question_text, answer_text))
727
  top_faqs.sort(reverse=True)
728
 
729
- faq_threshold = 0.85
730
- ambiguous_threshold = 0.65
731
 
732
  is_low_confidence = False # Default safe initialization
733
  context_results = None
 
734
 
735
  if top_faqs and top_faqs[0][0] >= faq_threshold:
736
  best_score, faq_id, question_text, answer_text = top_faqs[0]
737
  print(f"DEBUG: Processing FAQ match through LLM and Truth Sheet rules...")
738
  context_results = answer_text
 
739
 
740
  elif activated_mode == "Mode A":
741
  # Mode A: Any score < 0.85 triggers Clarification -> Email
 
 
 
742
  clarification_count = session_state.get('clarification_count', 0)
743
- if clarification_count == 0:
744
  update_session_state(current_session_id, increment_clarification=True, increment_count=False)
745
  return "I want to make sure I give you the best advice. Are you looking for classes in [Atlanta](https://www.getscenestudios.com/instudio), [Online](https://www.getscenestudios.com/online), or something else like getting an agent? You can also start right now with our [Free Online Class](https://www.getscenestudios.com/online)!"
746
- else:
747
  update_session_state(current_session_id, reset_clarification=True)
748
  return "I'm still not quite sure, and I want to make sure you get the right answer! Please email our team at info@getscenestudios.com and we'll help you directly. In the meantime, you can explore or [register for our Online Path](https://www.getscenestudios.com/online) or [In-Studio classes in Atlanta](https://www.getscenestudios.com/instudio)."
 
749
 
750
  elif top_faqs and top_faqs[0][0] >= ambiguous_threshold:
751
- # Mode B: Ambiguous Score (0.65 - 0.85) -> Ask "Did you mean?"
752
- update_session_state(current_session_id, increment_clarification=True, increment_count=False)
753
- best_match_q = top_faqs[0][2]
754
- return f"Did you mean: {best_match_q}?"
 
 
755
 
756
  else:
757
  # 5. HALLUCINATION GUARD: Check if query is acting-related before blocking
@@ -760,7 +806,7 @@ def process_question(question: str, current_session_id: str):
760
  has_session_context = (current_topic is not None) or (user_preference is not None)
761
 
762
  FOLLOWUP_KEYWORDS = ['yes', 'no', 'sure', 'okay', 'thanks', 'thank you', 'please', 'go ahead', 'continue', 'more']
763
- ACTING_KEYWORDS = ['class', 'workshop', 'coaching', 'studio', 'acting', 'online', 'person', 'atlanta', 'training', 'prefer', 'preference', 'format', 'recommendation', 'online class', 'online workshop','instudio class','instudio workshop', 'actor', 'scene', 'audition', 'theatre', 'film', 'tv', 'commercial', 'agent', 'rep', 'manager', 'instructor', 'role', 'auditing', 'audit', 'representation', 'summit', 'sign up', 'sign-up', 'register', 'enroll', 'schedule', 'cancel', 'reschedule', 'how do i']
764
 
765
  is_acting_related = (
766
  len(categories) > 0 or
@@ -786,14 +832,40 @@ def process_question(question: str, current_session_id: str):
786
  top_workshops = find_top_workshops(user_embedding, k=3)
787
  top_podcasts = find_top_k_matches(user_embedding, podcast_data, k=3)
788
 
 
 
 
 
789
  enriched_podcast_links = []
790
  for _, podcast_id, _ in top_podcasts:
791
  row = fetch_row_by_id("podcast_episodes", podcast_id)
792
- enriched_podcast_links.extend(generate_enriched_links(row))
 
793
 
794
  if not enriched_podcast_links:
795
  fallback = fetch_row_by_id("podcast_episodes", podcast_data[0][0])
796
  enriched_podcast_links = generate_enriched_links(fallback)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
 
798
  # Brevity & Detail Detection
799
  wants_details = any(syn in question.lower() for syn in DETAIL_SYNONYMS)
@@ -805,22 +877,22 @@ def process_question(question: str, current_session_id: str):
805
  top_workshops,
806
  user_preference=user_preference,
807
  user_type=user_type,
808
- enriched_podcast_links=enriched_podcast_links,
809
  wants_details=wants_details,
810
  current_topic=current_topic,
811
  mode=activated_mode,
812
- is_low_confidence=is_low_confidence
 
813
  )
814
 
815
- # Invoke LLM
816
- print(f"DEBUG FINAL PROMPT:\n{final_prompt}\n--- END PROMPT ---")
 
 
817
 
818
  response = openai.chat.completions.create(
819
  model=GEN_MODEL,
820
- messages=[
821
- {"role": "system", "content": final_prompt},
822
- {"role": "user", "content": question}
823
- ]
824
  )
825
 
826
  answer_text = response.choices[0].message.content.strip()
@@ -867,21 +939,23 @@ def process_question(question: str, current_session_id: str):
867
  # GRADIO INTERFACE
868
  # ============================================================================
869
 
870
- def chat_with_bot(message, history):
871
  """
872
  Process message directly without Flask API
873
 
874
  Args:
875
  message: User's current message
876
- history: Chat history (list of message dictionaries)
 
877
 
878
  Returns:
879
- Updated history with new exchange
880
  """
881
- global session_id
882
-
 
883
  if not message.strip():
884
- return history
885
 
886
  try:
887
  # Process question directly
@@ -889,16 +963,15 @@ def chat_with_bot(message, history):
889
  except Exception as e:
890
  bot_reply = f"❌ Error: {str(e)}"
891
 
892
- # Append to history in Gradio 6.0 format
893
  history.append({"role": "user", "content": message})
894
  history.append({"role": "assistant", "content": bot_reply})
895
- return history
896
 
897
  def reset_session():
898
  """Reset session ID for new conversation"""
899
- global session_id
900
- session_id = str(uuid.uuid4())
901
- return [] #, f"🔄 New session started: {session_id[:8]}..."
902
 
903
  # Create Gradio interface
904
  with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
@@ -931,11 +1004,14 @@ with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
931
  clear_btn = gr.Button("Clear Chat 🗑️", scale=1)
932
  reset_btn = gr.Button("New Session 🔄", scale=1)
933
 
 
 
 
934
  # Event handlers
935
  submit_btn.click(
936
  fn=chat_with_bot,
937
- inputs=[msg, chatbot],
938
- outputs=[chatbot]
939
  ).then(
940
  fn=lambda: "",
941
  inputs=None,
@@ -944,8 +1020,8 @@ with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
944
 
945
  msg.submit(
946
  fn=chat_with_bot,
947
- inputs=[msg, chatbot],
948
- outputs=[chatbot]
949
  ).then(
950
  fn=lambda: "",
951
  inputs=None,
@@ -961,16 +1037,9 @@ with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
961
  reset_btn.click(
962
  fn=reset_session,
963
  inputs=None,
964
- outputs=[chatbot]
965
  )
966
 
967
  # Launch the app
968
- if __name__ == "__main__":
969
- print("\n" + "="*60)
970
- print("🎬 Get Scene Studios Chatbot")
971
- print("="*60)
972
- print("\n✅ No Flask API needed - all processing is done directly!")
973
- print("🌐 Gradio interface will open in your browser")
974
- print("="*60 + "\n")
975
-
976
  demo.launch()
 
5
  import os
6
  from datetime import datetime, timedelta
7
  import uuid
8
+ import random
9
  from typing import Dict
10
 
11
  from config import (
 
18
  get_embedding, cosine_similarity, find_top_k_matches,
19
  classify_intent, should_include_email, classify_user_type
20
  )
 
21
  from database import (
22
  fetch_all_embeddings,
23
  fetch_row_by_id,
24
  fetch_all_faq_embeddings,
25
  get_session_state,
26
  update_session_state,
27
+ log_question,
28
+ get_recent_history
29
  )
30
+ from scraper import scrape_workshops_from_squarespace
31
 
32
  # ============================================================================
33
  # CONFIGURATION
 
39
  openai.api_key = OPENAI_API_KEY
40
 
41
 
42
+ # Removed global session_id for multi-user compatibility
 
43
 
44
  # Cache for workshop data and embeddings
45
  workshop_cache = {
 
223
  markdown = f"🎧 [Watch {guest_name}'s episode here]({base_url}) - {short_summary}"
224
  return [markdown]
225
 
226
+ def build_enhanced_prompt(user_question, context_results, top_workshops, user_preference=None, user_type='unknown', enriched_podcast_links=None, wants_details=False, current_topic=None, mode="Mode B", is_low_confidence=False, is_faq_match=False):
227
  """Builds the system prompt with strict formatting rules."""
228
 
229
  # Dynamic Links from Structured Knowledge
 
279
  # Mandatory Hyperlink Enforcement
280
  workshop_text = f"We are constantly updating our schedule! You can view and [register for upcoming {label}workshops here]({link})."
281
 
282
+ # Pass multiple podcast options to the LLM for variety
283
+ podcast_options = ""
284
  if not enriched_podcast_links:
285
+ podcast_options = "Our latest industry insights are available on YouTube: https://www.youtube.com/@GetSceneStudios"
286
  else:
287
+ # Provide up to 3 options
288
+ podcast_options = "\n".join(enriched_podcast_links[:3])
289
 
290
  # --- EMOTIONAL / SUPPORT MODE CHECK ---
291
  is_emotional = detect_response_type(user_question) == "support"
 
445
  elif user_type == 'current_student':
446
  user_type_instruction = "USER TYPE: EXISTING STUDENT. Focus on GSP membership benefits, advanced mentorships (WAM), and specialized recurring workshops."
447
 
448
+ # --- FAQ MATCH MODE (Highest Priority) ---
449
+ if is_faq_match:
450
+ prompt = f"""{PERSONA_INSTRUCTION}
451
+
452
+ {truth_sheet_snippet}
453
+
454
+ {BUSINESS_RULES_INSTRUCTION}
455
+
456
+ {user_type_instruction}
457
+
458
+ {context_snippet}{retrieved_info}
459
+
460
+ CRITICAL INSTRUCTIONS (FAQ MODE):
461
+ - You are answering a question that has a direct match in our FAQ.
462
+ - Answer the user's question directly and punchily using ONLY the provided information.
463
+ - **DO NOT** use the structured 1. 2. 3. format.
464
+ - **DO NOT** ask a routing question.
465
+ - **MANDATORY: Use direct hyperlinks.** For ANY mention of signing up, classes, kids programs, or the free class, you MUST include the direct [Title](Link) format.
466
+ - Focus on being a helpful guide. {preference_instruction}
467
+
468
+ CRITICAL ROLE GUARD (FINAL AUTHORITY):
469
+ - Corey Lawson: Instructor/Actor [NOT an Agent]
470
+ - Jacob Lawson: Agent/Owner [NOT an Instructor]
471
+ - Jesse Malinowski: Founder/Mentor [NOT an Agent]
472
+ - Alex White: Agent [NOT an Instructor/Mentor]
473
+ - THE TRUTH SHEET IS THE ABSOLUTE AUTHORITY.
474
+
475
+ USER'S QUESTION: {user_question}
476
+
477
+ REQUIRED RESPONSE FORMAT:
478
+ [Punchy, helpful answer based on FAQ with relevant links]{email_contact}"""
479
+ return prompt
480
+
481
  if mode == "Mode A":
482
  # Recommendation Mode: Existing checklist applies
483
  prompt = f"""{PERSONA_INSTRUCTION}
 
498
  - For each recommendation, add a tiny bit of "mentor advice" on why it helps.
499
  - Use ONLY the provided links - do not invent recommendations.
500
  - **MANDATORY: Use direct hyperlinks.** For ANY mention of signing up, classes, kids programs, the Summit, or the free class, you MUST include the direct [Title](Link) format.
501
+ - **CRITICAL: PRESERVE URLS.** You MUST include the full URL in parentheses `(https://...)`. DO NOT output just the bracketed text `[Title]`. If you fail to include the URL, the link will be broken.
502
  - **NEVER say "check our website"** or "visit the link below". Embed the link directly into the relevant part of your mentor advice.
503
  - Focus on clean, readable formatting.{preference_instruction}
504
 
 
518
  Here's your path forward:
519
  1. Free Online Class (Mandatory First Step): {free_class_url}
520
  2. Recommended Podcast Episode (For Industry Mindset):
521
+ {podcast_options}
522
  3. Recommended Workshop/Next Step:
523
  {workshop_text}{email_contact}
524
 
 
540
  - Answer the user's question directly using the provided information but keep it punchy—**no essays**.
541
  - **MANDATORY: Provide direct hyperlinks** for ANY mention of registration, classes, kids programs, the Summit, or more information. Use EXACTLY these links as relevant:
542
  - Free Online Class: [{free_class_url}]({free_class_url})
543
+ - Recommended for you: {podcast_options}
544
  - Upcoming Workshops: {workshop_text}
545
  - Southeast Actor Summit: [Southeast Actor Summit Registration](https://www.getscenestudios.com/southeast-actor-summit)
546
  - **NEVER say "go to the website"** or "check our site". Always provide the specific hyperlink directly in your answer.
 
560
 
561
  USER'S QUESTION: {user_question}
562
 
 
563
  [Routing Question]
564
  [Helpful, punchy response with links]
565
+ **IMPORTANT: You MUST choose the most relevant podcast from the list provided above and include its FULL Markdown link including the URL in your response.**
566
  [Next step guidance]{email_contact}"""
567
 
568
  return prompt
 
583
  'pricing': ['price', 'cost', 'pricing', '$', 'money', 'payment', 'fee'],
584
  'classes': ['class', 'workshop', 'training', 'course', 'learn'],
585
  'membership': ['membership', 'join', 'member', 'gsp', 'plus'],
586
+ 'podcast': ['podcast', 'podcasts', 'youtube', 'watch', 'listen', 'episode', 'episodes'],
587
  'technical': ['self-tape', 'equipment', 'lighting', 'editing', 'camera']
588
  }
589
 
 
764
  top_faqs.append((score, entry_id, question_text, answer_text))
765
  top_faqs.sort(reverse=True)
766
 
767
+ faq_threshold = 0.50
768
+ ambiguous_threshold = 0.60
769
 
770
  is_low_confidence = False # Default safe initialization
771
  context_results = None
772
+ is_faq_match = False
773
 
774
  if top_faqs and top_faqs[0][0] >= faq_threshold:
775
  best_score, faq_id, question_text, answer_text = top_faqs[0]
776
  print(f"DEBUG: Processing FAQ match through LLM and Truth Sheet rules...")
777
  context_results = answer_text
778
+ is_faq_match = True
779
 
780
  elif activated_mode == "Mode A":
781
  # Mode A: Any score < 0.85 triggers Clarification -> Email
782
+ # EXCEPTION: If they specifically ask for podcasts or recommendations, let it through to LLM path
783
+ is_recommendation_query = any(k in question.lower() for k in ['podcast', 'reccomend', 'recommend', 'path', 'help', 'advice', 'guide'])
784
+
785
  clarification_count = session_state.get('clarification_count', 0)
786
+ if clarification_count == 0 and not is_recommendation_query:
787
  update_session_state(current_session_id, increment_clarification=True, increment_count=False)
788
  return "I want to make sure I give you the best advice. Are you looking for classes in [Atlanta](https://www.getscenestudios.com/instudio), [Online](https://www.getscenestudios.com/online), or something else like getting an agent? You can also start right now with our [Free Online Class](https://www.getscenestudios.com/online)!"
789
+ elif clarification_count > 0 and not is_recommendation_query:
790
  update_session_state(current_session_id, reset_clarification=True)
791
  return "I'm still not quite sure, and I want to make sure you get the right answer! Please email our team at info@getscenestudios.com and we'll help you directly. In the meantime, you can explore or [register for our Online Path](https://www.getscenestudios.com/online) or [In-Studio classes in Atlanta](https://www.getscenestudios.com/instudio)."
792
+ # Else: is_recommendation_query is True, fall through to LLM path
793
 
794
  elif top_faqs and top_faqs[0][0] >= ambiguous_threshold:
795
+ # Mode B: Ambiguous Score -> Use best FAQ match as context for LLM
796
+ # Instead of asking "Did you mean?", answer naturally using the FAQ content
797
+ best_score, faq_id, question_text, answer_text = top_faqs[0]
798
+ print(f"DEBUG: Ambiguous FAQ match (score={best_score:.2f}), using as LLM context: {question_text[:60]}...")
799
+ context_results = answer_text
800
+ is_faq_match = True
801
 
802
  else:
803
  # 5. HALLUCINATION GUARD: Check if query is acting-related before blocking
 
806
  has_session_context = (current_topic is not None) or (user_preference is not None)
807
 
808
  FOLLOWUP_KEYWORDS = ['yes', 'no', 'sure', 'okay', 'thanks', 'thank you', 'please', 'go ahead', 'continue', 'more']
809
+ ACTING_KEYWORDS = ['class', 'workshop', 'coaching', 'studio', 'acting', 'online', 'person', 'atlanta', 'training', 'prefer', 'preference', 'format', 'recommendation', 'online class', 'online workshop','instudio class','instudio workshop', 'actor', 'scene', 'audition', 'theatre', 'film', 'tv', 'commercial', 'agent', 'rep', 'manager', 'instructor', 'role', 'auditing', 'audit', 'representation', 'summit', 'sign up', 'sign-up', 'register', 'enroll', 'schedule', 'cancel', 'reschedule', 'how do i', 'podcast', 'podcasts', 'youtube', 'episode', 'episodes', 'watch']
810
 
811
  is_acting_related = (
812
  len(categories) > 0 or
 
832
  top_workshops = find_top_workshops(user_embedding, k=3)
833
  top_podcasts = find_top_k_matches(user_embedding, podcast_data, k=3)
834
 
835
+ # Get chat history for rotation logic
836
+ chat_history = get_recent_history(current_session_id, limit=5)
837
+ history_text = " ".join([m['content'] for m in chat_history]).lower()
838
+
839
  enriched_podcast_links = []
840
  for _, podcast_id, _ in top_podcasts:
841
  row = fetch_row_by_id("podcast_episodes", podcast_id)
842
+ links = generate_enriched_links(row)
843
+ enriched_podcast_links.extend(links)
844
 
845
  if not enriched_podcast_links:
846
  fallback = fetch_row_by_id("podcast_episodes", podcast_data[0][0])
847
  enriched_podcast_links = generate_enriched_links(fallback)
848
+
849
+ # Diversity Logic: Shuffle and prioritize unseen podcasts
850
+ random.shuffle(enriched_podcast_links)
851
+ seen_links = []
852
+ unseen_links = []
853
+
854
+ for link in enriched_podcast_links:
855
+ # Extract guest name or unique part to check history
856
+ # e.g. "🎧 [Watch Haillie Ricardo's episode here]..."
857
+ match = re.search(r'Watch (.*)\'s episode', link)
858
+ if match:
859
+ guest_name = match.group(1).lower()
860
+ if guest_name in history_text:
861
+ seen_links.append(link)
862
+ else:
863
+ unseen_links.append(link)
864
+ else:
865
+ unseen_links.append(link)
866
+
867
+ # Combine: Unseen first, then seen
868
+ final_podcast_options = unseen_links + seen_links
869
 
870
  # Brevity & Detail Detection
871
  wants_details = any(syn in question.lower() for syn in DETAIL_SYNONYMS)
 
877
  top_workshops,
878
  user_preference=user_preference,
879
  user_type=user_type,
880
+ enriched_podcast_links=final_podcast_options,
881
  wants_details=wants_details,
882
  current_topic=current_topic,
883
  mode=activated_mode,
884
+ is_low_confidence=is_low_confidence,
885
+ is_faq_match=is_faq_match
886
  )
887
 
888
+ # LLM messages
889
+ messages = [{"role": "system", "content": final_prompt}]
890
+ messages.extend(chat_history)
891
+ messages.append({"role": "user", "content": question})
892
 
893
  response = openai.chat.completions.create(
894
  model=GEN_MODEL,
895
+ messages=messages
 
 
 
896
  )
897
 
898
  answer_text = response.choices[0].message.content.strip()
 
939
  # GRADIO INTERFACE
940
  # ============================================================================
941
 
942
+ def chat_with_bot(message, history, session_id):
943
  """
944
  Process message directly without Flask API
945
 
946
  Args:
947
  message: User's current message
948
+ history: Chat history
949
+ session_id: Per-user session ID state
950
 
951
  Returns:
952
+ Updated history and session_id
953
  """
954
+ if not session_id:
955
+ session_id = str(uuid.uuid4())
956
+
957
  if not message.strip():
958
+ return history, session_id
959
 
960
  try:
961
  # Process question directly
 
963
  except Exception as e:
964
  bot_reply = f"❌ Error: {str(e)}"
965
 
966
+ # Append to history
967
  history.append({"role": "user", "content": message})
968
  history.append({"role": "assistant", "content": bot_reply})
969
+ return history, session_id
970
 
971
  def reset_session():
972
  """Reset session ID for new conversation"""
973
+ new_id = str(uuid.uuid4())
974
+ return [], new_id
 
975
 
976
  # Create Gradio interface
977
  with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
 
1004
  clear_btn = gr.Button("Clear Chat 🗑️", scale=1)
1005
  reset_btn = gr.Button("New Session 🔄", scale=1)
1006
 
1007
+ # Session state
1008
+ session_state = gr.State("")
1009
+
1010
  # Event handlers
1011
  submit_btn.click(
1012
  fn=chat_with_bot,
1013
+ inputs=[msg, chatbot, session_state],
1014
+ outputs=[chatbot, session_state]
1015
  ).then(
1016
  fn=lambda: "",
1017
  inputs=None,
 
1020
 
1021
  msg.submit(
1022
  fn=chat_with_bot,
1023
+ inputs=[msg, chatbot, session_state],
1024
+ outputs=[chatbot, session_state]
1025
  ).then(
1026
  fn=lambda: "",
1027
  inputs=None,
 
1037
  reset_btn.click(
1038
  fn=reset_session,
1039
  inputs=None,
1040
+ outputs=[chatbot, session_state]
1041
  )
1042
 
1043
  # Launch the app
1044
+ if __name__ == "__main__":
 
 
 
 
 
 
 
1045
  demo.launch()