February 8, 2026live

FocusBot: A Pomodoro Coach Chatbot

A glassmorphism Pomodoro timer with a Gemini-powered study coach that flips personality between a strict two-sentence tutor in Focus mode and a Stoic-quoting conversationalist on breaks — a small weekend-scale experiment with the Gemini API, hosted free on Azure.

Python 3.11Flask 3.1.2google-genai 1.61.0GunicornGemini 2.5 Flash LiteAzure App Service

I wanted to do a brief experiment with the Gemini API, so I built the smallest thing that would still be useful to me: a Pomodoro timer with a chatbot bolted on. The twist is that the bot is mode-aware — while the 25-minute timer runs it's a strict tutor that answers in two sentences and refuses to chat about anything off-topic, and when you flip to Break mode it loosens up, gets witty, and starts offering Stoic quotes and asking for Feynman-style explanations of whatever you just studied.

FocusBot is live at pomodochat.tech. It's a single Flask route, a single HTML file, and a lot of prompt engineering — which turned out to be exactly the right size for learning how the new Gemini SDK actually behaves.

Contents

The Timer and the Two Personas

The whole frontend is one file, templates/index.html — vanilla JavaScript, no framework, wrapped in an iOS-flavored glassmorphism UI: animated gradient background, floating blur blobs, backdrop-filter on everything. The timer is the classic 25:00 with Start / Pause / Reset, counting down in a one-second setInterval and throwing an alert when the Pomodoro completes.

Next to the timer sit a topic input ("C Pointers, Calculus...") and a Focus/Break toggle switch. Those two controls are the entire persona system: every message the frontend sends to the backend carries is_focus_mode and study_topic, and the backend prepends a mode-specific instruction block to the user's message on every single turn:

[SYSTEM UPDATE: FOCUS MODE ACTIVE]
You are FocusBot. The user is studying {study_topic}.
- Answer concisley (max 2 sentences).
- FIRMLY REFUSE off-topic chatter.

Break mode swaps that for "be witty and conversational, mention Stoic quotes or ask for Feynman explanations." Injecting the instructions per-turn instead of once at session creation means the bot obeys the toggle's current position mid-conversation — flip the switch and the very next reply changes personality. (Yes, "concisley" is really in the prompt. Gemini doesn't seem to mind.)

Backend: One Flask Route

The server is app.py — about a hundred lines of Flask 3.1.2. GET / renders the page; POST /chat does everything else. Each request creates or retrieves a Gemini chat via the google-genai SDK (1.61.0):

chat = client.chats.create(
    model="gemini-2.5-flash-lite",
    config=types.GenerateContentConfig(
        temperature=0.7,
        safety_settings=[
            types.SafetySetting(category="HARM_CATEGORY_DANGEROUS_CONTENT", threshold="BLOCK_NONE"),
            # ... same for harassment, hate speech, explicit content
        ]
    )
)

Two config choices worth explaining. The safety thresholds are set to BLOCK_NONE across all four categories because default settings kept false-positive-crashing on ordinary CS vocabulary — ask about "killing a process" in an OS course and the default filter reads it as a threat. And the app started life on the deprecated google.generativeai package; migrating to the new google-genai SDK meant refactoring the client initialization and how prompts get passed, which was half the point of the experiment.

Chat memory is a plain Python dictionary: chat_sessions maps a client-generated session_id to a live chat object, so follow-up messages replay through the same Gemini conversation. It's the simplest possible fix for HTTP's statelessness — no database, no Redis, memory dies with the process. For a toy, that's fine. There is, however, a catch — see Results.

Rendering Math and Markdown

A study bot that can't render an integral is useless for calculus. Bot replies get parsed by Marked.js for markdown (bold, code blocks, lists), then MathJax 3 typesets any LaTeX in the freshly inserted message, with $...$ and \(...\) configured as inline math delimiters. Asking for integration by parts comes back as a properly rendered equation instead of raw TeX soup.

Running It Locally

git clone https://github.com/Manixh-S/Pomodoro-Coach-Chatbot.git
cd Pomodoro-Coach-Chatbot
python -m venv venv && venv\Scripts\activate   # Windows
pip install -r requirements.txt

Copy .env.example to .env and add your key — the app loads it with python-dotenv:

GEMINI_API_KEY=add_key_here
FLASK_APP=app.py

Then python app.py runs the Flask dev server in debug mode.

Deployment

Every push to main triggers a GitHub Actions workflow that deploys to an Azure App Service (Linux) app named pomochat. The workflow does a Python 3.11 test-install of requirements.txt, uploads the repo as an artifact (excluding the venv), and hands it to azure/webapps-deploy@v3; Azure's Oryx build engine then installs dependencies on the platform side, enabled by SCM_DO_BUILD_DURING_DEPLOYMENT=true in .deployment. In production the app runs under Gunicorn:

# startup.txt — App Service startup command
gunicorn --bind=0.0.0.0 --timeout 600 app:app

The pomodochat.tech domain points at Azure through Namecheap DNS, and the whole thing runs free on GitHub Student Developer Pack benefits, funded through January 2027.

Results

FocusBot is live at pomodochat.tech. The timer, the mode-aware personas, LaTeX and markdown rendering, and the Azure deployment all work in production.

One honest caveat: the session memory doesn't actually engage. The backend's chat_sessions dictionary is built to give each conversation continuity, but the frontend generates a new session_id inside every sendMessage() call instead of once per page load — so every message lands in a fresh Gemini chat and the bot never remembers the previous turn. The per-turn persona injection masks it well (the bot always knows your topic and mode, since those ride along with each request), which is exactly why I didn't catch it. The fix is moving one line of ID generation out of the function.

Roadmap

  1. Fix the session ID generation so the memory system the backend already has actually gets used.
  2. Persist chat history outside process memory, so an App Service restart doesn't wipe conversations.
  3. Replace the polyfill.io script tag — that CDN was compromised in 2024 and shouldn't be loaded anymore.
  4. Sync the bot's persona to the timer, so hitting 00:00 flips Focus to Break automatically.

FocusBot is live at pomodochat.tech, and the code — all two files of it — is at Manixh-S/Pomodoro-Coach-Chatbot.