DEV Community

Cover image for FocusBuddy - Offline Open-Source AI Task Chunker for Executive Dysfunction
Ruhi Chaudhari
Ruhi Chaudhari

Posted on

FocusBuddy - Offline Open-Source AI Task Chunker for Executive Dysfunction

This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend

What I Built

FocusBuddy is a 100% offline, privacy-first task decomposer designed to help individuals dealing with ADHD or executive dysfunction. When facing overwhelming projects—such as "make notes on certain topics of DBMS"—FocusBuddy uses local open-source AI to break the goal into bite-sized, 5-minute micro-steps. It presents these steps strictly one at a time to eliminate overthinking and cognitive overload.

I built this for a friend who frequently experiences task paralysis when starting large academic projects. What they said after testing it:

"Seeing only one step at a time stopped me from overthinking and helped me actually start studying without feeling overwhelmed!"

Demo

FocusBuddy UI Interface
(Run index.html in any browser while Ollama is running locally).

Code

Here is the complete source code (index.html) used to build the local frontend:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FocusBuddy</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; background: #0f172a; color: #f8fafc; }
        input, button { padding: 12px; font-size: 16px; margin-top: 10px; width: 100%; border-radius: 8px; box-sizing: border-box; border: none; }
        input { background: #1e293b; color: #fff; border: 1px solid #334155; }
        button { background: #2563eb; color: white; cursor: pointer; font-weight: bold; }
        button:hover { background: #1d4ed8; }
        .step-box { background: #1e293b; padding: 20px; border-radius: 8px; margin-top: 20px; border-left: 5px solid #3b82f6; }
        .hidden { display: none; }
        .btn-group { display: flex; gap: 10px; margin-top: 15px; }
    </style>
</head>
<body>
    <h1>⚡ FocusBuddy</h1>
    <p>Your offline, low-pressure task chunker.</p>

    <div id="input-section">
        <input type="text" id="taskInput" placeholder="What task feels overwhelming? (e.g., Clean my desk)">
        <button onclick="breakDownTask()">Break It Down 🚀</button>
    </div>

    <div id="loading" class="hidden" style="margin-top:20px;">⏳ Breaking task into micro-steps...</div>

    <div id="step-section" class="hidden">
        <div class="step-box">
            <h3 id="stepHeader">Step 1</h3>
            <p id="stepText" style="font-size: 18px; line-height: 1.5;"></p>
        </div>
        <div class="btn-group">
            <button onclick="prevStep()">⬅️ Previous</button>
            <button onclick="nextStep()">Next ➡️</button>
        </div>
    </div>

    <script>
        let steps = [];
        let currentStep = 0;

        async function breakDownTask() {
            const task = document.getElementById('taskInput').value;
            if (!task.trim()) return;

            document.getElementById('loading').classList.remove('hidden');
            document.getElementById('step-section').classList.add('hidden');

            const prompt = `Break down the task "${task}" into 4 to 6 bite-sized, 5-minute micro-steps. Return ONLY a valid JSON array of strings, like ["Step 1 text", "Step 2 text"]. Do not include markdown or commentary.`;

            try {
                const response = await fetch('http://localhost:11434/api/generate', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        model: 'llama3.2',
                        prompt: prompt,
                        stream: false
                    })
                });

                const data = await response.json();
                let text = data.response.trim();
                if (text.includes('```

')) {
                    text = text.replace(/
{% raw %}
```json|```
{% endraw %}
/g, '').trim();
                }
                steps = JSON.parse(text);
                currentStep = 0;
                showStep();
            } catch (err) {
                alert('Error connecting to Ollama. Make sure Ollama is running in the background!');
            } finally {
                document.getElementById('loading').classList.add('hidden');
            }
        }

        function showStep() {
            if (steps.length === 0) return;
            document.getElementById('step-section').classList.remove('hidden');
            document.getElementById('stepHeader').innerText = {% raw %}`Step ${currentStep + 1} of ${steps.length}`{% endraw %};
            document.getElementById('stepText').innerText = steps[currentStep];
        }

        function nextStep() {
            if (currentStep < steps.length - 1) {
                currentStep++;
                showStep();
            } else {
                alert('🎉 Amazing work! You completed all the steps.');
            }
        }

        function prevStep() {
            if (currentStep > 0) {
                currentStep--;
                showStep();
            }
        }
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)