| import gradio as gr |
| import openai |
| import json |
| from datetime import datetime, timedelta |
| import uuid |
| from typing import Dict |
|
|
| from config import OPENAI_API_KEY, DB_PATH, EMBED_MODEL |
| from utils import get_embedding, cosine_similarity, find_top_k_matches |
| from scraper import scrape_workshops_from_squarespace |
| from database import ( |
| fetch_all_embeddings, |
| fetch_row_by_id, |
| fetch_all_faq_embeddings, |
| get_session_state, |
| update_session_state, |
| log_question |
| ) |
|
|
| |
| |
| |
|
|
| if not OPENAI_API_KEY: |
| raise ValueError("OPENAI_API_KEY not found in .env file") |
|
|
| openai.api_key = OPENAI_API_KEY |
|
|
|
|
| |
| session_id = str(uuid.uuid4()) |
|
|
| |
| workshop_cache = { |
| 'data': [], |
| 'embeddings': [], |
| 'last_updated': None, |
| 'cache_duration': timedelta(hours=24) |
| } |
|
|
| |
| |
| |
|
|
| EMOTIONAL_KEYWORDS = [ |
| 'stuck', 'frustrated', 'discouraged', 'overwhelmed', 'scared', |
| 'nervous', 'anxious', 'worried', 'fear', 'doubt', 'confidence', |
| 'insecure', 'lost', 'confused', 'struggling', 'hard time', |
| 'giving up', 'burnout', 'rejection', 'failed', 'can\'t', |
| 'feeling', 'feel', 'emotional', 'depressed', 'sad', 'unmotivated', |
| 'hopeless', 'stressed', 'pressure', 'imposter' |
| ] |
|
|
| ACTION_KEYWORDS = [ |
| 'get an agent', 'find agent', 'need agent', 'want agent', 'sign with agent', |
| 'more auditions', 'book', 'booking', 'callbacks', 'improve', |
| 'better', 'self-tape', 'materials', 'headshots', 'reel', |
| 'network', 'connections', 'industry', 'career', 'strategy', |
| 'agent prep', 'total agent prep', 'workshop', 'class', 'training', |
| 'results', 'success', 'grow', 'advance', 'level up' |
| ] |
|
|
| POLICY_KEYWORDS = [ |
| 'refund', 'refunds', 'money back', |
| 'attend', 'attendance', 'miss', 'missed', 'missing', 'absent', |
| 'late', 'lateness', 'tardy', |
| 'reschedule', 'change date', 'move class', |
| 'credit', 'credits', |
| 'cancel', 'cancellation', 'canceling', |
| 'policy', 'policies' |
| ] |
|
|
| DETAIL_SYNONYMS = [ |
| 'detail', 'details', 'explain', 'elaborate', 'tell me more', |
| 'more info', 'describe', 'thorough', 'comprehensive' |
| ] |
|
|
| PERSONA_INSTRUCTION = """ |
| You are a warm, encouraging mentor at Get Scene Studios. Your goal is to help actors navigate their careers with confidence. |
| - Sound natural and human, not scripted or robotic. Use conversational transitions like "I'd suggest starting with..." or "A great way to approach this is..." |
| - Be encouraging but practical. Acknowledge that the acting journey is a marathon, not a sprint. |
| - Help the user THINK: Instead of just giving an answer, add a brief "mentorship flourish" that explains the value of a recommendation (e.g., "This workshop is great because it gets you comfortable with the pressure of a real callback.") |
| """ |
|
|
| |
| |
| |
|
|
| def calculate_workshop_confidence(w: Dict) -> float: |
| """Calculate confidence score of retrieved workshop data""" |
| score = 0.0 |
| if w.get('title'): score += 0.3 |
| if w.get('instructor_name'): score += 0.3 |
| if w.get('date'): score += 0.2 |
| if w.get('time'): score += 0.1 |
| if w.get('source_url'): score += 0.1 |
| return round(score, 2) |
|
|
| |
| |
| |
|
|
| def get_current_workshops(): |
| """Get current workshops with caching""" |
| global workshop_cache |
| |
| now = datetime.now() |
| |
| |
| if (workshop_cache['last_updated'] and |
| now - workshop_cache['last_updated'] < workshop_cache['cache_duration'] and |
| workshop_cache['data']): |
| print("Using cached workshop data") |
| return workshop_cache['data'], workshop_cache['embeddings'] |
| |
| print("Fetching fresh workshop data...") |
| |
| |
| online_workshops = scrape_workshops_from_squarespace("https://www.getscenestudios.com/online") |
| instudio_workshops = scrape_workshops_from_squarespace("https://www.getscenestudios.com/instudio") |
| |
| all_workshops = online_workshops + instudio_workshops |
| |
| |
| valid_workshops = [] |
| total_score = 0 |
| for w in all_workshops: |
| conf = calculate_workshop_confidence(w) |
| if conf >= 0.8: |
| valid_workshops.append(w) |
| total_score += conf |
| else: |
| print(f"β οΈ Rejecting weak record (Confidence: {conf}): {w.get('title', 'Unknown')}", flush=True) |
| |
| avg_conf = total_score / len(valid_workshops) if valid_workshops else 0 |
| print(f"π DATA INTEGRITY: Found {len(all_workshops)} total, {len(valid_workshops)} valid (Confidence >= 0.8)", flush=True) |
| print(f"π Retrieval Confidence: {avg_conf:.2f} (Average)", flush=True) |
| |
| all_workshops = valid_workshops |
| |
| if not all_workshops: |
| if workshop_cache['data']: |
| print("Scraping failed, using cached data") |
| return workshop_cache['data'], workshop_cache['embeddings'] |
| else: |
| print("No workshop data available") |
| return [], [] |
| |
| |
| workshop_embeddings = [] |
| for workshop in all_workshops: |
| try: |
| embedding = get_embedding(workshop['full_text']) |
| workshop_embeddings.append(embedding) |
| except Exception as e: |
| print(f"Error generating embedding for workshop: {e}") |
| workshop_embeddings.append([0] * 1536) |
| |
| |
| workshop_cache['data'] = all_workshops |
| workshop_cache['embeddings'] = workshop_embeddings |
| workshop_cache['last_updated'] = now |
| |
| print(f"Cached {len(all_workshops)} workshops") |
| return all_workshops, workshop_embeddings |
|
|
| def find_top_workshops(user_embedding, k=3): |
| """Find top matching workshops using real-time data""" |
| workshops, workshop_embeddings = get_current_workshops() |
| |
| if not workshops: |
| return [] |
| |
| scored = [] |
| for i, (workshop, emb) in enumerate(zip(workshops, workshop_embeddings)): |
| try: |
| score = cosine_similarity(user_embedding, emb) |
| scored.append((score, i, workshop['full_text'], workshop)) |
| except Exception as e: |
| print(f"Error calculating similarity: {e}") |
| continue |
| |
| scored.sort(reverse=True) |
| return scored[:k] |
|
|
| |
| |
| |
|
|
| def generate_enriched_links(row): |
| base_url = row.get("youtube_url") |
| guest_name = row.get("guest_name", "") |
| highlights = json.loads(row.get("highlight_json", "[]")) |
| summary = highlights[0]["summary"] if highlights else "" |
| |
| |
| if summary: |
| first_sentence = summary.split('.')[0] + '.' |
| |
| if len(first_sentence) > 120: |
| short_summary = first_sentence[:117] + "..." |
| else: |
| short_summary = first_sentence |
| else: |
| short_summary = "Industry insights for actors" |
| |
| markdown = f"π§ [Watch {guest_name}'s episode here]({base_url}) - {short_summary}" |
| return [markdown] |
|
|
| def build_enhanced_prompt(user_question, context_results, top_workshops, user_preference=None, enriched_podcast_links=None, wants_details=False, current_topic=None): |
| """Builds the system prompt with strict formatting rules.""" |
| |
| |
| free_class_url = "https://www.getscenestudios.com/online" |
| |
| |
| def format_workshop(w): |
| if not w.get('title') or not w.get('instructor_name') or not w.get('date'): |
| return None |
| |
| link = "https://www.getscenestudios.com/instudio" if "/instudio" in w.get('source_url', '') else "https://www.getscenestudios.com/online" |
| |
| |
| w_type = "Online" if "online" in w.get('source_url', '') else "In-Studio" |
| if user_preference: |
| if user_preference.lower() != w_type.lower(): |
| return None |
|
|
| |
| confidence = calculate_workshop_confidence(w) |
| if confidence < 0.70: |
| return None |
|
|
| |
| display_title = f"{w['title']} ({w_type})" |
| return f"- [{display_title}]({link}) with {w['instructor_name']} on {w['date']} at {w.get('time', '')}" |
|
|
| |
| workshop_lines = [] |
| if top_workshops: |
| for _, _, _, w_data in top_workshops[:10]: |
| formatted = format_workshop(w_data) |
| if formatted: |
| workshop_lines.append(formatted) |
| |
|
|
| workshop_text = "" |
| if workshop_lines: |
| workshop_text = "\n".join(workshop_lines[:3]) |
| else: |
| |
| label = f"{user_preference.capitalize()} " if user_preference else "" |
| link = "https://www.getscenestudios.com/online" if user_preference == 'online' else "https://www.getscenestudios.com/instudio" if user_preference == 'instudio' else "https://www.getscenestudios.com/online" |
| workshop_text = f"We are constantly updating our schedule! Check our current {label}availability and latest workshops at {link}" |
| |
| |
| if not enriched_podcast_links: |
| single_podcast = "Our latest industry insights are available on YouTube: https://www.youtube.com/@GetSceneStudios" |
| else: |
| single_podcast = enriched_podcast_links[0] |
| |
| |
| is_emotional = detect_response_type(user_question) == "support" |
| |
| if is_emotional: |
| prompt = f"""{PERSONA_INSTRUCTION} |
| |
| You are acting in SUPPORT MODE. |
| |
| CRITICAL INSTRUCTIONS: |
| 1. ACKNOWLEDGE their feelings first (e.g., "I hear how frustrating it is to feel stuck..."). |
| 2. Provide SUPPORTIVE language (2-3 sentences max). |
| 3. Offer EXACTLY ONE gentle follow-up resource: either the podcast OR the free class. |
| 4. DO NOT suggest paid workshops or upsell in this response. |
| 5. KEEP IT BRIEF (β€150 words). |
| |
| USER'S QUESTION: {user_question} |
| |
| REQUIRED RESPONSE FORMAT: |
| [Your empathetic, supportive acknowledgment] |
| |
| Here's a free resource that might help you move forward: |
| [Pick ONE: {single_podcast} OR Free Class at {free_class_url}] |
| |
| Questions? Contact info@getscenestudios.com""" |
| return prompt |
|
|
| |
| question_lower = user_question.lower() |
| context_snippet = "" |
| |
| |
| detected_topic = None |
| if any(word in question_lower for word in ['agent', 'representation', 'rep', 'manager']): |
| detected_topic = 'agent' |
| elif any(word in question_lower for word in ['beginner', 'new', 'start', 'beginning']): |
| detected_topic = 'beginner' |
| elif any(word in question_lower for word in ['callback', 'audition', 'tape', 'self-tape', 'booking']): |
| detected_topic = 'audition' |
| elif any(word in question_lower for word in ['mentorship', 'coaching']): |
| detected_topic = 'mentorship' |
| elif any(word in question_lower for word in ['price', 'cost', 'how much']): |
| detected_topic = 'pricing' |
| |
| |
| if not detected_topic and current_topic: |
| topic_map = { |
| 'agent_seeking': 'agent', |
| 'beginner': 'beginner', |
| 'audition_help': 'audition', |
| 'mentorship': 'mentorship', |
| 'pricing': 'pricing' |
| } |
| detected_topic = topic_map.get(current_topic) |
|
|
| |
| if detected_topic == 'agent': |
| context_snippet = "Get Scene Studios has helped 1000+ actors land representation. Total Agent Prep offers live practice with working agents (age 16+, limited to 12 actors)." |
| elif detected_topic == 'beginner': |
| context_snippet = "Get Scene Studios specializes in getting actors audition-ready fast with camera technique and professional self-tape skills." |
| elif detected_topic == 'audition': |
| context_snippet = "Get Scene offers Crush the Callback (Zoom simulation) and Perfect Submission (self-tape mastery) for actors refining their technique." |
| elif detected_topic == 'mentorship': |
| context_snippet = "Working Actor Mentorship is a 6-month program ($3,000) with structured feedback and industry access." |
| elif detected_topic == 'pricing': |
| context_snippet = "Get Scene Studios pricing varies by program. Most workshops cap at 12-14 actors for personalized feedback." |
| else: |
| context_snippet = "Get Scene Studios (founded by Jesse Malinowski) offers training for TV/film actors at all levels." |
|
|
| preference_instruction = "" |
| if not user_preference: |
| preference_instruction = """ |
| IMPORTANT: We need to know if the user prefers "Online" or "In-Studio" workshops. |
| If their question implies a location or they haven't specified, ask: "Are you looking for Online or In-Studio training?" as part of your response. |
| """ |
| else: |
| preference_instruction = f""" |
| USER PREFERENCE KNOWN: {user_preference.upper()} |
| 1. DO NOT ask "Online or In-Studio" again. |
| 2. Ensure your recommendations align with {user_preference.upper()} where possible. |
| """ |
|
|
| |
| detail_instruction = "Answer the user's question briefly (2-3 sentences max, β€150 words total)." |
| if wants_details: |
| detail_instruction = "Provide a detailed and thorough explanation for the user's request, but keep it structured and readable." |
|
|
| prompt = f"""{PERSONA_INSTRUCTION} |
| |
| {context_snippet} |
| |
| CRITICAL INSTRUCTIONS: |
| - {detail_instruction} |
| - Use natural, human transitions between your answer and the recommendations. |
| - For each recommendation, add a tiny bit of "mentor advice" on why it helps. |
| - Then ALWAYS provide exactly these three numbered recommendations (1. 2. 3.): |
| - Use ONLY the provided links - do not invent recommendations |
| - Every workshop Title MUST be followed by its format in parentheses, e.g., "Workshop Name (Online)" or "Workshop Name (In-Studio)". |
| - Focus on clean, readable formatting.{preference_instruction} |
| |
| USER'S QUESTION: {user_question} |
| |
| REQUIRED RESPONSE FORMAT: |
| [Your brief answer to their question, β€150 words total] |
| |
| Here's your path forward: |
| 1. Free class (start here, no credit card required): {free_class_url} |
| 2. Recommended podcast episode: |
| {single_podcast} |
| 3. Relevant paid workshop: |
| {workshop_text} |
| |
| Questions? Contact info@getscenestudios.com""" |
| |
| return prompt |
|
|
| |
| |
| |
|
|
| def detect_question_category(question): |
| """Categorize user questions for better context injection""" |
| question_lower = question.lower() |
| |
| categories = { |
| 'agent_seeking': ['agent', 'representation', 'rep', 'manager', 'get an agent'], |
| 'beginner': ['beginner', 'new', 'start', 'beginning', 'first time', 'never acted'], |
| 'audition_help': ['audition', 'callback', 'tape', 'self-tape', 'submission'], |
| 'mentorship': ['mentorship', 'coaching', 'intensive', 'mentor', 'one-on-one'], |
| 'pricing': ['price', 'cost', 'pricing', '$', 'money', 'payment', 'fee'], |
| 'classes': ['class', 'workshop', 'training', 'course', 'learn'], |
| 'membership': ['membership', 'join', 'member', 'gsp', 'plus'], |
| 'technical': ['self-tape', 'equipment', 'lighting', 'editing', 'camera'] |
| } |
| |
| detected = [] |
| for category, keywords in categories.items(): |
| if any(keyword in question_lower for keyword in keywords): |
| detected.append(category) |
| |
| return detected |
|
|
| def detect_response_type(question): |
| """Detect if question is emotional/support vs action/results oriented""" |
| question_lower = question.lower() |
| |
| emotional_count = sum(1 for word in EMOTIONAL_KEYWORDS if word in question_lower) |
| action_count = sum(1 for word in ACTION_KEYWORDS if word in question_lower) |
| |
| if emotional_count > 0 and emotional_count >= action_count: |
| return "support" |
| return "standard" |
|
|
| def detect_policy_issue(question): |
| """Detect if question violates hard policy rules""" |
| question_lower = question.lower() |
| return any(word in question_lower for word in POLICY_KEYWORDS) |
|
|
| def detect_preference(question): |
| """Detect if user is stating a preference""" |
| q_lower = question.lower() |
| if 'online' in q_lower and 'studio' not in q_lower: |
| return 'online' |
| if ('studio' in q_lower or 'person' in q_lower or 'atlanta' in q_lower) and 'online' not in q_lower: |
| return 'instudio' |
| return None |
|
|
| def get_contextual_business_info(categories): |
| """Return relevant business information based on detected question categories""" |
| |
| context_map = { |
| 'agent_seeking': { |
| 'programs': ['Total Agent Prep', 'Working Actor Mentorship'], |
| 'key_info': 'Live pitch practice with real agents, Actors Access optimization', |
| 'journey': 'Total Agent Prep β GSP β Mentorship for sustained progress' |
| }, |
| 'beginner': { |
| 'programs': ['Free Classes', 'Get Scene 360', 'Get Scene Plus'], |
| 'key_info': 'Start with holistic foundation, build consistency', |
| 'journey': 'Free class β Get Scene 360 β GSP membership' |
| }, |
| 'audition_help': { |
| 'programs': ['Perfect Submission', 'Crush the Callback', 'Audition Insight'], |
| 'key_info': 'Self-tape mastery, callback simulation, pro feedback', |
| 'journey': 'Perfect Submission β GSP for ongoing Audition Insight' |
| }, |
| 'mentorship': { |
| 'programs': ['Working Actor Mentorship'], |
| 'key_info': '6-month intensive with structured feedback and accountability', |
| 'journey': 'Ready for commitment β WAM β Advanced workshops' |
| } |
| } |
| |
| relevant_info = {} |
| for category in categories: |
| if category in context_map: |
| relevant_info[category] = context_map[category] |
| |
| return relevant_info |
|
|
| |
| |
| |
|
|
| def update_knowledge_from_question(session_id: str, question: str): |
| """Extract attributes and update knowledge dictionary""" |
| updates = {} |
| |
| |
| pref = detect_preference(question) |
| if pref: |
| updates['format'] = pref |
| |
| |
| cats = detect_question_category(question) |
| if cats: |
| |
| priority_topics = ['agent_seeking', 'beginner', 'audition_help', 'mentorship', 'pricing'] |
| for topic in priority_topics: |
| if topic in cats: |
| updates['topic'] = topic |
| break |
| if 'topic' not in updates and cats: |
| updates['topic'] = cats[0] |
|
|
| if updates: |
| update_session_state(session_id, knowledge_update=updates, increment_count=False) |
| return updates |
| return {} |
|
|
| def process_question(question: str, current_session_id: str): |
| """Main function to process user questions - replaces Flask /ask endpoint""" |
| |
| if not question: |
| return "Question is required" |
|
|
| |
| if detect_policy_issue(question): |
| log_question(question, current_session_id) |
| |
| return "Please email info@getscenestudios.com." |
|
|
| |
| update_knowledge_from_question(current_session_id, question) |
| |
| session_state = get_session_state(current_session_id) |
| |
| try: |
| knowledge = json.loads(session_state.get('knowledge_context', '{}')) |
| except: |
| knowledge = {} |
| |
| user_preference = knowledge.get('format') |
| current_topic = knowledge.get('topic') |
| |
| if not user_preference: |
| user_preference = session_state.get('preference') |
| |
| update_session_state(current_session_id, increment_count=True) |
|
|
| |
| user_embedding = get_embedding(question) |
|
|
| |
| faq_data = fetch_all_faq_embeddings() |
| top_faqs = [] |
|
|
| for entry_id, question_text, answer_text, emb in faq_data: |
| score = cosine_similarity(user_embedding, emb) |
| top_faqs.append((score, entry_id, question_text, answer_text)) |
| top_faqs.sort(reverse=True) |
|
|
| faq_threshold = 0.85 |
| ambiguous_threshold = 0.70 |
|
|
| |
| if top_faqs and top_faqs[0][0] >= faq_threshold: |
| update_session_state(current_session_id, reset_clarification=True, increment_count=False) |
| |
| best_score, faq_id, question_text, answer_text = top_faqs[0] |
| |
| mentor_framing_start = "That's a great question! Here's the information on that:" |
| mentor_framing_end = "I hope that clears things up! Remember, every bit of knowledge helps you steer your career in the right direction." |
| |
| enhanced_answer = f"{mentor_framing_start}\n\n{answer_text}" |
| |
| |
| if any(word in enhanced_answer.lower() for word in POLICY_KEYWORDS): |
| enhanced_answer = "Please email info@getscenestudios.com for assistance with this." |
| else: |
| categories = detect_question_category(question) |
| contextual_info = get_contextual_business_info(categories) |
| |
| if contextual_info: |
| next_steps = [] |
| for category, info in contextual_info.items(): |
| next_steps.append(f"A great next step for you: {info['journey']}") |
| |
| if next_steps: |
| enhanced_answer += f"\n\n{chr(10).join(next_steps)}" |
| |
| enhanced_answer += f"\n\n{mentor_framing_end}\n\nQuestions? Contact info@getscenestudios.com" |
|
|
| |
| log_question(question, current_session_id, answer=enhanced_answer) |
|
|
| return enhanced_answer |
| |
| elif top_faqs and top_faqs[0][0] >= ambiguous_threshold: |
| |
| needs_clarification = False |
| |
| if not user_preference: |
| needs_clarification = True |
| |
| is_generic_query = any(w in question.lower() for w in ['price', 'cost', 'how much', 'schedule', 'when']) |
| if is_generic_query and not current_topic: |
| needs_clarification = True |
| |
| clarification_count = session_state.get('clarification_count', 0) |
| if clarification_count > 0: |
| needs_clarification = False |
| |
| if needs_clarification: |
| update_session_state(current_session_id, increment_clarification=True, increment_count=False) |
| best_match_q = top_faqs[0][2] |
| return f"Did you mean: {best_match_q}?" |
|
|
| |
| update_session_state(current_session_id, reset_clarification=True, increment_count=False) |
| |
| best_score, faq_id, question_text, answer_text = top_faqs[0] |
| |
| categories = detect_question_category(question) |
| contextual_info = get_contextual_business_info(categories) |
| |
| enhanced_answer = answer_text |
| if contextual_info: |
| next_steps = [] |
| for category, info in contextual_info.items(): |
| next_steps.append(f"Next step: Consider {info['journey']}") |
| |
| if next_steps: |
| enhanced_answer += f"\n\n{chr(10).join(next_steps)}" |
| enhanced_answer += f"\n\nQuestions? Contact info@getscenestudios.com" |
| |
| log_question(question, current_session_id, answer=enhanced_answer) |
|
|
| return enhanced_answer |
|
|
| else: |
| |
| categories = detect_question_category(question) |
| |
| has_session_context = (current_topic is not None) or (user_preference is not None) |
| |
| is_acting_related = ( |
| len(categories) > 0 or |
| detect_response_type(question) == "support" or |
| any(k in question.lower() for k in ACTION_KEYWORDS) or |
| any(k in question.lower() for k in ['acting', 'actor', 'scene', 'audition', 'theatre', 'film', 'tv', 'commercial', 'agent', 'rep', 'manager']) |
| ) |
| |
| if not is_acting_related: |
| return "I'm not exactly sure about that. Please email info@getscenestudios.com so a member of our team can get you the most accurate answer!" |
| |
| |
| update_session_state(current_session_id, reset_clarification=True, increment_count=False) |
| podcast_data = fetch_all_embeddings("podcast_episodes") |
| top_workshops = find_top_workshops(user_embedding, k=10) |
| top_podcasts = find_top_k_matches(user_embedding, podcast_data, k=3) |
|
|
| enriched_podcast_links = [] |
| for _, podcast_id, _ in top_podcasts: |
| row = fetch_row_by_id("podcast_episodes", podcast_id) |
| enriched_podcast_links.extend(generate_enriched_links(row)) |
|
|
| if not enriched_podcast_links: |
| fallback = fetch_row_by_id("podcast_episodes", podcast_data[0][0]) |
| enriched_podcast_links = generate_enriched_links(fallback) |
|
|
| |
| wants_details = any(syn in question.lower() for syn in DETAIL_SYNONYMS) |
| |
| final_prompt = build_enhanced_prompt( |
| question, |
| None, |
| top_workshops, |
| user_preference=user_preference, |
| enriched_podcast_links=enriched_podcast_links, |
| wants_details=wants_details, |
| current_topic=current_topic |
| ) |
|
|
| response = openai.chat.completions.create( |
| model="gpt-4", |
| messages=[ |
| {"role": "system", "content": final_prompt}, |
| {"role": "user", "content": question} |
| ] |
| ) |
|
|
| |
| log_question(question, current_session_id) |
|
|
| return response.choices[0].message.content.strip() |
|
|
| |
| |
| |
|
|
| def chat_with_bot(message, history): |
| """ |
| Process message directly without Flask API |
| |
| Args: |
| message: User's current message |
| history: Chat history (list of message dictionaries) |
| |
| Returns: |
| Updated history with new exchange |
| """ |
| global session_id |
| |
| if not message.strip(): |
| return history |
| |
| try: |
| |
| bot_reply = process_question(message, session_id) |
| except Exception as e: |
| bot_reply = f"β Error: {str(e)}" |
| |
| |
| history.append({"role": "user", "content": message}) |
| history.append({"role": "assistant", "content": bot_reply}) |
| return history |
|
|
| def reset_session(): |
| """Reset session ID for new conversation""" |
| global session_id |
| session_id = str(uuid.uuid4()) |
| return [] |
|
|
| |
| with gr.Blocks(title="Get Scene Studios Chatbot") as demo: |
| |
| gr.Markdown( |
| """ |
| # π¬ Get Scene Studios AI Chatbot |
| |
| Ask questions about acting classes, workshops and more! |
| """ |
| ) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| chatbot = gr.Chatbot( |
| label="Conversation", |
| height=500 |
| ) |
| |
| |
| with gr.Row(): |
| msg = gr.Textbox( |
| label="Your Message", |
| lines=2, |
| scale=4 |
| ) |
| submit_btn = gr.Button("Send π€", scale=1, variant="primary") |
| |
| |
| with gr.Row(): |
| clear_btn = gr.Button("Clear Chat ποΈ", scale=1) |
| reset_btn = gr.Button("New Session π", scale=1) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| submit_btn.click( |
| fn=chat_with_bot, |
| inputs=[msg, chatbot], |
| outputs=[chatbot] |
| ).then( |
| fn=lambda: "", |
| inputs=None, |
| outputs=[msg] |
| ) |
| |
| msg.submit( |
| fn=chat_with_bot, |
| inputs=[msg, chatbot], |
| outputs=[chatbot] |
| ).then( |
| fn=lambda: "", |
| inputs=None, |
| outputs=[msg] |
| ) |
| |
| clear_btn.click( |
| fn=lambda: [], |
| inputs=None, |
| outputs=[chatbot] |
| ) |
| |
| reset_btn.click( |
| fn=reset_session, |
| inputs=None, |
| outputs=[chatbot] |
| ) |
|
|
| |
| if __name__ == "__main__": |
| print("\n" + "="*60) |
| print("π¬ Get Scene Studios Chatbot") |
| print("="*60) |
| print("\nβ
No Flask API needed - all processing is done directly!") |
| print("π Gradio interface will open in your browser") |
| print("="*60 + "\n") |
| |
| demo.launch() |
|
|