Spaces:
Sleeping
Sleeping
| """ | |
| Enhanced Timer Extension | |
| Set and track timers/countdowns with proactive notifications | |
| Now with better state management and orchestrator integration | |
| """ | |
| from base_extension import BaseExtension | |
| from google.genai import types | |
| from typing import Dict, Any, List, Optional | |
| import datetime | |
| import uuid | |
| class TimerExtension(BaseExtension): | |
| def name(self) -> str: | |
| return "timer" | |
| def display_name(self) -> str: | |
| return "Timer & Reminders" | |
| def description(self) -> str: | |
| return "Set timers and reminders for time-sensitive tasks with automatic notifications" | |
| def icon(self) -> str: | |
| return "⏰" | |
| def version(self) -> str: | |
| return "2.0.0" | |
| def get_system_context(self) -> str: | |
| return """ | |
| You have access to a Timer system for tracking time-sensitive activities. | |
| You can: | |
| - Set timers with a name and duration (in minutes, hours, or days) | |
| - List active timers and check elapsed time | |
| - Cancel timers | |
| - Calculate remaining time | |
| - Get automatic notifications when timers complete | |
| When users mention time-sensitive activities (fermentation stages, cooking, steeping, | |
| reminders, etc.), offer to set timers. Be proactive about time management. | |
| IMPORTANT: Timers persist across conversations! When users ask about "the timer" or | |
| "my timers", they're referring to timers created earlier. Always use list_timers to | |
| check existing timers before assuming none exist. | |
| """ | |
| def _get_default_state(self) -> Dict[str, Any]: | |
| return { | |
| "timers": [], | |
| "total_created": 0, | |
| "total_completed": 0, | |
| "created_at": datetime.datetime.now().isoformat(), | |
| "last_updated": datetime.datetime.now().isoformat() | |
| } | |
| def get_state_summary(self, user_id: str) -> Optional[str]: | |
| """Provide state summary for system prompt""" | |
| state = self.get_state(user_id) | |
| active_timers = [t for t in state.get("timers", []) if t.get("active", False)] | |
| if active_timers: | |
| return f"{len(active_timers)} active timer(s)" | |
| return None | |
| def get_metrics(self, user_id: str) -> Dict[str, Any]: | |
| """Provide usage metrics""" | |
| state = self.get_state(user_id) | |
| return { | |
| "total_created": state.get("total_created", 0), | |
| "total_completed": state.get("total_completed", 0), | |
| "currently_active": len([t for t in state.get("timers", []) if t.get("active", False)]) | |
| } | |
| def get_tools(self) -> List[types.Tool]: | |
| set_timer = types.FunctionDeclaration( | |
| name="set_timer", | |
| description="Set a new timer with a name and duration", | |
| parameters={ | |
| "type": "object", | |
| "properties": { | |
| "name": {"type": "string", "description": "Timer name/description"}, | |
| "duration_minutes": { | |
| "type": "number", | |
| "description": "Duration in minutes (can be fractional, e.g., 1440 for 1 day)" | |
| } | |
| }, | |
| "required": ["name", "duration_minutes"] | |
| } | |
| ) | |
| list_timers = types.FunctionDeclaration( | |
| name="list_timers", | |
| description="List all active timers with elapsed and remaining time. ALWAYS call this when user asks about 'the timer' or 'my timers'.", | |
| parameters={"type": "object", "properties": {}} | |
| ) | |
| check_timer = types.FunctionDeclaration( | |
| name="check_timer", | |
| description="Check status of a specific timer by ID", | |
| parameters={ | |
| "type": "object", | |
| "properties": { | |
| "timer_id": {"type": "string", "description": "Timer ID from list_timers"} | |
| }, | |
| "required": ["timer_id"] | |
| } | |
| ) | |
| cancel_timer = types.FunctionDeclaration( | |
| name="cancel_timer", | |
| description="Cancel/delete a timer by ID", | |
| parameters={ | |
| "type": "object", | |
| "properties": { | |
| "timer_id": {"type": "string", "description": "Timer ID to cancel"} | |
| }, | |
| "required": ["timer_id"] | |
| } | |
| ) | |
| return [types.Tool(function_declarations=[ | |
| set_timer, list_timers, check_timer, cancel_timer | |
| ])] | |
| def _execute_tool(self, user_id: str, tool_name: str, args: Dict[str, Any]) -> Any: | |
| """Execute tool logic""" | |
| state = self.get_state(user_id) | |
| if tool_name == "set_timer": | |
| start_time = datetime.datetime.now() | |
| duration_mins = args["duration_minutes"] | |
| end_time = start_time + datetime.timedelta(minutes=duration_mins) | |
| timer = { | |
| "id": str(uuid.uuid4())[:8], | |
| "name": args["name"], | |
| "start_time": start_time.isoformat(), | |
| "end_time": end_time.isoformat(), | |
| "duration_minutes": duration_mins, | |
| "active": True, | |
| "notified": False | |
| } | |
| state["timers"].append(timer) | |
| state["total_created"] = state.get("total_created", 0) + 1 | |
| self.update_state(user_id, state) | |
| # Log activity | |
| self.log_activity(user_id, "timer_created", { | |
| "timer_id": timer["id"], | |
| "name": timer["name"], | |
| "duration": duration_mins | |
| }) | |
| # Format duration for display | |
| if duration_mins < 60: | |
| duration_str = f"{duration_mins} minutes" | |
| elif duration_mins < 1440: | |
| hours = duration_mins / 60 | |
| duration_str = f"{hours:.1f} hours" | |
| else: | |
| days = duration_mins / 1440 | |
| duration_str = f"{days:.1f} days" | |
| return { | |
| "success": True, | |
| "message": f"⏰ Timer set: '{timer['name']}' for {duration_str}", | |
| "timer_id": timer["id"], | |
| "end_time": end_time.strftime("%Y-%m-%d %H:%M:%S"), | |
| "duration_display": duration_str | |
| } | |
| elif tool_name == "list_timers": | |
| now = datetime.datetime.now() | |
| active_timers = [] | |
| for timer in state["timers"]: | |
| if not timer["active"]: | |
| continue | |
| start = datetime.datetime.fromisoformat(timer["start_time"]) | |
| end = datetime.datetime.fromisoformat(timer["end_time"]) | |
| elapsed = (now - start).total_seconds() / 60 | |
| remaining = (end - now).total_seconds() / 60 | |
| active_timers.append({ | |
| "id": timer["id"], | |
| "name": timer["name"], | |
| "elapsed_minutes": round(elapsed, 1), | |
| "remaining_minutes": round(remaining, 1), | |
| "is_complete": remaining <= 0, | |
| "end_time": timer["end_time"], | |
| "percentage_complete": min(100, (elapsed / timer["duration_minutes"]) * 100) | |
| }) | |
| return { | |
| "active_timers": active_timers, | |
| "count": len(active_timers), | |
| "message": f"Found {len(active_timers)} active timer(s)" if active_timers else "No active timers" | |
| } | |
| elif tool_name == "check_timer": | |
| timer_id = args["timer_id"] | |
| now = datetime.datetime.now() | |
| for timer in state["timers"]: | |
| if timer["id"] == timer_id and timer["active"]: | |
| start = datetime.datetime.fromisoformat(timer["start_time"]) | |
| end = datetime.datetime.fromisoformat(timer["end_time"]) | |
| elapsed = (now - start).total_seconds() / 60 | |
| remaining = (end - now).total_seconds() / 60 | |
| return { | |
| "id": timer["id"], | |
| "name": timer["name"], | |
| "elapsed_minutes": round(elapsed, 1), | |
| "remaining_minutes": round(remaining, 1), | |
| "is_complete": remaining <= 0, | |
| "percentage_complete": min(100, (elapsed / timer["duration_minutes"]) * 100), | |
| "end_time": timer["end_time"] | |
| } | |
| return {"success": False, "error": f"Timer {timer_id} not found"} | |
| elif tool_name == "cancel_timer": | |
| timer_id = args["timer_id"] | |
| for timer in state["timers"]: | |
| if timer["id"] == timer_id: | |
| timer["active"] = False | |
| self.update_state(user_id, state) | |
| # Log activity | |
| self.log_activity(user_id, "timer_cancelled", { | |
| "timer_id": timer_id, | |
| "name": timer["name"] | |
| }) | |
| return { | |
| "success": True, | |
| "message": f"✅ Cancelled timer: {timer['name']}" | |
| } | |
| return {"success": False, "error": f"Timer {timer_id} not found"} | |
| return {"error": f"Unknown tool: {tool_name}"} | |
| def get_proactive_message(self, user_id: str) -> Optional[str]: | |
| """Check for completed timers and send notifications""" | |
| state = self.get_state(user_id) | |
| now = datetime.datetime.now() | |
| newly_completed = [] | |
| for timer in state.get("timers", []): | |
| if timer.get("active") and not timer.get("notified", False): | |
| end_time = datetime.datetime.fromisoformat(timer["end_time"]) | |
| if now >= end_time: | |
| newly_completed.append(timer) | |
| timer["notified"] = True | |
| if newly_completed: | |
| self.update_state(user_id, state) | |
| state["total_completed"] = state.get("total_completed", 0) + len(newly_completed) | |
| # Create notification message | |
| if len(newly_completed) == 1: | |
| timer = newly_completed[0] | |
| return f"⏰ **Timer Complete!** Your timer '{timer['name']}' has finished!" | |
| else: | |
| timer_names = ", ".join([f"'{t['name']}'" for t in newly_completed]) | |
| return f"⏰ **{len(newly_completed)} Timers Complete!** {timer_names}" | |
| return None | |
| def on_enable(self, user_id: str) -> str: | |
| self.initialize_state(user_id) | |
| return "⏰ Timer & Reminders enabled! I can now help you track time-sensitive tasks with automatic notifications." | |
| def on_disable(self, user_id: str) -> str: | |
| state = self.get_state(user_id) | |
| active = len([t for t in state.get("timers", []) if t.get("active", False)]) | |
| return f"⏰ Timer & Reminders disabled. {active} active timer(s) will be cleared." | |
| def health_check(self, user_id: str) -> Dict[str, Any]: | |
| """Check extension health""" | |
| state = self.get_state(user_id) | |
| issues = [] | |
| # Check for stale timers (older than 30 days) | |
| now = datetime.datetime.now() | |
| stale_count = 0 | |
| for timer in state.get("timers", []): | |
| if timer.get("active"): | |
| end_time = datetime.datetime.fromisoformat(timer["end_time"]) | |
| age_days = (now - end_time).days | |
| if age_days > 30: | |
| stale_count += 1 | |
| if stale_count > 0: | |
| issues.append(f"{stale_count} stale timer(s) older than 30 days") | |
| return { | |
| "healthy": len(issues) == 0, | |
| "extension": self.name, | |
| "version": self.version, | |
| "issues": issues if issues else None | |
| } |