ysharma HF Staff commited on
Commit
fa2cbec
·
verified ·
1 Parent(s): a485349

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +545 -3
README.md CHANGED
@@ -1,14 +1,556 @@
1
  ---
2
  title: Pomodoro Timer
3
- emoji: 😻
4
  colorFrom: red
5
  colorTo: red
6
  sdk: gradio
7
  sdk_version: 6.5.1
8
- app_file: app.py
9
  pinned: false
10
  license: mit
11
  short_description: 'Gamified Pomodoro Timer: a pixel-art tree grows as you focus'
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Pomodoro Timer
3
+ emoji: ⏲️🌳
4
  colorFrom: red
5
  colorTo: red
6
  sdk: gradio
7
  sdk_version: 6.5.1
8
+ app_file: pomodoro_forest.py
9
  pinned: false
10
  license: mit
11
  short_description: 'Gamified Pomodoro Timer: a pixel-art tree grows as you focus'
12
  ---
13
 
14
+ # 🍅 PomodoroTimer Component Documentation
15
+
16
+ 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!
17
+
18
+ ---
19
+
20
+ ## Table of Contents
21
+
22
+ - [Installation](#installation)
23
+ - [Quick Start](#quick-start)
24
+ - [Parameters](#parameters)
25
+ - [Value Schema](#value-schema)
26
+ - [Events](#events)
27
+ - [Tree Themes](#tree-themes)
28
+ - [Examples](#examples)
29
+ - [Basic Usage](#basic-usage)
30
+ - [Custom Durations](#custom-durations)
31
+ - [Listening to Events](#listening-to-events)
32
+ - [Updating the Timer Programmatically](#updating-the-timer-programmatically)
33
+ - [API Usage](#api-usage)
34
+ - [MCP (Model Context Protocol) Usage](#mcp-usage)
35
+ - [Customization](#customization)
36
+ - [Best Practices](#best-practices)
37
+
38
+ ---
39
+
40
+ ## Installation
41
+
42
+ The component is a single Python file. Copy `pomodoro_forest.py` into your project or import the `PomodoroTimer` class directly.
43
+
44
+ **Requirements:**
45
+ - Gradio 6.0+
46
+ - Python 3.9+
47
+
48
+ ```bash
49
+ pip install "gradio>=6.0"
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Quick Start
55
+
56
+ ```python
57
+ import gradio as gr
58
+ from pomodoro_forest import PomodoroTimer
59
+
60
+ with gr.Blocks() as demo:
61
+ timer = PomodoroTimer()
62
+
63
+ demo.launch()
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Parameters
69
+
70
+ | Parameter | Type | Default | Description |
71
+ |-----------|------|---------|-------------|
72
+ | `value` | `dict` | See below | Timer state (elapsed time, sessions, etc.) |
73
+ | `duration` | `int` | `25` | Duration of the current mode in minutes |
74
+ | `mode` | `str` | `"focus"` | Current mode: `"focus"`, `"short_break"`, or `"long_break"` |
75
+ | `tree_theme` | `str` | `"classic"` | Visual theme for the tree (see [Tree Themes](#tree-themes)) |
76
+ | `mode_color` | `str` | Auto | Override the mode's accent color (hex) |
77
+ | `trunk_color` | `str` | Auto | Override trunk color (hex) |
78
+ | `crown_color` | `str` | Auto | Override crown/leaves color (hex) |
79
+ | `crown_top_color` | `str` | Auto | Override crown top color (hex) |
80
+ | `fruit_color` | `str` | Auto | Override fruit color (hex) |
81
+
82
+ ---
83
+
84
+ ## Value Schema
85
+
86
+ The `value` parameter is a dictionary with the following structure:
87
+
88
+ ```python
89
+ {
90
+ "elapsed": int, # Seconds elapsed in current session (0 to duration*60)
91
+ "running": bool, # Whether the timer is currently running
92
+ "sessions": int, # Number of completed focus sessions (trees grown)
93
+ "total_minutes": int # Total minutes spent in focus mode
94
+ }
95
+ ```
96
+
97
+ **Default value:**
98
+ ```python
99
+ {"elapsed": 0, "running": False, "sessions": 0, "total_minutes": 0}
100
+ ```
101
+
102
+ **API Schema (JSON):**
103
+ ```json
104
+ {
105
+ "type": "object",
106
+ "properties": {
107
+ "elapsed": {"type": "integer"},
108
+ "running": {"type": "boolean"},
109
+ "sessions": {"type": "integer"},
110
+ "total_minutes": {"type": "integer"}
111
+ }
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Events
118
+
119
+ The PomodoroTimer component emits the following events:
120
+
121
+ | Event | Trigger | Event Data | Description |
122
+ |-------|---------|------------|-------------|
123
+ | `.submit()` | Session completes | None | Fired when a focus/break session reaches 100% |
124
+ | `.select()` | Mode button clicked | `{"mode": str}` | Fired when user clicks Focus/Short Break/Long Break |
125
+ | `.change()` | Value changes | None | Fired on any value change (every second while running) |
126
+
127
+ ### Accessing Event Data
128
+
129
+ ```python
130
+ def handle_mode_select(evt: gr.EventData, timer_val):
131
+ # Safely access the mode from event data
132
+ new_mode = evt._data.get("mode", "focus") if evt._data else "focus"
133
+ return new_mode
134
+
135
+ timer.select(fn=handle_mode_select, inputs=[timer], outputs=[...])
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Tree Themes
141
+
142
+ Five built-in themes are available:
143
+
144
+ | Theme | Trunk | Crown | Fruit | Best For |
145
+ |-------|-------|-------|-------|----------|
146
+ | `classic` | Brown | Green | Red | Default look |
147
+ | `cherry` | Dark brown | Pink | Hot pink | Spring vibes |
148
+ | `autumn` | Brown | Orange | Deep orange | Fall season |
149
+ | `winter` | Gray | Silver | Light blue | Winter/holidays |
150
+ | `sakura` | Dark brown | Light pink | Pink | Japanese aesthetic |
151
+
152
+ **Theme colors (for reference):**
153
+ ```python
154
+ TREE_THEMES = {
155
+ "classic": {"trunk": "#8B5E3C", "crown": "#2ecc71", "crown_top": "#00b894", "fruit": "#e74c3c"},
156
+ "cherry": {"trunk": "#5D4037", "crown": "#F8BBD9", "crown_top": "#F48FB1", "fruit": "#E91E63"},
157
+ "autumn": {"trunk": "#6D4C41", "crown": "#FF9800", "crown_top": "#FFC107", "fruit": "#FF5722"},
158
+ "winter": {"trunk": "#455A64", "crown": "#B0BEC5", "crown_top": "#ECEFF1", "fruit": "#81D4FA"},
159
+ "sakura": {"trunk": "#4E342E", "crown": "#FCE4EC", "crown_top": "#F8BBD0", "fruit": "#EC407A"},
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Examples
166
+
167
+ ### Basic Usage
168
+
169
+ ```python
170
+ import gradio as gr
171
+ from pomodoro_forest import PomodoroTimer
172
+
173
+ with gr.Blocks() as demo:
174
+ gr.Markdown("# My Pomodoro App")
175
+ timer = PomodoroTimer(duration=25, mode="focus", tree_theme="classic")
176
+
177
+ demo.launch()
178
+ ```
179
+
180
+ ### Custom Durations
181
+
182
+ ```python
183
+ import gradio as gr
184
+ from pomodoro_forest import PomodoroTimer, _update_timer
185
+
186
+ with gr.Blocks() as demo:
187
+ timer = PomodoroTimer(duration=50, mode="focus") # 50-minute focus
188
+
189
+ # Quick preset buttons
190
+ with gr.Row():
191
+ btn_25 = gr.Button("25 min")
192
+ btn_50 = gr.Button("50 min")
193
+
194
+ btn_25.click(
195
+ fn=lambda v: _update_timer({**v, "elapsed": 0}, 25, "focus", "classic"),
196
+ inputs=[timer],
197
+ outputs=[timer]
198
+ )
199
+ btn_50.click(
200
+ fn=lambda v: _update_timer({**v, "elapsed": 0}, 50, "focus", "classic"),
201
+ inputs=[timer],
202
+ outputs=[timer]
203
+ )
204
+
205
+ demo.launch()
206
+ ```
207
+
208
+ ### Listening to Events
209
+
210
+ ```python
211
+ import gradio as gr
212
+ from pomodoro_forest import PomodoroTimer, _update_timer
213
+
214
+ with gr.Blocks() as demo:
215
+ timer = PomodoroTimer()
216
+ status = gr.Textbox(label="Status")
217
+
218
+ # When a session completes
219
+ def on_complete(timer_val):
220
+ sessions = timer_val.get("sessions", 0)
221
+ return f"🎉 Congratulations! You've grown {sessions} trees!"
222
+
223
+ timer.submit(fn=on_complete, inputs=[timer], outputs=[status])
224
+
225
+ demo.launch()
226
+ ```
227
+
228
+ ### Updating the Timer Programmatically
229
+
230
+ > ⚠️ **Important:** Always use `gr.HTML(...)` to update props, never return a new `PomodoroTimer()` instance.
231
+
232
+ ```python
233
+ import gradio as gr
234
+ from pomodoro_forest import PomodoroTimer, _update_timer, TREE_THEMES, MODE_COLORS
235
+
236
+ with gr.Blocks() as demo:
237
+ timer = PomodoroTimer()
238
+
239
+ # Add 5 sessions programmatically (for testing)
240
+ def add_sessions(timer_val):
241
+ new_val = {
242
+ **timer_val,
243
+ "sessions": timer_val.get("sessions", 0) + 5,
244
+ "total_minutes": timer_val.get("total_minutes", 0) + 125
245
+ }
246
+ return _update_timer(new_val, 25, "focus", "classic")
247
+
248
+ btn = gr.Button("Add 5 Sessions (Demo)")
249
+ btn.click(fn=add_sessions, inputs=[timer], outputs=[timer])
250
+
251
+ demo.launch()
252
+ ```
253
+
254
+ ---
255
+
256
+ ## API Usage
257
+
258
+ When your Gradio app is running, you can interact with the PomodoroTimer via the API.
259
+
260
+ ### Get Current State
261
+
262
+ ```python
263
+ from gradio_client import Client
264
+
265
+ client = Client("http://localhost:7860")
266
+
267
+ # If your timer is an input to an API endpoint
268
+ result = client.predict(
269
+ {"elapsed": 0, "running": False, "sessions": 3, "total_minutes": 75},
270
+ api_name="/your_endpoint"
271
+ )
272
+ ```
273
+
274
+ ### Python Client Example
275
+
276
+ ```python
277
+ from gradio_client import Client
278
+
279
+ client = Client("http://localhost:7860")
280
+
281
+ # Start a session with pre-existing data
282
+ timer_state = {
283
+ "elapsed": 0,
284
+ "running": False,
285
+ "sessions": 5,
286
+ "total_minutes": 125
287
+ }
288
+
289
+ # Call your function that takes timer as input
290
+ result = client.predict(timer_state, api_name="/process_timer")
291
+ print(result)
292
+ ```
293
+
294
+ ### REST API
295
+
296
+ ```bash
297
+ curl -X POST http://localhost:7860/api/your_endpoint \
298
+ -H "Content-Type: application/json" \
299
+ -d '{
300
+ "data": [{
301
+ "elapsed": 0,
302
+ "running": false,
303
+ "sessions": 10,
304
+ "total_minutes": 250
305
+ }]
306
+ }'
307
+ ```
308
+
309
+ ---
310
+
311
+ ## MCP Usage
312
+
313
+ The PomodoroTimer component works with Gradio's MCP (Model Context Protocol) support, allowing AI assistants to interact with it.
314
+
315
+ ### Exposing via MCP
316
+
317
+ ```python
318
+ import gradio as gr
319
+ from pomodoro_forest import PomodoroTimer, _update_timer
320
+
321
+ with gr.Blocks() as demo:
322
+ timer = PomodoroTimer()
323
+ output = gr.JSON(label="Timer State")
324
+
325
+ def get_timer_state(timer_val):
326
+ """Get the current Pomodoro timer state.
327
+
328
+ Returns the timer's current state including elapsed time,
329
+ running status, completed sessions, and total focus minutes.
330
+ """
331
+ return timer_val
332
+
333
+ def set_timer_sessions(timer_val, sessions: int, total_minutes: int):
334
+ """Set the Pomodoro timer's session count.
335
+
336
+ Args:
337
+ sessions: Number of completed focus sessions
338
+ total_minutes: Total minutes spent focusing
339
+ """
340
+ new_val = {**timer_val, "sessions": sessions, "total_minutes": total_minutes}
341
+ return _update_timer(new_val, 25, "focus", "classic"), new_val
342
+
343
+ # Expose as API endpoints for MCP
344
+ get_btn = gr.Button("Get State")
345
+ get_btn.click(
346
+ fn=get_timer_state,
347
+ inputs=[timer],
348
+ outputs=[output],
349
+ api_name="get_pomodoro_state" # MCP-accessible endpoint
350
+ )
351
+
352
+ with gr.Row():
353
+ sessions_input = gr.Number(label="Sessions", value=0)
354
+ minutes_input = gr.Number(label="Total Minutes", value=0)
355
+
356
+ set_btn = gr.Button("Set Sessions")
357
+ set_btn.click(
358
+ fn=set_timer_sessions,
359
+ inputs=[timer, sessions_input, minutes_input],
360
+ outputs=[timer, output],
361
+ api_name="set_pomodoro_sessions" # MCP-accessible endpoint
362
+ )
363
+
364
+ demo.launch()
365
+ ```
366
+
367
+ ### MCP Tool Definitions
368
+
369
+ When used with MCP, the following tools become available:
370
+
371
+ **`get_pomodoro_state`**
372
+ ```json
373
+ {
374
+ "name": "get_pomodoro_state",
375
+ "description": "Get the current Pomodoro timer state",
376
+ "parameters": {
377
+ "timer_val": {
378
+ "type": "object",
379
+ "properties": {
380
+ "elapsed": {"type": "integer"},
381
+ "running": {"type": "boolean"},
382
+ "sessions": {"type": "integer"},
383
+ "total_minutes": {"type": "integer"}
384
+ }
385
+ }
386
+ }
387
+ }
388
+ ```
389
+
390
+ **`set_pomodoro_sessions`**
391
+ ```json
392
+ {
393
+ "name": "set_pomodoro_sessions",
394
+ "description": "Set the Pomodoro timer's session count",
395
+ "parameters": {
396
+ "sessions": {"type": "integer", "description": "Number of completed sessions"},
397
+ "total_minutes": {"type": "integer", "description": "Total focus minutes"}
398
+ }
399
+ }
400
+ ```
401
+
402
+ ### Using with Claude or Other MCP Clients
403
+
404
+ ```python
405
+ # Example: AI assistant querying your Pomodoro app via MCP
406
+ # The assistant can call these tools to interact with the timer
407
+
408
+ # Get current state
409
+ state = mcp_client.call_tool("get_pomodoro_state", {})
410
+ # Returns: {"elapsed": 300, "running": true, "sessions": 3, "total_minutes": 75}
411
+
412
+ # Set sessions (e.g., restore from saved data)
413
+ mcp_client.call_tool("set_pomodoro_sessions", {
414
+ "sessions": 10,
415
+ "total_minutes": 250
416
+ })
417
+ ```
418
+
419
+ ---
420
+
421
+ ## Customization
422
+
423
+ ### Adding Custom Themes
424
+
425
+ ```python
426
+ from pomodoro_forest import TREE_THEMES
427
+
428
+ # Add your own theme
429
+ TREE_THEMES["ocean"] = {
430
+ "trunk": "#1565C0",
431
+ "crown": "#4FC3F7",
432
+ "crown_top": "#81D4FA",
433
+ "fruit": "#00BCD4"
434
+ }
435
+
436
+ # Use it
437
+ timer = PomodoroTimer(tree_theme="ocean")
438
+ ```
439
+
440
+ ### Override Individual Colors
441
+
442
+ ```python
443
+ timer = PomodoroTimer(
444
+ tree_theme="classic",
445
+ crown_color="#9C27B0", # Purple crown
446
+ fruit_color="#FFEB3B", # Yellow fruit
447
+ )
448
+ ```
449
+
450
+ ### Custom Mode Colors
451
+
452
+ ```python
453
+ from pomodoro_forest import MODE_COLORS
454
+
455
+ MODE_COLORS["focus"] = "#9C27B0" # Purple for focus
456
+ MODE_COLORS["short_break"] = "#00BCD4" # Cyan for short break
457
+ MODE_COLORS["long_break"] = "#FF9800" # Orange for long break
458
+ ```
459
+
460
+ ---
461
+
462
+ ## Best Practices
463
+
464
+ ### 1. Always Use `gr.HTML()` for Updates
465
+
466
+ ```python
467
+ # ✅ Correct
468
+ def update_timer(timer_val):
469
+ return gr.HTML(value=new_val, duration=25, mode="focus", ...)
470
+
471
+ # ❌ Wrong - causes issues
472
+ def update_timer(timer_val):
473
+ return PomodoroTimer(value=new_val, duration=25, mode="focus")
474
+ ```
475
+
476
+ ### 2. Use Helper Functions
477
+
478
+ Import and use the provided helper functions:
479
+
480
+ ```python
481
+ from pomodoro_forest import _update_timer, _update_theme_only
482
+
483
+ # Full update
484
+ timer_output = _update_timer(value, duration, mode, theme)
485
+
486
+ # Theme-only update
487
+ timer_output = _update_theme_only(theme, mode)
488
+ ```
489
+
490
+ ### 3. Handle Events Safely
491
+
492
+ ```python
493
+ def handle_event(evt: gr.EventData, timer_val):
494
+ # Always check if _data exists
495
+ try:
496
+ data = evt._data.get("key", "default") if evt._data else "default"
497
+ except:
498
+ data = "default"
499
+ return data
500
+ ```
501
+
502
+ ### 4. Preserve State Across Updates
503
+
504
+ ```python
505
+ def my_handler(timer_val, new_duration):
506
+ # Spread existing state, only change what's needed
507
+ new_val = {**timer_val, "elapsed": 0}
508
+ return _update_timer(new_val, new_duration, "focus", "classic")
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Component Architecture
514
+
515
+ ```
516
+ PomodoroTimer (extends gr.HTML)
517
+ ├── html_template → Renders timer ring, tree scene, controls, stats
518
+ ├── css_template → Styles with ${prop} placeholders for dynamic colors
519
+ ├── js_on_load → Handles start/pause, reset, mode switching
520
+ └── value → Dict holding timer state
521
+ ```
522
+
523
+ **File Structure:**
524
+ ```
525
+ pomodoro_forest.py
526
+ ├── Constants (DURATIONS, MODE_COLORS, TREE_THEMES)
527
+ ├── Templates (HTML_TEMPLATE, CSS_TEMPLATE, JS_ON_LOAD)
528
+ ├── PomodoroTimer class
529
+ ├── Helper functions (_update_timer, _update_theme_only)
530
+ └── Demo app (with gr.Blocks)
531
+ ```
532
+
533
+ ---
534
+
535
+ ## Troubleshooting
536
+
537
+ | Issue | Cause | Solution |
538
+ |-------|-------|----------|
539
+ | Colors don't update | Using Python string formatting instead of `${prop}` | Use template syntax: `${mode_color}` |
540
+ | "Multiple values for argument" error | Returning subclass instance | Return `gr.HTML(...)` instead |
541
+ | Event data is None | Accessing wrong event or wrong data key | Check `evt._data` exists before accessing |
542
+ | Timer doesn't re-render | Mutating value in place | Always spread: `{...timer_val, key: newVal}` |
543
+
544
+ ---
545
+
546
+ ## License
547
+
548
+ MIT License - Feel free to use, modify, and distribute!
549
+
550
+ ---
551
+
552
+ ## Credits
553
+
554
+ Built with [Gradio 6](https://gradio.app) and the new `gr.HTML` custom component system.
555
+
556
+ Created as a demo for the Gradio HTML component capabilities.