PomoCat / app.py
pxiaoer's picture
init
d3febbf
#!/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
)