# -*- coding: utf-8 -*- """ מראות (Mirrors) - Hebrew Self-Reflective AI Agent Main application file with Gradio interface """ import gradio as gr import torch from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline import logging import sys from typing import List, Tuple, Optional import os import random # Import our custom modules from prompt_engineering import ( DEFAULT_PARTS, get_system_prompt, get_initial_prompts, get_part_selection_text ) from conversation_manager import ConversationManager, ConversationState # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class MirautrApp: """Main application class for מראות""" def __init__(self): self.model = None self.tokenizer = None self.generator = None self.conversation_manager = ConversationManager() self.model_available = False self.setup_model() def setup_model(self): """Initialize a Hebrew-capable model with proper fallback""" try: # Check environment is_hf_spaces = os.getenv("SPACE_ID") is not None is_test_mode = os.getenv("FORCE_LIGHT_MODEL") is not None logger.info(f"Environment: HF_Spaces={is_hf_spaces}, Test_Mode={is_test_mode}") # Try to load a model that can handle Hebrew model_name = None if is_test_mode: # For testing, use a small model but focus on template responses logger.info("Test mode - will use template-based responses primarily") self.model_available = False return elif is_hf_spaces: # For HF Spaces, try a lightweight multilingual model try: model_name = "microsoft/DialoGPT-small" # Start simple, can upgrade later logger.info(f"HF Spaces: Attempting to load {model_name}") except: logger.info("HF Spaces: Model loading failed, using template responses") self.model_available = False return else: # For local, try better models possible_models = [ "microsoft/DialoGPT-medium", # Better conversational model "microsoft/DialoGPT-small" # Fallback ] for model in possible_models: try: model_name = model logger.info(f"Local: Attempting to load {model_name}") break except: continue if not model_name: logger.info("Local: No suitable model found, using template responses") self.model_available = False return # Load the model if model_name: self.tokenizer = AutoTokenizer.from_pretrained(model_name) if self.tokenizer.pad_token is None: self.tokenizer.pad_token = self.tokenizer.eos_token # Use CPU for stability across environments self.model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.float32, low_cpu_mem_usage=True ) self.generator = pipeline( "text-generation", model=self.model, tokenizer=self.tokenizer, max_new_tokens=50, temperature=0.7, do_sample=True, pad_token_id=self.tokenizer.pad_token_id, return_full_text=False ) self.model_available = True logger.info(f"Model loaded successfully: {model_name}") except Exception as e: logger.warning(f"Model loading failed: {e}") logger.info("Falling back to template-based responses") self.model_available = False def generate_persona_response(self, user_message: str, conversation_state: ConversationState) -> str: """ Generate persona-based response using templates with personality variations This is our primary response system that always works """ part_info = DEFAULT_PARTS.get(conversation_state.selected_part, {}) persona_name = conversation_state.persona_name or part_info.get("default_persona_name", "חלק פנימי") # Get conversation context for more personalized responses recent_context = "" if conversation_state.conversation_history: # Get last few exchanges for context last_messages = conversation_state.conversation_history[-4:] # Last 2 exchanges recent_context = " ".join([msg["content"] for msg in last_messages]) # Generate contextual responses based on part type if conversation_state.selected_part == "הקול הביקורתי": responses = [ f"אני {persona_name}, הקול הביקורתי שלך. שמעתי מה שאמרת על '{user_message}' - אני חושב שצריך לבחון את זה יותר לעומק. מה באמת עומד מאחורי המחשבות האלה?", f"אני {persona_name}. מה שאמרת מעורר בי שאלות. '{user_message}' - אבל האם זה באמת המצב המלא? אולי יש כאן דברים שאתה לא רואה?", f"זה {persona_name} מדבר. אני שומע אותך אומר '{user_message}', אבל אני מרגיש שאנחנו צריכים להיות יותר ביקורתיים כאן. מה אתה לא מספר לעצמך?", f"אני {persona_name}, ואני כאן כדי לעזור לך לראות את התמונה המלאה. מה שאמרת על '{user_message}' - זה רק חצי מהסיפור, לא? בואנו נחפור עמוק יותר." ] elif conversation_state.selected_part == "הילד/ה הפנימית": responses = [ f"אני {persona_name}, הילד/ה הפנימית שלך. מה שאמרת על '{user_message}' גורם לי להרגיש... קצת פגיע. אתה באמת שומע אותי עכשיו?", f"זה {persona_name}. '{user_message}' - זה מבהיל אותי קצת. אני צריך לדעת שהכל יהיה בסדר. אתה יכול להרגיע אותי?", f"אני {persona_name}, החלק הצעיר שלך. מה שאמרת נוגע ללב שלי. '{user_message}' - אני מרגיש שיש כאן משהו חשוב שאני צריך להבין.", f"זה {persona_name} מדבר בשקט. אני שומע את '{user_message}' וזה מעורר בי רגשות. האם זה בטוח לחשוב על זה? אני קצת חרד." ] elif conversation_state.selected_part == "המרצה": responses = [ f"אני {persona_name}, המרצה שלך. שמעתי את '{user_message}' ואני רוצה לוודא שכולם יהיו בסדר עם זה. איך אנחנו יכולים לפתור את זה בצורה שתרצה את כולם?", f"זה {persona_name}. מה שאמרת על '{user_message}' גורם לי לדאוג - האם זה יכול לפגוע במישהו? בואנו נמצא דרך עדינה יותר להתמודד עם זה.", f"אני {persona_name}, ואני רוצה שכולם יהיו מרוצים כאן. '{user_message}' - זה נשמע כמו משהו שיכול ליצור מתח. איך נוכל לעשות את זה בצורה שכולם יאהבו?", f"זה {persona_name} מדבר. אני שומע את '{user_message}' ומיד אני חושב - מה אחרים יגידו על זה? בואנו נוודא שאנחנו לא פוגעים באף אחד." ] elif conversation_state.selected_part == "המגן": responses = [ f"אני {persona_name}, המגן שלך. '{user_message}' - אני מעריך את המצב. האם זה בטוח? אני כאן כדי לשמור עליך מכל מה שיכול לפגוע בך.", f"זה {persona_name}. שמעתי מה שאמרת על '{user_message}' ואני מיד בכוננות. מה האיומים כאן? איך אני יכול להגן עליך טוב יותר?", f"אני {persona_name}, השומר שלך. מה שאמרת מעורר בי את האינסטינקטים המגניים. '{user_message}' - בואנו נוודא שאתה חזק מספיק להתמודד עם זה.", f"זה {persona_name} מדבר. אני שומע את '{user_message}' ואני חושב על אסטרטגיות הגנה. מה אנחנו צריכים לעשות כדי שתהיה בטוח?" ] elif conversation_state.selected_part == "הנמנע/ת": responses = [ f"אני {persona_name}, הנמנע/ת שלך. מה שאמרת על '{user_message}' גורם לי לרצות להיסוג קצת. אולי... לא חייבים להתמודד עם זה עכשיו?", f"זה {persona_name}. '{user_message}' - זה נשמע מורכב ומפחיד. האם יש דרך להימנע מזה? לפעמים עדיף לא להיכנס למצבים קשים.", f"אני {persona_name}, ואני מרגיש קצת חרדה מ'{user_message}'. בואנו נחזור לזה אחר כך? אולי עכשיו זה לא הזמן המתאים.", f"זה {persona_name} מדבר בזהירות. מה שאמרת מעורר בי רצון לברוח. '{user_message}' - האם באמת צריך להתמודד עם זה עכשיו?" ] else: responses = [ f"אני {persona_name}, חלק פנימי שלך. שמעתי את '{user_message}' ואני כאן כדי לשוחח איתך על זה. מה עוד אתה מרגיש לגבי המצב הזה?", f"זה {persona_name}. מה שאמרת מעניין אותי. '{user_message}' - בואנו נחקור את זה יחד ונבין מה זה אומר עליך.", f"אני {persona_name}, ואני רוצה להבין אותך טוב יותר. '{user_message}' - איך זה משפיע עליך ברמה הרגשית?", f"זה {persona_name} מדבר. אני שומע את '{user_message}' ואני סקרן לדעת יותר. מה עוד יש בך בנושא הזה?" ] # Select response based on context or randomly if "פחד" in user_message or "חרדה" in user_message: # Choose responses that address fear/anxiety selected_response = responses[1] if len(responses) > 1 else responses[0] elif "כעס" in user_message or "מרגיש רע" in user_message: # Choose responses that address anger/negative feelings selected_response = responses[2] if len(responses) > 2 else responses[0] else: # Choose randomly for variety selected_response = random.choice(responses) # Add user context if relevant if conversation_state.user_context and len(conversation_state.conversation_history) < 4: selected_response += f" זכור שאמרת בהתחלה: {conversation_state.user_context[:100]}..." return selected_response def generate_response(self, user_message: str, conversation_state: ConversationState) -> str: """ Generate AI response - uses persona templates as primary with optional model enhancement """ try: if not conversation_state.selected_part: return "אני צריך שתבחר חלק פנימי כדי לשוחח איתו." # Always generate persona-based response first (our reliable system) persona_response = self.generate_persona_response(user_message, conversation_state) # If model is available, try to enhance the response (but don't depend on it) if self.model_available and self.generator: try: # Create a simple English prompt for the model to add conversational flow english_prompt = f"User said they feel: {user_message[:50]}. Respond supportively in 1-2 sentences:" model_output = self.generator(english_prompt, max_new_tokens=30, temperature=0.7) if model_output and len(model_output) > 0: # Extract any useful emotional tone or structure, but keep Hebrew content model_text = model_output[0]["generated_text"].strip() # Don't replace our Hebrew response, just use model for emotional context logger.info(f"Model provided contextual input: {model_text[:50]}...") except Exception as model_error: logger.warning(f"Model enhancement failed: {model_error}") # Continue with persona response only # Always return the Hebrew persona response return persona_response except Exception as e: logger.error(f"Error generating response: {e}") return "סליחה, בואנו ננסה שוב. איך אתה מרגיש עכשיו?" def create_main_interface(self): """Create the main Gradio interface""" # Custom CSS for Hebrew support css = """ .rtl { direction: rtl; text-align: right; } .hebrew-text { font-family: 'Segoe UI', Tahoma, Arial, sans-serif; direction: rtl; text-align: right; } .welcome-text { font-size: 24px; font-weight: bold; color: #2c5aa0; margin: 20px 0; } """ with gr.Blocks(css=css, title="מראות - מרחב אישי לשיח פנימי", theme=gr.themes.Soft()) as demo: # Session state conversation_state = gr.State(self.conversation_manager.create_new_session()) # Header status_message = "🤖 מערכת תגובות מותאמת אישית פעילה" if not self.model_available else "🤖 מערכת מלאה עם מודל AI פעילה" gr.HTML(f"""