-
Notifications
You must be signed in to change notification settings - Fork 278
Expand file tree
/
Copy pathmain.py
More file actions
135 lines (109 loc) · 4.98 KB
/
main.py
File metadata and controls
135 lines (109 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import json
import os
import re
from src.agent.capability import MatchingCapability
from src.agent.capability_worker import CapabilityWorker
from src.main import AgentWorker
# =============================================================================
# POMODORO FOCUS TIMER
# A voice-controlled Pomodoro timer that manages focus and break sessions.
# Pattern: Ask duration -> Focus -> Break -> Repeat or Exit
# =============================================================================
DEFAULT_FOCUS_MINUTES = 25
DEFAULT_BREAK_MINUTES = 5
LONG_BREAK_MINUTES = 15
SESSIONS_BEFORE_LONG_BREAK = 4
EXIT_WORDS = {"stop", "exit", "quit", "done", "cancel", "bye", "goodbye", "leave"}
DURATION_PATTERNS = [
(r"(\d{1,4})\s*(?:min|minute|minutes)", lambda m: int(m.group(1))),
(r"half\s*(?:an?\s*)?hour", lambda m: 30),
(r"an?\s*hour", lambda m: 60),
(r"(\d{1,4}(?:\.\d+)?)\s*(?:hour|hours|hr|hrs)", lambda m: round(float(m.group(1)) * 60)),
(r"\b(\d{1,4})\b", lambda m: int(m.group(1))),
]
class PomodoroTimerCapability(MatchingCapability):
worker: AgentWorker = None
capability_worker: CapabilityWorker = None
#{{register_capability}}
def call(self, worker: AgentWorker) -> None:
self.worker = worker
self.capability_worker = CapabilityWorker(self.worker)
self.worker.session_tasks.create(self.run())
async def run(self) -> None:
try:
session_count = 0
await self.capability_worker.speak(
"How long would you like to focus? I'll default to 25 minutes."
)
user_input = await self.capability_worker.user_response()
focus_minutes = self._parse_minutes(user_input)
self.worker.editor_logging_handler.info(
f"[PomodoroTimer] Starting with {focus_minutes}-minute sessions"
)
while True:
session_count += 1
await self.capability_worker.speak(
f"Starting focus session {session_count}. "
f"{focus_minutes} minutes on the clock. Good luck."
)
await self.worker.session_tasks.sleep(focus_minutes * 60)
if session_count % SESSIONS_BEFORE_LONG_BREAK == 0:
break_minutes = LONG_BREAK_MINUTES
await self.capability_worker.speak(
f"Focus session complete. You've done {session_count} sessions. "
f"Take a longer break, {break_minutes} minutes. "
"Say stop to finish, or anything else to keep going."
)
else:
break_minutes = DEFAULT_BREAK_MINUTES
await self.capability_worker.speak(
f"Focus session complete. Take a {break_minutes}-minute break. "
"Say stop to finish, or anything else to keep going."
)
user_input = await self.capability_worker.user_response()
if not user_input:
continue
input_words = set(user_input.lower().split())
if input_words & EXIT_WORDS:
total = session_count * focus_minutes
await self.capability_worker.speak(
f"Great work. You completed {session_count} "
f"focus session{'s' if session_count != 1 else ''}, "
f"totaling {total} minutes. See you next time."
)
break
await self.capability_worker.speak(
f"Enjoy your {break_minutes}-minute break."
)
await self.worker.session_tasks.sleep(break_minutes * 60)
await self.capability_worker.speak(
"Break's over. Ready for the next session."
)
except Exception as e:
self.worker.editor_logging_handler.error(
f"[PomodoroTimer] Unexpected error: {e}"
)
try:
await self.capability_worker.speak(
"Something went wrong with the timer. Let me hand you back."
)
except Exception:
pass
finally:
self.worker.editor_logging_handler.info(
"[PomodoroTimer] Ability ended"
)
self.capability_worker.resume_normal_flow()
def _parse_minutes(self, text: str) -> int:
"""Extract a duration in minutes from natural language. Falls back to 25."""
if not text or len(text) > 200:
return DEFAULT_FOCUS_MINUTES
cleaned = text.strip().lower()
for pattern, extractor in DURATION_PATTERNS:
match = re.search(pattern, cleaned)
if match:
minutes = extractor(match)
if 1 <= minutes <= 180:
return minutes
return DEFAULT_FOCUS_MINUTES
return DEFAULT_FOCUS_MINUTES