""" modules/conversation.py — FRIDAY Continuous Conversation Engine REMOVES THE WAKE WORD BARRIER: - Free-talk mode - no "hey FRIDAY" needed - Context carries across turns - Intent detection, not just commands - Seamless multi-turn conversations """ import time import json import os from config import DATA_DIR CONV_FILE = os.path.join(DATA_DIR, "conversation.json") def _load() -> dict: if not os.path.exists(CONV_FILE): return {"mode": "command", "context": {}, "last_intent": "", "free_talk": False} try: with open(CONV_FILE, "r") as f: return json.load(f) except Exception: return {"mode": "command", "context": {}, "last_intent": "", "free_talk": False} def _save(data: dict): os.makedirs(DATA_DIR, exist_ok=True) try: with open(CONV_FILE, "w") as f: json.dump(data, f, indent=2) except Exception: pass # ── Mode Control ──────────────────────────────────────────────────────── def enable_free_talk(enabled: bool = True): """Enable/disable free-talk mode.""" data = _load() data["free_talk"] = enabled data["mode"] = "free_talk" if enabled else "command" _save(data) def is_free_talk() -> bool: """Check if free-talk is enabled.""" return _load().get("free_talk", False) def get_mode() -> str: """Get current conversation mode.""" return _load().get("mode", "command") # ── Intent Detection ──────────────────────────────────────────── def detect_intent(text: str) -> str: """Detect intent from text.""" t = (text or "").lower() # Question intents if any(w in t for w in ["what", "how", "why", "when", "where", "which", "?"]): return "question" # Command intents if any(t.startswith(w) for w in ["open", "close", "start", "stop", "play", "pause", "set", "turn"]): return "command" # Request intents if any(w in t for w in ["can you", "please", "would you", "could", "help me"]): return "request" # Statement intents if any(w in t for w in ["i am", "i'm", "feeling", "working on", "doing"]): return "statement" # Opinion intents if any(w in t for w in ["think", "believe", "opinion", "should i"]): return "opinion" # Greeting intents if any(w in t for w in ["hey", "hi", "hello", "yo", "bro"]): return "greeting" # Emotional intents if any(w in t for w in ["frustrated", "annoyed", "happy", "excited", "sad", "tired"]): return "emotion" return "statement" # ── Context Management ──────────────────────────────────────────── def set_context(key: str, value): """Set conversation context.""" data = _load() data.setdefault("context", {})[key] = value _save(data) def get_context(key: str): """Get conversation context.""" return _load().get("context", {}).get(key) def clear_context(): """Clear conversation context.""" data = _load() data["context"] = {} _save(data) # ── Multi-turn Continuation ───────────────────────────────────────── def is_follow_up(text: str) -> bool: """Check if this is a follow-up to previous turn.""" t = (text or "").lower() # Follow-up words follow_ups = ["that", "it", "them", "this", "also", "and", "but", "again", "more", "yes", "no", "ok", "sure"] # Pronouns that need context pronouns = ["it", "that", "this", "them", "they", "he", "she", "you", "we"] # Check for follow-up patterns first_word = t.split()[0] if t.split() else "" if first_word in follow_ups: return True # Short responses that need context if len(t.split()) <= 3 and first_word in pronouns: return True return False def get_follow_up_context() -> dict: """Get context needed for follow-up.""" data = _load() return data.get("context", {}) # ── Should Respond ───────────────────────────────────────────────── def should_respond(text: str) -> bool: """Decide if FRIDAY should respond without wake word.""" # Check free-talk mode if not is_free_talk(): return False t = (text or "").strip() if not t: return False # Check for name mentions if "friday" in t.lower() or "jarvis" in t.lower(): return True # Check for direct address if any(t.lower().startswith(w) for w in ["hey", "hi", "yo", "bro", "ok"]): return True # Follow-ups always respond if is_follow_up(t): return True # Check for question if "?" in t: return True # Short commands without wake word if detect_intent(t) == "command" and len(t.split()) <= 4: return True return False # ── Natural Response Generation ──────────────────────────────────────── def get_natural_response(intent: str, context: dict) -> str: """Generate natural response based on intent.""" responses = { "question": [ "Good question. Let me think.", "Here's what I know:", "Let me look into that.", ], "command": [ "On it.", "Done.", "Consider it done.", ], "request": [ "Got it.", "Sure thing.", "I'll handle it.", ], "statement": [ "Interesting.", "Got it.", "Noted.", ], "emotion": [ "I hear you.", "Got it.", "I'm here.", ], } intents = responses.get(intent, responses["statement"]) import random return random.choice(intents) # ── Brain Integration ───────────────────────────────────────── def prepare_for_brain(text: str) -> dict: """Prepare conversation context for brain.""" intent = detect_intent(text) data = _load() # Update context data["last_intent"] = intent data["last_text"] = text data["last_time"] = time.time() _save(data) return { "mode": get_mode(), "intent": intent, "context": get_follow_up_context(), "is_follow_up": is_follow_up(text), "should_respond": should_respond(text), } # ── Toggle via Command ────────────────────────────────────────── def toggle_free_talk_command(enable: bool = None) -> str: """Toggle free-talk mode via voice command.""" if enable is None: enable = not is_free_talk() enable_free_talk(enable) if enable: return "Free talk enabled. Just talk to me." else: return "Free talk disabled. Say my name to get my attention."