File size: 19,959 Bytes
d3febbf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
#!/usr/bin/env python3
"""
PomoCat - Your focus buddy with a paw-sitive vibe
A Gradio-powered MCP server for managing pomodoro sessions with AI encouragement
"""

import gradio as gr
import os
import argparse
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
import json
from dataclasses import dataclass

# LLM API imports
try:
    from anthropic import Anthropic
    ANTHROPIC_AVAILABLE = True
except ImportError:
    ANTHROPIC_AVAILABLE = False
    print("⚠️  Anthropic library not available. AI encouragement will use fallback responses.")

@dataclass
class PomoCatState:
    """State management for PomoCat sessions"""
    is_working: bool = False
    is_on_break: bool = False
    current_session_start: Optional[datetime] = None
    total_work_time: int = 0  # in minutes
    session_count: int = 0
    last_break_time: Optional[datetime] = None
    work_start_time: Optional[datetime] = None
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert state to dictionary for JSON serialization"""
        return {
            "is_working": self.is_working,
            "is_on_break": self.is_on_break,
            "current_session_start": self.current_session_start.isoformat() if self.current_session_start else None,
            "total_work_time": self.total_work_time,
            "session_count": self.session_count,
            "last_break_time": self.last_break_time.isoformat() if self.last_break_time else None,
            "work_start_time": self.work_start_time.isoformat() if self.work_start_time else None
        }

# Global state instance
pomocat_state = PomoCatState()

def pomodoro_manager(user_query: str) -> str:
    """
    Manage pomodoro work sessions and provide AI encouragement.
    
    This function handles all pomodoro-related commands including starting work sessions,
    taking breaks, checking status, and providing AI-powered motivational support.
    It enforces a minimum 20-minute work requirement before allowing breaks.
    
    Args:
        user_query (str): User command or message. Commands include:
            - "start working" / "start pomodoro" - Begin a 25-minute focus session
            - "take a break" / "rest" - Take a break (requires 20+ minutes of work)
            - "resume work" / "back to work" - End break and resume working  
            - "stop" / "end session" - End current session completely
            - "status" / "check progress" - Get current session status
            - "help" / "commands" - Show available commands
            - Any other text - Get AI encouragement and motivation
        
    Returns:
        str: Response message with session status, encouragement, or command results
    """
    global pomocat_state
    
    query = user_query.lower().strip()
    
    # Handle different commands
    if any(keyword in query for keyword in ["start work", "start pomodoro", "begin work", "start focus"]):
        return start_work_session()
        
    elif any(keyword in query for keyword in ["take a break", "start break", "break time", "rest"]):
        return start_break_mode()
        
    elif any(keyword in query for keyword in ["resume work", "back to work", "end break", "continue work"]):
        return resume_work()
        
    elif any(keyword in query for keyword in ["stop", "end session", "quit", "finish"]):
        return stop_session()
        
    elif any(keyword in query for keyword in ["status", "check progress", "how am i doing", "progress"]):
        return get_session_status()
        
    elif any(keyword in query for keyword in ["help", "commands", "what can you do"]):
        return get_help_text()
        
    else:
        # AI encouragement for any other input
        return provide_ai_encouragement(user_query)

def get_session_status() -> str:
    """
    Get current pomodoro session status and detailed progress statistics.
    
    This function provides comprehensive information about the current session including:
    work time elapsed, break status, total accumulated work time, and current activity.
    
    Returns:
        str: Detailed session status including work time, break duration, and progress info
    """
    global pomocat_state
    
    if not pomocat_state.is_working and not pomocat_state.is_on_break:
        return "😸 No active session. Ready to start working? Use 'start working' to begin! 🐾"
    
    current_work_time = calculate_work_time() if pomocat_state.is_working else 0
    total_time = pomocat_state.total_work_time + current_work_time
    
    if pomocat_state.is_working:
        return f"😺 Currently working! \n🐾 Current session: {current_work_time} minutes \n⏰ Total work time: {total_time} minutes \nπŸ’ͺ Keep up the great focus! You're doing fantastic! ✨"
    elif pomocat_state.is_on_break:
        break_duration = (datetime.now() - pomocat_state.last_break_time).seconds // 60 if pomocat_state.last_break_time else 0
        return f"😺😺😺 Currently on break! \n🐾 Break duration: {break_duration} minutes \n⏰ Total work time: {total_time} minutes \n😸 Enjoy your rest! Use 'resume work' when ready to continue! 🌟"

def reset_session() -> str:
    """
    Reset the current pomodoro session completely and clear all progress.
    
    This function resets all session data including work time, break status, 
    timers, and accumulated statistics. Use this to start completely fresh.
    
    Returns:
        str: Confirmation message that the session has been successfully reset
    """
    global pomocat_state
    pomocat_state = PomoCatState()
    return "😺 Session reset! All timers and progress cleared. Ready for a fresh start? 🐾✨"

def start_work_session() -> str:
    """Start a new work session"""
    global pomocat_state
    
    if pomocat_state.is_working:
        elapsed = calculate_work_time()
        return f"😺 You're already working! Keep it up! Current session: {elapsed} minutes"
    
    pomocat_state.is_working = True
    pomocat_state.is_on_break = False
    pomocat_state.current_session_start = datetime.now()
    pomocat_state.work_start_time = datetime.now()
    
    return "😺 Awesome! Pomodoro timer started! Focus time: 25 minutes... \n🐾 *purrs encouragingly* You've got this! Stay focused and let's make some progress together! πŸ’ͺ✨"

def start_break_mode() -> str:
    """Start break mode with time validation"""
    global pomocat_state
    
    if not pomocat_state.is_working:
        return "😸 You're not currently working! Use 'start working' to begin a pomodoro session first. 🐾"
    
    # Calculate elapsed work time
    elapsed_minutes = calculate_work_time()
    
    if elapsed_minutes < 20:
        remaining = 20 - elapsed_minutes
        return f"😾 Meow! You need to work at least 20 minutes before taking a break! You've worked for {elapsed_minutes} minutes, keep going for {remaining} more minutes! πŸ’ͺ \n🐱 *encouraging purr* You're doing great - just a little bit more focus time! 🌟"
    
    # Allow break
    pomocat_state.is_on_break = True
    pomocat_state.is_working = False
    pomocat_state.last_break_time = datetime.now()
    
    if elapsed_minutes >= 25:
        return f"😻 Perfect! You've completed {elapsed_minutes} minutes of focused work! Time for a well-deserved break! πŸŽ‰ \n😺😺😺 *stretches paws* Enjoy your break! You've earned it! 🐾✨"
    else:
        return f"😸 Good job! You've worked for {elapsed_minutes} minutes. While you haven't completed the full 25-minute session, you've earned a break! \n😺😺😺 *gentle purr* Take a moment to rest, then we can tackle more work together! 🌟"

def resume_work() -> str:
    """Resume work session"""
    global pomocat_state
    
    if not pomocat_state.is_on_break:
        if pomocat_state.is_working:
            return "😺 You're already working! Keep up the great focus! 🐾"
        else:
            return "😸 You're not on a break. Use 'start working' to begin a new pomodoro session! 🐾"
    
    pomocat_state.is_working = True
    pomocat_state.is_on_break = False
    pomocat_state.work_start_time = datetime.now()  # Reset work start time for new session
    
    return "😻 Welcome back! Focus mode resumed. Let's tackle this together! πŸš€ \n🐾 *determined purr* Time to get back into the flow! You're doing amazing! πŸ’ͺ✨"

def stop_session() -> str:
    """Stop the current session"""
    global pomocat_state
    
    if not pomocat_state.is_working and not pomocat_state.is_on_break:
        return "😸 No active session to stop. You're all set! 🐾"
    
    elapsed = calculate_work_time() if pomocat_state.is_working else 0
    total_time = pomocat_state.total_work_time + elapsed
    
    # Reset state
    pomocat_state = PomoCatState()
    
    return f"😺 Session ended! Great work today! \n🐾 Total focused time: {total_time} minutes. You should be proud of your effort! 🌟 \n😻 *content purr* Take care and see you next time! πŸ’•"

def provide_ai_encouragement(user_input: str) -> str:
    """
    Provide AI-powered encouragement based on user input using Claude API.
    
    Analyzes the user's message and provides personalized, cat-themed 
    motivational responses to help with focus and productivity.
    
    Args:
        user_input: The user's message or question about motivation, challenges, or feelings
    
    Returns:
        Encouraging cat-themed response tailored to the user's emotional state
    """
    
    # Get current session context for more personalized responses
    global pomocat_state
    current_work_time = calculate_work_time() if pomocat_state.is_working else 0
    total_time = pomocat_state.total_work_time + current_work_time
    
    session_context = ""
    if pomocat_state.is_working:
        session_context = f"The user is currently in a work session ({current_work_time} minutes so far, {total_time} total minutes today)."
    elif pomocat_state.is_on_break:
        session_context = f"The user is currently on a break (total work time today: {total_time} minutes)."
    else:
        session_context = f"The user has no active session (total work time today: {total_time} minutes)."
    
    # Try to use Claude API if available
    api_key = os.getenv('ANTHROPIC_API_KEY')
    if ANTHROPIC_AVAILABLE and api_key:
        try:
            client = Anthropic(api_key=api_key)
            
            prompt = f"""You are PomoCat, a friendly and encouraging productivity assistant with a cat theme. 
Your role is to provide supportive, motivational responses to help users with their work and focus.

Context: {session_context}

User message: "{user_input}"

Please respond with:
- Cat-themed language and emojis (😺😸😻🐾etc.)
- Warm, encouraging tone
- Practical advice when appropriate
- Keep responses conversational and supportive
- 2-3 sentences maximum
- Use cat behavior metaphors when helpful

Respond as PomoCat would:"""

            response = client.messages.create(
                model="claude-sonnet-4-20250514",
                max_tokens=200,
                temperature=0.7,
                messages=[{"role": "user", "content": prompt}]
            )
            
            return response.content[0].text.strip()
            
        except Exception as e:
            print(f"⚠️  Claude API error: {e}")
            print(f"   API Key present: {'Yes' if api_key else 'No'}")
            print(f"   Using fallback responses instead...")
            # Fall back to keyword-based responses
    else:
        if not ANTHROPIC_AVAILABLE:
            print("⚠️  Anthropic library not available")
        elif not api_key:
            print("⚠️  ANTHROPIC_API_KEY environment variable not set")
            
    # Fallback to keyword-based responses when API is not available
    return get_fallback_encouragement(user_input)

def get_fallback_encouragement(user_input: str) -> str:
    """Fallback encouragement responses when AI API is not available"""
    input_lower = user_input.lower()
    
    if any(word in input_lower for word in ["stress", "overwhelm", "anxious", "worried", "pressure"]):
        return "😸 *purrs softly* I can sense you're feeling a bit overwhelmed, but remember that even the biggest projects are just a series of small, focused steps! 🐾 Take a deep breath with me - you're more capable than you know! πŸ’ͺ✨"
        
    elif any(word in input_lower for word in ["tired", "exhausted", "drained", "fatigue"]):
        return "😴 *yawns sympathetically* It sounds like you need some rest! Remember, even cats need their naps to stay sharp! 🐱 Maybe take a short break, stretch those muscles, and come back refreshed! πŸ’€βœ¨"
        
    elif any(word in input_lower for word in ["difficult", "hard", "challenging", "tough", "struggle"]):
        return "😼 *sits up alertly* Challenges are just opportunities in disguise! Every difficult moment is making you stronger! 🐾 Break it down into smaller pieces - even the mightiest cat catches mice one at a time! 🦁πŸ’ͺ"
        
    elif any(word in input_lower for word in ["motivation", "inspire", "encourage", "boost"]):
        return "😻 *purrs enthusiastically* You're already taking the right steps by being here! Every small action is progress! 🌟 Remember, I believe in you completely - you have everything you need inside you already! πŸΎπŸ’•"
        
    elif any(word in input_lower for word in ["success", "complete", "finish", "achieve", "done", "accomplish"]):
        return "😻 *happy purr* That's amazing! I'm so proud of you! πŸŽ‰ Celebrating your wins is just as important as the work itself! 🐾 You're building incredible momentum - keep this energy going! βœ¨πŸ†"
        
    elif any(word in input_lower for word in ["procrastinate", "delay", "avoid", "postpone"]):
        return "😺 *gentle head bump* We all have those moments! The secret is to start with just one tiny step - even 5 minutes of work is better than none! 🐾 I'll be right here cheering you on! Sometimes the hardest part is just beginning! πŸ’ͺ✨"
        
    elif any(word in input_lower for word in ["focus", "concentrate", "attention", "distract"]):
        return "😸 *alert ears* Focus is like a muscle - it gets stronger with practice! 🐾 Try the pomodoro technique: 25 minutes of focused work, then a short break! Even cats know when to hunt and when to rest! 🎯✨"
        
    else:
        # General encouraging response
        return "😺 *purrs warmly* I'm here to support you through your work journey! 🐾 Whatever you're working on, remember that progress is progress, no matter how small! πŸ’ͺ You've got this! ✨"

def calculate_work_time() -> int:
    """Calculate elapsed work time in minutes"""
    global pomocat_state
    if not pomocat_state.work_start_time:
        return 0
    return int((datetime.now() - pomocat_state.work_start_time).total_seconds() / 60)

def get_help_text() -> str:
    """Get help text with available commands"""
    return """😺 PomoCat Commands Help 🐾

Work Session Commands:
β€’ "start working" / "start pomodoro" - Begin a 25-minute focus session
β€’ "take a break" / "rest" - Take a break (minimum 20 minutes work required)
β€’ "resume work" / "back to work" - End break and resume working
β€’ "stop" / "end session" - End current session completely

Status Commands:
β€’ "status" / "check progress" - See current session status
β€’ "help" / "commands" - Show this help message

🐾 Special Features:
β€’ Must work at least 20 minutes before breaks are allowed
β€’ AI encouragement for any questions or concerns
β€’ Session tracking and progress monitoring

😻 Just chat with me about anything - I'm here to encourage and support you! πŸ’•
    """

# Create Gradio interface with MCP support
def create_gradio_interface():
    """Create the main Gradio interface for PomoCat with MCP support"""
    
    with gr.Blocks(
        title="🐱 PomoCat - Your Focus Buddy"
    ) as demo:
        
        gr.Markdown("""
        # 🐱 PomoCat - Your Focus Buddy with a Paw-sitive Vibe
        
        Welcome to PomoCat! I'm here to help you stay focused with the Pomodoro technique.
        
        **🎯 Key Features:**
        - 25-minute focused work sessions
        - 20-minute minimum work time before breaks
        - AI-powered encouragement and support
        - Session tracking and progress monitoring
        
        **πŸ”§ Available APIs:**
        - `pomodoro_manager(user_query)` - Main interface: start/stop sessions, AI encouragement  
        - `get_session_status()` - Get detailed session status and progress statistics
        - `reset_session()` - Reset current session and clear all progress
        
        **πŸ€– AI Features:**
        - Real AI-powered encouragement using Claude API
        - Set ANTHROPIC_API_KEY environment variable for full AI features
        - Fallback to smart keyword-based responses if API not configured
        
        **πŸš€ Quick Start:**
        Just type commands like "start working", "take a break", or ask me anything for encouragement!
        """)
        
        with gr.Row():
            with gr.Column(scale=2):
                user_input = gr.Textbox(
                    label="πŸ’¬ Chat with PomoCat",
                    placeholder="Type 'start working' to begin, or ask me anything for encouragement...",
                    lines=2
                )
                
                submit_btn = gr.Button("Send 🐾", variant="primary")
                
            with gr.Column(scale=1):
                status_btn = gr.Button("Check Status πŸ“Š")
                reset_btn = gr.Button("Reset Session πŸ”„")
                help_btn = gr.Button("Help πŸ’‘")
        
        output = gr.Textbox(
            label="🐱 PomoCat Response",
            lines=8,
            max_lines=20,
            interactive=False
        )
        
        # Event handlers
        submit_btn.click(
            fn=pomodoro_manager,
            inputs=[user_input],
            outputs=[output]
        )
        
        user_input.submit(
            fn=pomodoro_manager,
            inputs=[user_input],
            outputs=[output]
        )
        
        status_btn.click(
            fn=get_session_status,
            outputs=[output]
        )
        
        reset_btn.click(
            fn=reset_session,
            outputs=[output]
        )
        
        help_btn.click(
            fn=get_help_text,
            outputs=[output]
        )
        
        # Example interactions
        gr.Examples(
            examples=[
                ["start working"],
                ["take a break"],
                ["status"],
                ["I'm feeling overwhelmed with my project"],
                ["How can I stay motivated?"],
                ["resume work"],
                ["stop"]
            ],
            inputs=[user_input],
            outputs=[output],
            fn=pomodoro_manager
        )
        


    
    return demo

if __name__ == "__main__":
    # Parse command line arguments
    parser = argparse.ArgumentParser(description="🐱 PomoCat - Your Focus Buddy with a Paw-sitive Vibe")
    parser.add_argument("--port", type=int, default=7860, help="Port to run the server on (default: 7860)")
    parser.add_argument("--host", default="0.0.0.0", help="Host to bind the server to (default: 0.0.0.0)")
    args = parser.parse_args()
    
    
    print(f"🐱 Starting PomoCat...")
    print(f"πŸ“‘ Server: http://{args.host}:{args.port}")
    
    # Note: MCP functionality depends on Gradio version compatibility
    
    # Create and launch the Gradio interface
    demo = create_gradio_interface()
    
    # Launch the interface
    demo.launch(
        share=False,
        server_name=args.host,
        server_port=args.port,
        show_error=True,
        mcp_server=True
    )