FocusFlowAI / core /focus_check.py
avaliev's picture
Initial commit
0491e54 verified
"""
Focus Monitoring Logic.
"""
import time
import json
from typing import List, Optional, Tuple, Any
class FocusMonitor:
def __init__(self, task_manager, file_monitor, metrics_tracker, voice_generator=None):
self.task_manager = task_manager
self.file_monitor = file_monitor
self.metrics_tracker = metrics_tracker
self.voice_generator = voice_generator
self.focus_agent = None
self.consecutive_distracted = 0
self.activity_log: List[str] = []
self.demo_text_content = ""
self.launch_mode = "demo" # Default
def set_agent(self, agent):
self.focus_agent = agent
def set_launch_mode(self, mode: str):
self.launch_mode = mode
def update_demo_text(self, text: str) -> str:
"""Update demo text content (demo mode only)."""
self.demo_text_content = text
return f"βœ… Text updated ({len(text)} characters)"
def get_activity_summary(self, monitoring_active: bool) -> str:
"""Get recent activity summary."""
if self.launch_mode == "demo":
return f"πŸ“ Demo text content: {len(self.demo_text_content)} characters"
if not monitoring_active:
return "⏸️ Monitoring is not active"
recent = self.file_monitor.get_recent_activity(5)
if not recent:
return "πŸ’€ No recent file activity"
summary = []
for event in recent:
summary.append(f"β€’ {event['type'].upper()}: {event['filename']}")
return "\n".join(summary)
def run_check(self) -> Tuple[str, Optional[str], Optional[Any]]:
"""
Run the focus check analysis with distraction escalation.
Returns:
Tuple[log_string, alert_js, voice_audio]
"""
if not self.focus_agent:
return "⚠️ Agent not initialized. Check environment variables.", None, None
active_task = self.task_manager.get_active_task()
# Get recent activity based on mode
if self.launch_mode == "demo":
# In demo mode, create synthetic activity from text content
recent_activity = [{
'type': 'text_edit',
'filename': 'demo_workspace',
'content': self.demo_text_content[-500:] if self.demo_text_content else "",
'timestamp': time.time()
}] if self.demo_text_content else []
else:
recent_activity = self.file_monitor.get_recent_activity(10)
result = self.focus_agent.analyze(active_task, recent_activity)
verdict = result.get("verdict", "Unknown")
message = result.get("message", "No message")
# Handle distraction escalation logic
if verdict == "On Track":
# Reset counter when back on track
self.consecutive_distracted = 0
elif verdict == "Distracted":
# Increment distraction counter
self.consecutive_distracted += 1
# Log to metrics if we have an active task
if active_task:
self.metrics_tracker.log_focus_check(
active_task['id'],
active_task['title'],
verdict,
message
)
# Determine emoji
emoji = "βœ…" if verdict == "On Track" else "⚠️" if verdict == "Distracted" else "πŸ’€"
log_entry = f"{emoji} [{verdict}] {message}"
self.activity_log.append(log_entry)
# Keep only last 20 entries
if len(self.activity_log) > 20:
self.activity_log.pop(0)
# Generate voice feedback (optional, graceful if unavailable)
voice_audio = None
if self.voice_generator:
try:
voice_audio = self.voice_generator.get_focus_message_audio(verdict, message)
except Exception as e:
print(f"Voice generation error: {e}")
# Trigger browser alert and audio for distracted/idle status with escalation
alert_js = None
if verdict in ["Distracted", "Idle"]:
safe_message = json.dumps(message)
# Escalation logic:
# 1st distraction: play sound only
# 2nd distraction: play sound again
# 3rd+ distraction: add voice feedback
# play_voice = self.consecutive_distracted >= 3 # Logic handled by caller or voice_audio presence?
# Actually voice_audio is generated regardless, but maybe we only play it if distracted?
# The original code generated it always if available.
alert_js = f"""
() => {{
const audio = document.getElementById('nudge-alert');
if (audio) {{
audio.currentTime = 0;
audio.play().catch(e => console.log('Audio play failed:', e));
}}
if (Notification.permission === "granted") {{
new Notification("FocusFlow Alert πŸ¦‰", {{
body: {safe_message},
icon: "https://em-content.zobj.net/thumbs/160/apple/354/owl_1f989.png"
}});
}}
return null;
}}
"""
return "\n".join(self.activity_log), alert_js, voice_audio