--- 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](#installation) - [Quick Start](#quick-start) - [Parameters](#parameters) - [Value Schema](#value-schema) - [Events](#events) - [Tree Themes](#tree-themes) - [Examples](#examples) - [Basic Usage](#basic-usage) - [Custom Durations](#custom-durations) - [Listening to Events](#listening-to-events) - [Updating the Timer Programmatically](#updating-the-timer-programmatically) - [API Usage](#api-usage) - [MCP (Model Context Protocol) Usage](#mcp-usage) - [Customization](#customization) - [Best Practices](#best-practices) --- ## 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+ ```bash pip install "gradio>=6.0" ``` --- ## Quick Start ```python 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](#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: ```python { "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:** ```python {"elapsed": 0, "running": False, "sessions": 0, "total_minutes": 0} ``` **API Schema (JSON):** ```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 ```python 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):** ```python 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 ```python 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 ```python 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 ```python 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. ```python 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 ```python 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 ```python 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 ```bash 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 ```python 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`** ```json { "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`** ```json { "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 ```python # 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 ```python 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 ```python timer = PomodoroTimer( tree_theme="classic", crown_color="#9C27B0", # Purple crown fruit_color="#FFEB3B", # Yellow fruit ) ``` ### Custom Mode Colors ```python 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 ```python # ✅ 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: ```python 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 ```python 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 ```python 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](https://gradio.app) and the new `gr.HTML` custom component system. Created as a demo for the Gradio HTML component capabilities.