pomodoro-timer / README.md
ysharma's picture
ysharma HF Staff
Update README.md
fa2cbec verified

A newer version of the Gradio SDK is available: 6.8.0

Upgrade
metadata
title: Pomodoro Timer
emoji: ⏲️🌳
colorFrom: red
colorTo: red
sdk: gradio
sdk_version: 6.5.1
app_file: pomodoro_forest.py
pinned: false
license: mit
short_description: 'Gamified Pomodoro Timer: a pixel-art tree grows as you focus'

🍅 PomodoroTimer Component Documentation

A gamified Pomodoro timer built with Gradio 6's gr.HTML component. Watch pixel-art trees grow as you stay focused, and build a forest over time!


Table of Contents


Installation

The component is a single Python file. Copy pomodoro_forest.py into your project or import the PomodoroTimer class directly.

Requirements:

  • Gradio 6.0+
  • Python 3.9+
pip install "gradio>=6.0"

Quick Start

import gradio as gr
from pomodoro_forest import PomodoroTimer

with gr.Blocks() as demo:
    timer = PomodoroTimer()

demo.launch()

Parameters

Parameter Type Default Description
value dict See below Timer state (elapsed time, sessions, etc.)
duration int 25 Duration of the current mode in minutes
mode str "focus" Current mode: "focus", "short_break", or "long_break"
tree_theme str "classic" Visual theme for the tree (see Tree Themes)
mode_color str Auto Override the mode's accent color (hex)
trunk_color str Auto Override trunk color (hex)
crown_color str Auto Override crown/leaves color (hex)
crown_top_color str Auto Override crown top color (hex)
fruit_color str Auto Override fruit color (hex)

Value Schema

The value parameter is a dictionary with the following structure:

{
    "elapsed": int,        # Seconds elapsed in current session (0 to duration*60)
    "running": bool,       # Whether the timer is currently running
    "sessions": int,       # Number of completed focus sessions (trees grown)
    "total_minutes": int   # Total minutes spent in focus mode
}

Default value:

{"elapsed": 0, "running": False, "sessions": 0, "total_minutes": 0}

API Schema (JSON):

{
    "type": "object",
    "properties": {
        "elapsed": {"type": "integer"},
        "running": {"type": "boolean"},
        "sessions": {"type": "integer"},
        "total_minutes": {"type": "integer"}
    }
}

Events

The PomodoroTimer component emits the following events:

Event Trigger Event Data Description
.submit() Session completes None Fired when a focus/break session reaches 100%
.select() Mode button clicked {"mode": str} Fired when user clicks Focus/Short Break/Long Break
.change() Value changes None Fired on any value change (every second while running)

Accessing Event Data

def handle_mode_select(evt: gr.EventData, timer_val):
    # Safely access the mode from event data
    new_mode = evt._data.get("mode", "focus") if evt._data else "focus"
    return new_mode

timer.select(fn=handle_mode_select, inputs=[timer], outputs=[...])

Tree Themes

Five built-in themes are available:

Theme Trunk Crown Fruit Best For
classic Brown Green Red Default look
cherry Dark brown Pink Hot pink Spring vibes
autumn Brown Orange Deep orange Fall season
winter Gray Silver Light blue Winter/holidays
sakura Dark brown Light pink Pink Japanese aesthetic

Theme colors (for reference):

TREE_THEMES = {
    "classic": {"trunk": "#8B5E3C", "crown": "#2ecc71", "crown_top": "#00b894", "fruit": "#e74c3c"},
    "cherry":  {"trunk": "#5D4037", "crown": "#F8BBD9", "crown_top": "#F48FB1", "fruit": "#E91E63"},
    "autumn":  {"trunk": "#6D4C41", "crown": "#FF9800", "crown_top": "#FFC107", "fruit": "#FF5722"},
    "winter":  {"trunk": "#455A64", "crown": "#B0BEC5", "crown_top": "#ECEFF1", "fruit": "#81D4FA"},
    "sakura":  {"trunk": "#4E342E", "crown": "#FCE4EC", "crown_top": "#F8BBD0", "fruit": "#EC407A"},
}

Examples

Basic Usage

import gradio as gr
from pomodoro_forest import PomodoroTimer

with gr.Blocks() as demo:
    gr.Markdown("# My Pomodoro App")
    timer = PomodoroTimer(duration=25, mode="focus", tree_theme="classic")

demo.launch()

Custom Durations

import gradio as gr
from pomodoro_forest import PomodoroTimer, _update_timer

with gr.Blocks() as demo:
    timer = PomodoroTimer(duration=50, mode="focus")  # 50-minute focus
    
    # Quick preset buttons
    with gr.Row():
        btn_25 = gr.Button("25 min")
        btn_50 = gr.Button("50 min")
    
    btn_25.click(
        fn=lambda v: _update_timer({**v, "elapsed": 0}, 25, "focus", "classic"),
        inputs=[timer],
        outputs=[timer]
    )
    btn_50.click(
        fn=lambda v: _update_timer({**v, "elapsed": 0}, 50, "focus", "classic"),
        inputs=[timer],
        outputs=[timer]
    )

demo.launch()

Listening to Events

import gradio as gr
from pomodoro_forest import PomodoroTimer, _update_timer

with gr.Blocks() as demo:
    timer = PomodoroTimer()
    status = gr.Textbox(label="Status")
    
    # When a session completes
    def on_complete(timer_val):
        sessions = timer_val.get("sessions", 0)
        return f"🎉 Congratulations! You've grown {sessions} trees!"
    
    timer.submit(fn=on_complete, inputs=[timer], outputs=[status])

demo.launch()

Updating the Timer Programmatically

⚠️ Important: Always use gr.HTML(...) to update props, never return a new PomodoroTimer() instance.

import gradio as gr
from pomodoro_forest import PomodoroTimer, _update_timer, TREE_THEMES, MODE_COLORS

with gr.Blocks() as demo:
    timer = PomodoroTimer()
    
    # Add 5 sessions programmatically (for testing)
    def add_sessions(timer_val):
        new_val = {
            **timer_val,
            "sessions": timer_val.get("sessions", 0) + 5,
            "total_minutes": timer_val.get("total_minutes", 0) + 125
        }
        return _update_timer(new_val, 25, "focus", "classic")
    
    btn = gr.Button("Add 5 Sessions (Demo)")
    btn.click(fn=add_sessions, inputs=[timer], outputs=[timer])

demo.launch()

API Usage

When your Gradio app is running, you can interact with the PomodoroTimer via the API.

Get Current State

from gradio_client import Client

client = Client("http://localhost:7860")

# If your timer is an input to an API endpoint
result = client.predict(
    {"elapsed": 0, "running": False, "sessions": 3, "total_minutes": 75},
    api_name="/your_endpoint"
)

Python Client Example

from gradio_client import Client

client = Client("http://localhost:7860")

# Start a session with pre-existing data
timer_state = {
    "elapsed": 0,
    "running": False,
    "sessions": 5,
    "total_minutes": 125
}

# Call your function that takes timer as input
result = client.predict(timer_state, api_name="/process_timer")
print(result)

REST API

curl -X POST http://localhost:7860/api/your_endpoint \
  -H "Content-Type: application/json" \
  -d '{
    "data": [{
      "elapsed": 0,
      "running": false,
      "sessions": 10,
      "total_minutes": 250
    }]
  }'

MCP Usage

The PomodoroTimer component works with Gradio's MCP (Model Context Protocol) support, allowing AI assistants to interact with it.

Exposing via MCP

import gradio as gr
from pomodoro_forest import PomodoroTimer, _update_timer

with gr.Blocks() as demo:
    timer = PomodoroTimer()
    output = gr.JSON(label="Timer State")
    
    def get_timer_state(timer_val):
        """Get the current Pomodoro timer state.
        
        Returns the timer's current state including elapsed time,
        running status, completed sessions, and total focus minutes.
        """
        return timer_val
    
    def set_timer_sessions(timer_val, sessions: int, total_minutes: int):
        """Set the Pomodoro timer's session count.
        
        Args:
            sessions: Number of completed focus sessions
            total_minutes: Total minutes spent focusing
        """
        new_val = {**timer_val, "sessions": sessions, "total_minutes": total_minutes}
        return _update_timer(new_val, 25, "focus", "classic"), new_val
    
    # Expose as API endpoints for MCP
    get_btn = gr.Button("Get State")
    get_btn.click(
        fn=get_timer_state,
        inputs=[timer],
        outputs=[output],
        api_name="get_pomodoro_state"  # MCP-accessible endpoint
    )
    
    with gr.Row():
        sessions_input = gr.Number(label="Sessions", value=0)
        minutes_input = gr.Number(label="Total Minutes", value=0)
    
    set_btn = gr.Button("Set Sessions")
    set_btn.click(
        fn=set_timer_sessions,
        inputs=[timer, sessions_input, minutes_input],
        outputs=[timer, output],
        api_name="set_pomodoro_sessions"  # MCP-accessible endpoint
    )

demo.launch()

MCP Tool Definitions

When used with MCP, the following tools become available:

get_pomodoro_state

{
    "name": "get_pomodoro_state",
    "description": "Get the current Pomodoro timer state",
    "parameters": {
        "timer_val": {
            "type": "object",
            "properties": {
                "elapsed": {"type": "integer"},
                "running": {"type": "boolean"},
                "sessions": {"type": "integer"},
                "total_minutes": {"type": "integer"}
            }
        }
    }
}

set_pomodoro_sessions

{
    "name": "set_pomodoro_sessions",
    "description": "Set the Pomodoro timer's session count",
    "parameters": {
        "sessions": {"type": "integer", "description": "Number of completed sessions"},
        "total_minutes": {"type": "integer", "description": "Total focus minutes"}
    }
}

Using with Claude or Other MCP Clients

# Example: AI assistant querying your Pomodoro app via MCP
# The assistant can call these tools to interact with the timer

# Get current state
state = mcp_client.call_tool("get_pomodoro_state", {})
# Returns: {"elapsed": 300, "running": true, "sessions": 3, "total_minutes": 75}

# Set sessions (e.g., restore from saved data)
mcp_client.call_tool("set_pomodoro_sessions", {
    "sessions": 10,
    "total_minutes": 250
})

Customization

Adding Custom Themes

from pomodoro_forest import TREE_THEMES

# Add your own theme
TREE_THEMES["ocean"] = {
    "trunk": "#1565C0",
    "crown": "#4FC3F7", 
    "crown_top": "#81D4FA",
    "fruit": "#00BCD4"
}

# Use it
timer = PomodoroTimer(tree_theme="ocean")

Override Individual Colors

timer = PomodoroTimer(
    tree_theme="classic",
    crown_color="#9C27B0",  # Purple crown
    fruit_color="#FFEB3B",  # Yellow fruit
)

Custom Mode Colors

from pomodoro_forest import MODE_COLORS

MODE_COLORS["focus"] = "#9C27B0"       # Purple for focus
MODE_COLORS["short_break"] = "#00BCD4" # Cyan for short break
MODE_COLORS["long_break"] = "#FF9800"  # Orange for long break

Best Practices

1. Always Use gr.HTML() for Updates

# ✅ Correct
def update_timer(timer_val):
    return gr.HTML(value=new_val, duration=25, mode="focus", ...)

# ❌ Wrong - causes issues
def update_timer(timer_val):
    return PomodoroTimer(value=new_val, duration=25, mode="focus")

2. Use Helper Functions

Import and use the provided helper functions:

from pomodoro_forest import _update_timer, _update_theme_only

# Full update
timer_output = _update_timer(value, duration, mode, theme)

# Theme-only update
timer_output = _update_theme_only(theme, mode)

3. Handle Events Safely

def handle_event(evt: gr.EventData, timer_val):
    # Always check if _data exists
    try:
        data = evt._data.get("key", "default") if evt._data else "default"
    except:
        data = "default"
    return data

4. Preserve State Across Updates

def my_handler(timer_val, new_duration):
    # Spread existing state, only change what's needed
    new_val = {**timer_val, "elapsed": 0}
    return _update_timer(new_val, new_duration, "focus", "classic")

Component Architecture

PomodoroTimer (extends gr.HTML)
├── html_template    → Renders timer ring, tree scene, controls, stats
├── css_template     → Styles with ${prop} placeholders for dynamic colors
├── js_on_load       → Handles start/pause, reset, mode switching
└── value            → Dict holding timer state

File Structure:

pomodoro_forest.py
├── Constants (DURATIONS, MODE_COLORS, TREE_THEMES)
├── Templates (HTML_TEMPLATE, CSS_TEMPLATE, JS_ON_LOAD)
├── PomodoroTimer class
├── Helper functions (_update_timer, _update_theme_only)
└── Demo app (with gr.Blocks)

Troubleshooting

Issue Cause Solution
Colors don't update Using Python string formatting instead of ${prop} Use template syntax: ${mode_color}
"Multiple values for argument" error Returning subclass instance Return gr.HTML(...) instead
Event data is None Accessing wrong event or wrong data key Check evt._data exists before accessing
Timer doesn't re-render Mutating value in place Always spread: {...timer_val, key: newVal}

License

MIT License - Feel free to use, modify, and distribute!


Credits

Built with Gradio 6 and the new gr.HTML custom component system.

Created as a demo for the Gradio HTML component capabilities.