feat: complete local integration of AI mind codebase (brain, memory, etc.) in text2video, supporting both Direct Codebase execution and Remote HTTP fallback
3d7a63c | # core/learning.py | |
| import re | |
| import json | |
| import threading | |
| from datetime import datetime | |
| from memory import ConversationMemory | |
| import sqlite3 | |
| class LearningEngine: | |
| """ | |
| Self-learning engine that continuously observes user conversations, | |
| extracts communication patterns, interests, and preferences, | |
| and consolidates them into an adaptive user model. | |
| The more a user interacts, the better Invicta understands them. | |
| """ | |
| def __init__(self, memory: ConversationMemory): | |
| self.memory = memory | |
| self._lock = threading.Lock() | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # REAL-TIME MESSAGE ANALYSIS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def analyze_message(self, user_id, message): | |
| if not message or not user_id: | |
| return | |
| try: | |
| self.memory.increment_interaction_count(user_id) | |
| self._detect_interests(user_id, message) | |
| self._detect_communication_style(user_id, message) | |
| self._detect_emotional_state(user_id, message) | |
| self._detect_preferences(user_id, message) | |
| self._detect_language(user_id, message) # β ADD THIS | |
| except Exception as e: | |
| print(f"β οΈ Learning analysis error: {e}") | |
| def _detect_interests(self, user_id, message): | |
| """Detect topics the user is interested in based on what they ask about.""" | |
| interest_patterns = [ | |
| # Technology | |
| (r'\b(?:python|javascript|typescript|rust|golang|programming|coding|developer|software|algorithm|api|database|web\s*dev|machine\s*learning|deep\s*learning|ai|artificial\s*intelligence|neural\s*network|data\s*science|cybersecurity|blockchain|crypto|cloud\s*computing|devops)\b', 'technology'), | |
| # Science | |
| (r'\b(?:physics|chemistry|biology|quantum|astronomy|space|nasa|evolution|genetics|neuroscience|psychology|math|calculus|statistics|research)\b', 'science'), | |
| # Business & Finance | |
| (r'\b(?:startup|business|entrepreneur|invest|stock|market|finance|crypto|trading|marketing|sales|revenue|profit|startup|company|venture)\b', 'business'), | |
| # Creative | |
| (r'\b(?:design|art|music|writing|creative|paint|draw|compose|fiction|novel|poetry|photography|filmmaking|video|animation)\b', 'creative'), | |
| # Health & Fitness | |
| (r'\b(?:fitness|workout|exercise|diet|nutrition|health|medical|yoga|meditation|mental\s*health|therapy|wellness|gym|running|weight)\b', 'health'), | |
| # Gaming | |
| (r'\b(?:game|gaming|gamer|xbox|playstation|nintendo|steam|esports|rpg|fps|mmorpg|minecraft|fortnite)\b', 'gaming'), | |
| # Travel | |
| (r'\b(?:travel|trip|vacation|flight|hotel|country|city|backpack|adventure|explore|destination|tourist|visa)\b', 'travel'), | |
| # Food & Cooking | |
| (r'\b(?:recipe|cooking|food|cuisine|bake|chef|restaurant|meal|ingredient|kitchen|dish)\b', 'food'), | |
| # Education | |
| (r'\b(?:study|university|college|school|learn|course|degree|exam|academic|education|teach|student|homework|tutorial)\b', 'education'), | |
| # Philosophy & Religion | |
| (r'\b(?:philosophy|existential|meaning\s*of\s*life|moral|ethics|religion|spiritual|meditation|mindfulness|consciousness)\b', 'philosophy'), | |
| ] | |
| msg_lower = message.lower() | |
| for pattern, category in interest_patterns: | |
| matches = re.findall(pattern, msg_lower) | |
| if matches: | |
| # Store each matched keyword as an interest observation | |
| for match in matches: | |
| keyword = match.strip().lower() | |
| self.memory.store_learning( | |
| user_id=user_id, | |
| category="interest", | |
| key=keyword, | |
| value=f"Interested in {category}: {keyword}", | |
| confidence=0.4 | |
| ) | |
| def _detect_language(self, user_id, message): | |
| """Detect if user is writing in a non-English language and store preference.""" | |
| # Simple detection for common scripts | |
| import re | |
| if re.search(r'[\u0900-\u097F]', message): # Devanagari (Hindi) | |
| self.memory.store_learning(user_id, "preference", "language", "hindi", 0.6) | |
| elif re.search(r'[\u0600-\u06FF]', message): # Arabic | |
| self.memory.store_learning(user_id, "preference", "language", "arabic", 0.6) | |
| elif re.search(r'[\u3040-\u30FF]', message): # Japanese | |
| self.memory.store_learning(user_id, "preference", "language", "japanese", 0.6) | |
| elif re.search(r'[\u4E00-\u9FFF]', message): # Chinese | |
| self.memory.store_learning(user_id, "preference", "language", "chinese", 0.6) | |
| elif re.search(r'[\uAC00-\uD7AF]', message): # Korean | |
| self.memory.store_learning(user_id, "preference", "language", "korean", 0.6) | |
| elif re.search(r'[ÑéΓΓ³ΓΊΓ±ΒΏΒ‘]', message, re.IGNORECASE): # Spanish | |
| self.memory.store_learning(user_id, "preference", "language", "spanish", 0.3) | |
| def _detect_communication_style(self, user_id, message): | |
| """Detect how the user communicates β formality, length, tone.""" | |
| # ββ FIX: msg_lower was missing here, causing the crash ββ | |
| msg_lower = message.lower() | |
| words = message.split() | |
| word_count = len(words) | |
| # ββ Formality detection ββ | |
| formal_indicators = sum(1 for w in words if w.lower() in { | |
| "therefore", "however", "furthermore", "consequently", "nevertheless", | |
| "accordingly", "moreover", "henceforth", "would", "shall", "may", | |
| "perhaps", "kindly", "regards", "sincerely", "please", "respectfully", | |
| }) | |
| casual_indicators = sum(1 for w in words if w.lower() in { | |
| "lol", "haha", "yeah", "nah", "yep", "nope", "gonna", "wanna", | |
| "gotta", "dunno", "kinda", "sorta", "btw", "omg", "wtf", "lmao", | |
| "bruh", "dude", "hey", "yo", "sup", "ngl", "fr", "tbh", "imo", | |
| }) | |
| contractions = sum(1 for w in words if re.match(r"\w+'\w+", w)) | |
| if formal_indicators > 2 or (formal_indicators > 0 and casual_indicators == 0 and contractions == 0): | |
| self.memory.store_learning(user_id, "style", "formality_signal", "formal", 0.3) | |
| elif casual_indicators > 0 or contractions > 2: | |
| self.memory.store_learning(user_id, "style", "formality_signal", "casual", 0.3) | |
| # ββ Response length preference ββ | |
| if word_count <= 5: | |
| self.memory.store_learning(user_id, "style", "length_signal", "short", 0.25) | |
| elif word_count <= 20: | |
| self.memory.store_learning(user_id, "style", "length_signal", "medium", 0.2) | |
| else: | |
| self.memory.store_learning(user_id, "style", "length_signal", "detailed", 0.25) | |
| # ββ Tone detection ββ | |
| # Question-heavy = curious | |
| questions = message.count('?') | |
| exclamations = message.count('!') | |
| emojis = len(re.findall(r'[π₯π‘β€οΈππππ€ππβ¨π―πͺππ§ βππ«]', message)) | |
| if questions >= 2: | |
| self.memory.store_learning(user_id, "style", "tone_signal", "curious", 0.3) | |
| if exclamations >= 2 or emojis >= 2: | |
| self.memory.store_learning(user_id, "style", "tone_signal", "enthusiastic", 0.3) | |
| if any(phrase in msg_lower for phrase in ['help me', 'can you', 'please', 'how do i', 'how to']): | |
| self.memory.store_learning(user_id, "style", "tone_signal", "practical", 0.25) | |
| # Detect humor/wit | |
| humor_words = ['joke', 'funny', 'lol', 'haha', 'lmao', 'pun', 'humor'] | |
| if any(w in msg_lower for w in humor_words): | |
| self.memory.store_learning(user_id, "style", "tone_signal", "witty", 0.35) | |
| def _detect_emotional_state(self, user_id, message): | |
| """Detect the user's emotional state from their messages.""" | |
| msg_lower = message.lower() | |
| # Stress/frustration | |
| stress_words = ['stressed', 'frustrated', 'overwhelmed', 'anxious', 'worried', | |
| 'tired', 'exhausted', 'burnt out', 'burnout', 'struggling', | |
| 'can\'t handle', 'too much', 'losing hope'] | |
| if any(w in msg_lower for w in stress_words): | |
| self.memory.store_learning(user_id, "emotion", "recent_state", "stressed", 0.5) | |
| self.memory.store_learning(user_id, "emotion", "stress_prone", "yes", 0.2) | |
| # Happiness/excitement | |
| happy_words = ['excited', 'happy', 'great', 'awesome', 'amazing', 'wonderful', | |
| 'fantastic', 'love it', 'so good', 'thrilled', 'pumped', 'stoked'] | |
| if any(w in msg_lower for w in happy_words): | |
| self.memory.store_learning(user_id, "emotion", "recent_state", "happy", 0.5) | |
| # Sadness | |
| sad_words = ['sad', 'depressed', 'lonely', 'miss', 'lost', 'heartbroken', | |
| 'crying', 'hurt', 'pain', 'grief', 'mourn'] | |
| if any(w in msg_lower for w in sad_words): | |
| self.memory.store_learning(user_id, "emotion", "recent_state", "sad", 0.5) | |
| # Curiosity | |
| curious_words = ['wonder', 'curious', 'interesting', 'fascinating', 'tell me more', | |
| 'how come', 'why is', 'what if', 'explain'] | |
| if any(w in msg_lower for w in curious_words): | |
| self.memory.store_learning(user_id, "emotion", "recent_state", "curious", 0.4) | |
| def _detect_preferences(self, user_id, message): | |
| """Detect explicit and implicit preferences.""" | |
| msg_lower = message.lower() | |
| # Explicit preferences | |
| pref_patterns = [ | |
| (r"i (?:prefer|like|want|need) (?:my (?:answers?|responses?) )?(?:to be )?(short|brief|concise)", "short"), | |
| (r"i (?:prefer|like|want|need) (?:my (?:answers?|responses?) )?(?:to be )?(long|detailed|thorough|in.depth|comprehensive)", "detailed"), | |
| (r"i (?:prefer|like|want|need) (?:my (?:answers?|responses?) )?(?:to be )?(simple|easy|basic|beginner)", "simple"), | |
| (r"i (?:prefer|like|want|need) (?:my (?:answers?|responses?) )?(?:to be )?(technical|advanced|complex)", "technical"), | |
| (r"(?:don't|do not) (?:use|give me) (?:code|programming)", "no_code"), | |
| (r"(?:give|show) me (?:the )?code", "wants_code"), | |
| (r"(?:give|show) me (?:an )?example", "wants_examples"), | |
| (r"(?:step.by.step|step by step)", "step_by_step"), | |
| ] | |
| for pattern, pref in pref_patterns: | |
| if re.search(pattern, msg_lower): | |
| self.memory.store_learning(user_id, "preference", pref, "true", 0.6) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONSOLIDATION (runs periodically) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_daily_consolidation(self): | |
| """ | |
| Consolidate raw learning observations into user insights. | |
| Called periodically by the background thread. | |
| """ | |
| try: | |
| # Get all users who have learning data | |
| all_users = self._get_active_users() | |
| for user_id in all_users: | |
| try: | |
| self._consolidate_user(user_id) | |
| except Exception as e: | |
| print(f"β οΈ Consolidation error for user {user_id}: {e}") | |
| except Exception as e: | |
| print(f"β οΈ Daily consolidation error: {e}") | |
| def _get_active_users(self): | |
| """Get list of user IDs that have learning data.""" | |
| db_path = self.memory.db_path | |
| conn = sqlite3.connect(db_path) | |
| conn.row_factory = sqlite3.Row | |
| cur = conn.cursor() | |
| cur.execute("SELECT DISTINCT user_id FROM user_learning") | |
| rows = cur.fetchall() | |
| conn.close() | |
| return [row["user_id"] for row in rows] | |
| def _consolidate_user(self, user_id): | |
| """Consolidate all learning data for one user into insights.""" | |
| insights = self.memory.get_user_insights(user_id) or {} | |
| # ββ Formality ββ | |
| style_data = self.memory.get_learning_by_category(user_id, "style") | |
| formality_signals = [] | |
| for key, val in style_data.items(): | |
| if key == "formality_signal": | |
| formality_signals.append(val) | |
| if formality_signals: | |
| # Most frequent signal wins | |
| from collections import Counter | |
| most_common = Counter(formality_signals).most_common(1)[0][0] | |
| insights["preferred_formality"] = most_common | |
| # ββ Response length ββ | |
| length_signals = [v for k, v in style_data.items() if k == "length_signal"] | |
| if length_signals: | |
| from collections import Counter | |
| most_common = Counter(length_signals).most_common(1)[0][0] | |
| insights["preferred_response_length"] = most_common | |
| # ββ Tone ββ | |
| tone_signals = [v for k, v in style_data.items() if k == "tone_signal"] | |
| if tone_signals: | |
| from collections import Counter | |
| most_common = Counter(tone_signals).most_common(1)[0][0] | |
| insights["preferred_tone"] = most_common | |
| # ββ Topics of interest ββ | |
| top_topics = self.memory.get_top_learning_topics(user_id, limit=15) | |
| interest_list = [t["key"] for t in top_topics if t.get("times_observed", 0) >= 2] | |
| if interest_list: | |
| insights["topics_of_interest"] = interest_list[:10] | |
| # ββ Communication patterns ββ | |
| patterns = {} | |
| pref_data = self.memory.get_learning_by_category(user_id, "preference") | |
| for k, v in pref_data.items(): | |
| patterns[k] = v | |
| if patterns: | |
| insights["communication_patterns"] = patterns | |
| # Save consolidated insights | |
| self.memory.update_user_insights(user_id, insights) | |
| # Prune old learning data | |
| self.memory.prune_old_learning(user_id, max_entries=500) | |
| print(f"π§ Consolidated learning for user {user_id}") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # USER MODEL (used by Brain to adapt responses) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_user_model(self, user_id): | |
| """ | |
| Build the current user model from consolidated insights | |
| + real-time learning data. Used by Brain._build_adaptation_prompt(). | |
| """ | |
| if not user_id: | |
| return None | |
| insights = self.memory.get_user_insights(user_id) | |
| model = {} | |
| # Formality | |
| model["formality"] = insights.get("preferred_formality", "casual") if insights else "casual" | |
| # Response length | |
| model["response_length"] = insights.get("preferred_response_length", "medium") if insights else "medium" | |
| # Tone | |
| model["tone"] = insights.get("preferred_tone", "friendly") if insights else "friendly" | |
| # Interests | |
| model["interests"] = insights.get("topics_of_interest", []) if insights else [] | |
| # Preferences | |
| if insights and insights.get("communication_patterns"): | |
| model["preferences"] = insights["communication_patterns"] | |
| else: | |
| model["preferences"] = {} | |
| # Emotional state (from real-time learning) | |
| emotion_data = self.memory.get_learning_by_category(user_id, "emotion") | |
| recent_state = emotion_data.get("recent_state", "") | |
| if recent_state: | |
| model["recent_emotional_state"] = recent_state | |
| # Interaction count | |
| if insights: | |
| model["interaction_count"] = insights.get("total_interactions", 0) | |
| else: | |
| model["interaction_count"] = 0 | |
| return model |