CoolFace
Apppublic

shim5/mirrors

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py576 linesDownload Raw Back to root
1# -*- coding: utf-8 -*-2"""3מראות (Mirrors) - Hebrew Self-Reflective AI Agent4Main application file with Gradio interface5"""6 7import gradio as gr8import torch9from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline10import logging11import sys12from typing import List, Tuple, Optional13import os14import random15 16# Import our custom modules17from prompt_engineering import (18    DEFAULT_PARTS, 19    get_system_prompt, 20    get_initial_prompts, 21    get_part_selection_text22)23from conversation_manager import ConversationManager, ConversationState24 25# Configure logging26logging.basicConfig(level=logging.INFO)27logger = logging.getLogger(__name__)28 29class MirautrApp:30    """Main application class for מראות"""31    32    def __init__(self):33        self.model = None34        self.tokenizer = None35        self.generator = None36        self.conversation_manager = ConversationManager()37        self.model_available = False38        self.setup_model()39    40    def setup_model(self):41        """Initialize a Hebrew-capable model with proper fallback"""42        try:43            # Check environment44            is_hf_spaces = os.getenv("SPACE_ID") is not None45            is_test_mode = os.getenv("FORCE_LIGHT_MODEL") is not None46            47            logger.info(f"Environment: HF_Spaces={is_hf_spaces}, Test_Mode={is_test_mode}")48            49            # Try to load a model that can handle Hebrew50            model_name = None51            52            if is_test_mode:53                # For testing, use a small model but focus on template responses  54                logger.info("Test mode - will use template-based responses primarily")55                self.model_available = False56                return57            elif is_hf_spaces:58                # For HF Spaces, try a lightweight multilingual model59                try:60                    model_name = "microsoft/DialoGPT-small"  # Start simple, can upgrade later61                    logger.info(f"HF Spaces: Attempting to load {model_name}")62                except:63                    logger.info("HF Spaces: Model loading failed, using template responses")64                    self.model_available = False65                    return66            else:67                # For local, try better models68                possible_models = [69                    "microsoft/DialoGPT-medium",  # Better conversational model70                    "microsoft/DialoGPT-small"   # Fallback71                ]72                73                for model in possible_models:74                    try:75                        model_name = model76                        logger.info(f"Local: Attempting to load {model_name}")77                        break78                    except:79                        continue80                        81                if not model_name:82                    logger.info("Local: No suitable model found, using template responses")83                    self.model_available = False84                    return85            86            # Load the model87            if model_name:88                self.tokenizer = AutoTokenizer.from_pretrained(model_name)89                if self.tokenizer.pad_token is None:90                    self.tokenizer.pad_token = self.tokenizer.eos_token91                92                # Use CPU for stability across environments93                self.model = AutoModelForCausalLM.from_pretrained(94                    model_name,95                    torch_dtype=torch.float32,96                    low_cpu_mem_usage=True97                )98                99                self.generator = pipeline(100                    "text-generation",101                    model=self.model,102                    tokenizer=self.tokenizer,103                    max_new_tokens=50,104                    temperature=0.7,105                    do_sample=True,106                    pad_token_id=self.tokenizer.pad_token_id,107                    return_full_text=False108                )109                110                self.model_available = True111                logger.info(f"Model loaded successfully: {model_name}")112            113        except Exception as e:114            logger.warning(f"Model loading failed: {e}")115            logger.info("Falling back to template-based responses")116            self.model_available = False117    118    def generate_persona_response(self, user_message: str, conversation_state: ConversationState) -> str:119        """120        Generate persona-based response using templates with personality variations121        This is our primary response system that always works122        """123        part_info = DEFAULT_PARTS.get(conversation_state.selected_part, {})124        persona_name = conversation_state.persona_name or part_info.get("default_persona_name", "חלק פנימי")125        126        # Get conversation context for more personalized responses127        recent_context = ""128        if conversation_state.conversation_history:129            # Get last few exchanges for context130            last_messages = conversation_state.conversation_history[-4:]  # Last 2 exchanges131            recent_context = " ".join([msg["content"] for msg in last_messages])132        133        # Generate contextual responses based on part type134        if conversation_state.selected_part == "הקול הביקורתי":135            responses = [136                f"אני {persona_name}, הקול הביקורתי שלך. שמעתי מה שאמרת על '{user_message}' - אני חושב שצריך לבחון את זה יותר לעומק. מה באמת עומד מאחורי המחשבות האלה?",137                f"אני {persona_name}. מה שאמרת מעורר בי שאלות. '{user_message}' - אבל האם זה באמת המצב המלא? אולי יש כאן דברים שאתה לא רואה?",138                f"זה {persona_name} מדבר. אני שומע אותך אומר '{user_message}', אבל אני מרגיש שאנחנו צריכים להיות יותר ביקורתיים כאן. מה אתה לא מספר לעצמך?",139                f"אני {persona_name}, ואני כאן כדי לעזור לך לראות את התמונה המלאה. מה שאמרת על '{user_message}' - זה רק חצי מהסיפור, לא? בואנו נחפור עמוק יותר."140            ]141        142        elif conversation_state.selected_part == "הילד/ה הפנימית":143            responses = [144                f"אני {persona_name}, הילד/ה הפנימית שלך. מה שאמרת על '{user_message}' גורם לי להרגיש... קצת פגיע. אתה באמת שומע אותי עכשיו?",145                f"זה {persona_name}. '{user_message}' - זה מבהיל אותי קצת. אני צריך לדעת שהכל יהיה בסדר. אתה יכול להרגיע אותי?",146                f"אני {persona_name}, החלק הצעיר שלך. מה שאמרת נוגע ללב שלי. '{user_message}' - אני מרגיש שיש כאן משהו חשוב שאני צריך להבין.",147                f"זה {persona_name} מדבר בשקט. אני שומע את '{user_message}' וזה מעורר בי רגשות. האם זה בטוח לחשוב על זה? אני קצת חרד."148            ]149        150        elif conversation_state.selected_part == "המרצה":151            responses = [152                f"אני {persona_name}, המרצה שלך. שמעתי את '{user_message}' ואני רוצה לוודא שכולם יהיו בסדר עם זה. איך אנחנו יכולים לפתור את זה בצורה שתרצה את כולם?",153                f"זה {persona_name}. מה שאמרת על '{user_message}' גורם לי לדאוג - האם זה יכול לפגוע במישהו? בואנו נמצא דרך עדינה יותר להתמודד עם זה.",154                f"אני {persona_name}, ואני רוצה שכולם יהיו מרוצים כאן. '{user_message}' - זה נשמע כמו משהו שיכול ליצור מתח. איך נוכל לעשות את זה בצורה שכולם יאהבו?",155                f"זה {persona_name} מדבר. אני שומע את '{user_message}' ומיד אני חושב - מה אחרים יגידו על זה? בואנו נוודא שאנחנו לא פוגעים באף אחד."156            ]157        158        elif conversation_state.selected_part == "המגן":159            responses = [160                f"אני {persona_name}, המגן שלך. '{user_message}' - אני מעריך את המצב. האם זה בטוח? אני כאן כדי לשמור עליך מכל מה שיכול לפגוע בך.",161                f"זה {persona_name}. שמעתי מה שאמרת על '{user_message}' ואני מיד בכוננות. מה האיומים כאן? איך אני יכול להגן עליך טוב יותר?",162                f"אני {persona_name}, השומר שלך. מה שאמרת מעורר בי את האינסטינקטים המגניים. '{user_message}' - בואנו נוודא שאתה חזק מספיק להתמודד עם זה.",163                f"זה {persona_name} מדבר. אני שומע את '{user_message}' ואני חושב על אסטרטגיות הגנה. מה אנחנו צריכים לעשות כדי שתהיה בטוח?"164            ]165        166        elif conversation_state.selected_part == "הנמנע/ת":167            responses = [168                f"אני {persona_name}, הנמנע/ת שלך. מה שאמרת על '{user_message}' גורם לי לרצות להיסוג קצת. אולי... לא חייבים להתמודד עם זה עכשיו?",169                f"זה {persona_name}. '{user_message}' - זה נשמע מורכב ומפחיד. האם יש דרך להימנע מזה? לפעמים עדיף לא להיכנס למצבים קשים.",170                f"אני {persona_name}, ואני מרגיש קצת חרדה מ'{user_message}'. בואנו נחזור לזה אחר כך? אולי עכשיו זה לא הזמן המתאים.",171                f"זה {persona_name} מדבר בזהירות. מה שאמרת מעורר בי רצון לברוח. '{user_message}' - האם באמת צריך להתמודד עם זה עכשיו?"172            ]173        174        else:175            responses = [176                f"אני {persona_name}, חלק פנימי שלך. שמעתי את '{user_message}' ואני כאן כדי לשוחח איתך על זה. מה עוד אתה מרגיש לגבי המצב הזה?",177                f"זה {persona_name}. מה שאמרת מעניין אותי. '{user_message}' - בואנו נחקור את זה יחד ונבין מה זה אומר עליך.",178                f"אני {persona_name}, ואני רוצה להבין אותך טוב יותר. '{user_message}' - איך זה משפיע עליך ברמה הרגשית?",179                f"זה {persona_name} מדבר. אני שומע את '{user_message}' ואני סקרן לדעת יותר. מה עוד יש בך בנושא הזה?"180            ]181        182        # Select response based on context or randomly183        if "פחד" in user_message or "חרדה" in user_message:184            # Choose responses that address fear/anxiety185            selected_response = responses[1] if len(responses) > 1 else responses[0]186        elif "כעס" in user_message or "מרגיש רע" in user_message:187            # Choose responses that address anger/negative feelings188            selected_response = responses[2] if len(responses) > 2 else responses[0]189        else:190            # Choose randomly for variety191            selected_response = random.choice(responses)192        193        # Add user context if relevant194        if conversation_state.user_context and len(conversation_state.conversation_history) < 4:195            selected_response += f" זכור שאמרת בהתחלה: {conversation_state.user_context[:100]}..."196        197        return selected_response198    199    def generate_response(self, user_message: str, conversation_state: ConversationState) -> str:200        """201        Generate AI response - uses persona templates as primary with optional model enhancement202        """203        try:204            if not conversation_state.selected_part:205                return "אני צריך שתבחר חלק פנימי כדי לשוחח איתו."206            207            # Always generate persona-based response first (our reliable system)208            persona_response = self.generate_persona_response(user_message, conversation_state)209            210            # If model is available, try to enhance the response (but don't depend on it)211            if self.model_available and self.generator:212                try:213                    # Create a simple English prompt for the model to add conversational flow214                    english_prompt = f"User said they feel: {user_message[:50]}. Respond supportively in 1-2 sentences:"215                    216                    model_output = self.generator(english_prompt, max_new_tokens=30, temperature=0.7)217                    218                    if model_output and len(model_output) > 0:219                        # Extract any useful emotional tone or structure, but keep Hebrew content220                        model_text = model_output[0]["generated_text"].strip()221                        # Don't replace our Hebrew response, just use model for emotional context222                        logger.info(f"Model provided contextual input: {model_text[:50]}...")223                224                except Exception as model_error:225                    logger.warning(f"Model enhancement failed: {model_error}")226                    # Continue with persona response only227            228            # Always return the Hebrew persona response229            return persona_response230            231        except Exception as e:232            logger.error(f"Error generating response: {e}")233            return "סליחה, בואנו ננסה שוב. איך אתה מרגיש עכשיו?"234    235    def create_main_interface(self):236        """Create the main Gradio interface"""237        238        # Custom CSS for Hebrew support239        css = """240        .rtl {241            direction: rtl;242            text-align: right;243        }244        .hebrew-text {245            font-family: 'Segoe UI', Tahoma, Arial, sans-serif;246            direction: rtl;247            text-align: right;248        }249        .welcome-text {250            font-size: 24px;251            font-weight: bold;252            color: #2c5aa0;253            margin: 20px 0;254        }255        """256        257        with gr.Blocks(css=css, title="מראות - מרחב אישי לשיח פנימי", theme=gr.themes.Soft()) as demo:258            259            # Session state260            conversation_state = gr.State(self.conversation_manager.create_new_session())261            262            # Header263            status_message = "🤖 מערכת תגובות מותאמת אישית פעילה" if not self.model_available else "🤖 מערכת מלאה עם מודל AI פעילה"264            265            gr.HTML(f"""266            <div class="hebrew-text welcome-text" style="text-align: center;">267                🪞 מראות: מרחב אישי לשיח פנימי ומפתח עם עצמך 🪞268            </div>269            <div class="hebrew-text" style="text-align: center; margin-bottom: 20px;">270                מקום בטוח לשוחח עם החלקים השונים של עצמך ולפתח הבנה עצמית עמוקה יותר271            </div>272            <div style="background-color: #e8f5e8; border: 1px solid #4caf50; padding: 10px; margin: 10px 0; border-radius: 5px; text-align: center;">273                <strong>{status_message}</strong>274            </div>275            """)276            277            # Main interface areas278            with gr.Column():279                280                # Step 1: Initial context gathering281                with gr.Group(visible=True) as initial_step:282                    gr.Markdown("## שלב 1: ספר/ספרי על עצמך", elem_classes=["hebrew-text"])283                    284                    initial_prompts = get_initial_prompts()285                    286                    initial_choice = gr.Radio(287                        choices=[288                            ("תאר/תארי את עצמך כאדם", "describe_self"),289                            ("איך אתה חושב שאחרים רואים אותך?", "self_perception"),290                            ("איזה אתגר אתה חווה עכשיו בחיים?", "current_challenge")291                        ],292                        label="בחר/בחרי נושא לשיתוף:",293                        elem_classes=["hebrew-text"]294                    )295                    296                    user_context_input = gr.Textbox(297                        label="ספר/ספרי בכמה משפטים:",298                        placeholder="כתוב/כתבי כאן את המחשבות שלך...",299                        lines=4,300                        elem_classes=["hebrew-text"]301                    )302                    303                    continue_to_parts = gr.Button("המשך לבחירת חלק פנימי", variant="primary")304                305                # Step 2: Part selection306                with gr.Group(visible=False) as parts_step:307                    gr.Markdown("## שלב 2: בחר/בחרי חלק פנימי לשיחה", elem_classes=["hebrew-text"])308                    309                    part_selection = gr.Radio(310                        choices=[311                            ("הקול הביקורתי - החלק שמנסה להגן עליך על ידי ביקורת והכוונה", "הקול הביקורתי"),312                            ("הילד/ה הפנימית - החלק הפגיע, הצעיר והאמיתי שלך", "הילד/ה הפנימית"),313                            ("המרצה - החלק שרוצה שכולם יהיו מרוצים", "המרצה"),314                            ("המגן - החלק החזק שמגן עליך מפני פגיעות", "המגן"),315                            ("הנמנע/ת - החלק שמעדיף להימנע ממצבים מאתגרים", "הנמנע/ת")316                        ],317                        label="איזה חלק פנימי תרצה לפגוש?",318                        elem_classes=["hebrew-text"]319                    )320                    321                    # Customization options322                    with gr.Accordion("התאמה אישית (אופציונלי)", open=False):323                        persona_name = gr.Textbox(324                            label="שם לחלק הזה:",325                            placeholder="למשל: דני, מיכל, אבי...",326                            elem_classes=["hebrew-text"]327                        )328                        persona_age = gr.Textbox(329                            label="גיל או תקופת חיים:",330                            placeholder="למשל: ילד/ה, מתבגר/ת, בוגר/ת...",331                            elem_classes=["hebrew-text"]332                        )333                        persona_style = gr.Textbox(334                            label="סגנון דיבור מיוחד:",335                            placeholder="למשל: רגוש, רציני, משעשע...",336                            elem_classes=["hebrew-text"]337                        )338                    339                    start_conversation = gr.Button("התחל שיחה", variant="primary")340                341                # Step 3: Conversation interface342                with gr.Group(visible=False) as conversation_step:343                    gr.Markdown("## שיחה עם החלק הפנימי שלך", elem_classes=["hebrew-text"])344                    345                    current_part_display = gr.Markdown("", elem_classes=["hebrew-text"])346                    347                    # Chat interface348                    with gr.Row():349                        with gr.Column(scale=4):350                            chatbot = gr.Chatbot(351                                height=400,352                                label="השיחה שלך",353                                elem_classes=["hebrew-text"],354                                rtl=True355                            )356                            357                            msg_input = gr.Textbox(358                                label="ההודעה שלך:",359                                placeholder="כתוב/כתבי את המחשבות שלך כאן...",360                                lines=2,361                                elem_classes=["hebrew-text"]362                            )363                            364                            with gr.Row():365                                send_btn = gr.Button("שלח", variant="primary")366                                clear_btn = gr.Button("נקה שיחה")367                                368                        with gr.Column(scale=1):369                            gr.Markdown("### פעולות נוספות", elem_classes=["hebrew-text"])370                            change_part_btn = gr.Button("החלף חלק פנימי")371                            restart_btn = gr.Button("התחל מחדש")372            373            # Event handlers374            def process_initial_context(choice, context, state):375                """Process initial context and move to part selection"""376                if not choice or not context.strip():377                    gr.Warning("אנא בחר נושא וכתב משהו כדי להמשיך")378                    return state, gr.update(), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)379                380                state = self.conversation_manager.set_initial_context(state, choice, context)381                return (382                    state, 383                    gr.update(),384                    gr.update(visible=False), 385                    gr.update(visible=True), 386                    gr.update(visible=False)387                )388            389            def start_chat(part, p_name, p_age, p_style, state):390                """Start the conversation with selected part"""391                if not part:392                    gr.Warning("אנא בחר חלק פנימי כדי להתחיל")393                    return state, gr.update(), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update()394                395                state = self.conversation_manager.set_selected_part(396                    state, part, p_name.strip() if p_name else None, 397                    p_age.strip() if p_age else None, p_style.strip() if p_style else None398                )399                400                part_info = DEFAULT_PARTS.get(part, {})401                display_name = (p_name.strip() if p_name else None) or part_info.get("default_persona_name", "חלק פנימי")402                403                display_text = f"🗣️ כעת אתה מתשוחח עם: **{display_name}** ({part})"404                405                return (406                    state,407                    display_text,408                    gr.update(visible=False),409                    gr.update(visible=False), 410                    gr.update(visible=True),411                    []412                )413            414            def handle_message(message, history, state):415                """Handle user message and generate response"""416                if not message.strip():417                    return "", history, state418                419                # Generate response420                response = self.generate_response(message, state)421                422                # Update conversation state423                state = self.conversation_manager.add_to_history(state, message, response)424                425                # Update history for display426                history.append([message, response])427                428                return "", history, state429            430            def clear_conversation(state):431                """Clear conversation history"""432                state = self.conversation_manager.clear_conversation(state)433                return [], state434            435            def change_part():436                """Return to part selection"""437                return (438                    gr.update(visible=False),439                    gr.update(visible=True),440                    gr.update(visible=False)441                )442            443            def restart_completely():444                """Restart the entire session"""445                new_state = self.conversation_manager.create_new_session()446                return (447                    new_state,448                    gr.update(visible=True),449                    gr.update(visible=False),450                    gr.update(visible=False),451                    [],452                    "",453                    "",454                    None,455                    None,456                    "",457                    "",458                    ""459                )460            461            # Wire up event handlers462            continue_to_parts.click(463                fn=process_initial_context,464                inputs=[initial_choice, user_context_input, conversation_state],465                outputs=[conversation_state, current_part_display, initial_step, parts_step, conversation_step]466            )467            468            start_conversation.click(469                fn=start_chat,470                inputs=[part_selection, persona_name, persona_age, persona_style, conversation_state],471                outputs=[conversation_state, current_part_display, initial_step, parts_step, conversation_step, chatbot]472            )473            474            # Chat message handling475            msg_input.submit(476                fn=handle_message,477                inputs=[msg_input, chatbot, conversation_state],478                outputs=[msg_input, chatbot, conversation_state]479            )480            481            send_btn.click(482                fn=handle_message,483                inputs=[msg_input, chatbot, conversation_state],484                outputs=[msg_input, chatbot, conversation_state]485            )486            487            clear_btn.click(488                fn=clear_conversation,489                inputs=[conversation_state],490                outputs=[chatbot, conversation_state]491            )492            493            change_part_btn.click(494                fn=change_part,495                outputs=[conversation_step, parts_step, initial_step]496            )497            498            restart_btn.click(499                fn=restart_completely,500                outputs=[conversation_state, initial_step, parts_step, conversation_step, chatbot, 501                        user_context_input, current_part_display, initial_choice, part_selection,502                        persona_name, persona_age, persona_style]503            )504        505        return demo506 507def main():508    """Main function to launch the application"""509    logger.info("Starting מראות application...")510    511    try:512        app = MirautrApp()513        demo = app.create_main_interface()514        515        # Check environment516        is_hf_spaces = os.getenv("SPACE_ID") is not None517        518        logger.info(f"Launching app... HF Spaces: {is_hf_spaces}")519        520        # Unified launch configuration for both environments521        # This ensures identical experience in both local and HF Spaces522        launch_config = {523            "show_error": True,524            "show_api": False,  # Disable API docs to avoid schema issues525            "favicon_path": None,526            "auth": None,527            "enable_queue": False,  # Disable queue to prevent schema issues528            "max_threads": 1  # Limit threads for stability529        }530        531        if is_hf_spaces:532            # HF Spaces specific settings533            logger.info("Configuring for HF Spaces deployment")534            launch_config.update({535                "server_name": "0.0.0.0",536                "server_port": 7860,537                "share": False,  # HF Spaces handles public access538                "quiet": True539            })540        else:541            # Local development settings542            logger.info("Configuring for local development")543            544            # Try to find an available port545            default_port = int(os.getenv("GRADIO_SERVER_PORT", "7861"))546            available_port = default_port547            548            # Check if port is available, if not find next available549            import socket550            for port_try in range(default_port, default_port + 10):551                try:552                    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:553                        s.bind(('127.0.0.1', port_try))554                        available_port = port_try555                        break556                except OSError:557                    continue558            559            logger.info(f"Using port {available_port} for local development")560            561            launch_config.update({562                "server_name": "127.0.0.1", 563                "server_port": available_port,564                "share": True,  # Enable share for local testing to avoid localhost issues565                "inbrowser": True,  # Auto-open browser566                "quiet": False567            })568        569        demo.launch(**launch_config)570            571    except Exception as e:572        logger.error(f"Failed to start application: {e}")573        raise574 575if __name__ == "__main__":576    main()