# Project export: ADHD BDBD (ADHD Body Double BuDdy)

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: A pizza-themed focus app designed for students with ADHD, complete with a customizable pizza-slice body double and a daily ‘pizza plan’ study schedule.
- Devpost: https://devpost.com/software/adhd-bdbd-adhd-body-double-buddy
- GitHub: https://github.com/aiadhdexecfunction/Fish_voice/
- Video: https://www.youtube.com/embed/MUVG4_ucHWE?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Benjamin McIntyre (29 commits), yaxin-lin (3 commits)

## Devpost submission (written by the team)

### Inspiration

We built ADHD BDBD because most of us on the team have ADHD, and we know firsthand how hard it can be to study alone. One effective strategy for staying engaged is called body doubling: having someone present while you work. So we thought… What if that ‘someone’ was a slice of pizza?

### What it does

ADHD BDBD allows students to organize their day in a way that feels fun, structured, and supportive. Users can add their tasks manually, or import them directly from platforms like Gmail and Canvas. Each task can then be broken into subtasks, either manually or through AI-generated recommendations. Once those subtasks are prepared, students can choose the ones they want to focus on that day, which forms their ‘daily pizza’ study plan. To help maintain focus, users are joined by a customizable pizza-slice body double who acts as a virtual study companion. They can personalize its personality and voice. Then users choose different virtual study environments such as a coffee shop, a library, or a park. A built-in Pomodoro timer helps guide focused work sessions, and at the end of each session, the body double prompts a brief reflection to help the student consolidate what they’ve learned. In addition, the app includes a planning and overview page where students can evaluate tasks by urgency and importance, view upcoming deadlines, and track streaks to support habit building.

### How we built it

After researching about study habits of ADHD students, we decided to make the website as accessible as possible by adding many different functions to improve efficiency. We also fun and dynamic by incorporating our web development with engaging fonts and colors. Our backend turns a simple chat UI into a voice-enabled, tool-using, ADHD-friendly companion. It powers: • Stateful AI chat (Letta) that can use tools (email/calendar/files/LMS/tasks) via MCP and composio • Shared To-Do list (DB-backed) that both the agent and the UI can add/edit/complete/delete in real time • Pomodoro coaching with gentle follow-ups and spoken prompts • Voice via Fish Voice (Fish Audio) for real-time TTS • OAuthed integrations (Gmail, Google Calendar, Canvas LMS, Google Drive, Google Tasks) through Composio

### Challenges we ran into

We faced technical challenges, especially with communications between frontend and backend. The integration part was the most difficult for us. We realized that we should have communicated better with clear and meaningful talks, not just yapping.

### Accomplishments we're proud of

Making something that would actually benefit people with different neural systems and providing them an accessible app.

### What we learned

Throughout the development process, we learned that ADHD presents itself differently for everyone, and designing for flexibility and accessibility is essential.

### What's next

Partner with a professional, medical institute to conduct research on the effectiveness of the product. Make a 3D customizable body double based on the users image input and description Include a web extension so that users can move on to other web pages without a loss of time

## README (from the GitHub repository)

## Inspiration
We built ADHD BDBD because most of us on the team have ADHD, and we know firsthand how hard it can be to study alone. One effective strategy for staying engaged is called body doubling: having someone present while you work.
So we thought… What if that ‘someone’ was a slice of pizza?

## What it does
ADHD BDBD allows students to organize their day in a way that feels fun, structured, and supportive. Users can add their tasks manually, or import them directly from platforms like Gmail and Canvas. Each task can then be broken into subtasks, either manually or through AI-generated recommendations. Once those subtasks are prepared, students can choose the ones they want to focus on that day, which forms their ‘daily pizza’ study plan.

To help maintain focus, users are joined by a customizable pizza-slice body double who acts as a virtual study companion. They can personalize its personality and voice. Then users choose different virtual study environments such as a coffee shop, a library, or a park. A built-in Pomodoro timer helps guide focused work sessions, and at the end of each session, the body double prompts a brief reflection to help the student consolidate what they’ve learned.

In addition, the app includes a planning and overview page where students can evaluate tasks by urgency and importance, view upcoming deadlines, and track streaks to support habit building. 

## How we built it
After researching about study habits of ADHD students, we decided to make the website as accessible as possible by adding many different functions to improve  efficiency. We also fun and dynamic by incorporating our web development with engaging fonts and colors. 

Our backend turns a simple chat UI into a voice-enabled, tool-using, ADHD-friendly companion. It powers:
	•	Stateful AI chat (Letta) that can use tools (email/calendar/files/LMS/tasks) via MCP and composio
	•	Shared To-Do list (DB-backed) that both the agent and the UI can add/edit/complete/delete in real time
	•	Pomodoro coaching with gentle follow-ups and spoken prompts
	•	Voice via Fish Voice (Fish Audio) for real-time TTS
	•	OAuthed integrations (Gmail, Google Calendar, Canvas LMS, Google Drive, Google Tasks) through Composio

## Challenges we ran into
We faced technical challenges, especially with communications between frontend and backend. The integration part was the most difficult for us. We realized that we should have communicated better with clear and meaningful talks, not just yapping.

## Accomplishments that we're proud of
Making something that would actually benefit people with different neural systems and providing them an accessible app. 

## What we learned
Throughout the development process, we learned that ADHD presents itself differently for everyone, and designing for flexibility and accessibility is essential. 

## What's next for ADHD BDBD (ADHD Body Double BuDdy)
1. Partner with a professional, medical institute to conduct research on the effectiveness of the product. 
2. Make a 3D customizable body double based on the users image input and description
3. Include a web extension so that users can move on to other web pages without a loss of time


## Detected evidence (automated analysis)

Indexed codebase: 107 recognized source files, 623 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- Tailwind CSS (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (111 of 111)

```
.gitignore
app.py
composio_integration.py
config.py
database.py
fish_audio.py
flake.nix
frontend/index.html
frontend/package.json
frontend/README.md
frontend/src/App.tsx
frontend/src/Attributions.md
frontend/src/components/AddTaskDialog.tsx
frontend/src/components/AllTasksTab.tsx
frontend/src/components/CanvasDialog.tsx
frontend/src/components/CookieTodoList.tsx
frontend/src/components/DailyTasksTab.tsx
frontend/src/components/DeadlinesList.tsx
frontend/src/components/figma/ImageWithFallback.tsx
frontend/src/components/GlobalChat.tsx
frontend/src/components/GmailDialog.tsx
frontend/src/components/ImmersiveWorkSession.tsx
frontend/src/components/IntroTutorial.tsx
frontend/src/components/PersonalityCustomization.tsx
frontend/src/components/PizzaBodyDouble.tsx
frontend/src/components/PizzaTodoList.tsx
frontend/src/components/PrioritizationGraph.tsx
frontend/src/components/Register.tsx
frontend/src/components/TaskInbox.tsx
frontend/src/components/TaskInput.tsx
frontend/src/components/ui/accordion.tsx
frontend/src/components/ui/alert-dialog.tsx
frontend/src/components/ui/alert.tsx
frontend/src/components/ui/aspect-ratio.tsx
frontend/src/components/ui/avatar.tsx
frontend/src/components/ui/badge.tsx
frontend/src/components/ui/breadcrumb.tsx
frontend/src/components/ui/button.tsx
frontend/src/components/ui/calendar.tsx
frontend/src/components/ui/card.tsx
frontend/src/components/ui/carousel.tsx
frontend/src/components/ui/chart.tsx
frontend/src/components/ui/checkbox.tsx
frontend/src/components/ui/collapsible.tsx
frontend/src/components/ui/command.tsx
frontend/src/components/ui/context-menu.tsx
frontend/src/components/ui/dialog.tsx
frontend/src/components/ui/drawer.tsx
frontend/src/components/ui/dropdown-menu.tsx
frontend/src/components/ui/form.tsx
frontend/src/components/ui/hover-card.tsx
frontend/src/components/ui/input-otp.tsx
frontend/src/components/ui/input.tsx
frontend/src/components/ui/label.tsx
frontend/src/components/ui/menubar.tsx
frontend/src/components/ui/navigation-menu.tsx
frontend/src/components/ui/pagination.tsx
frontend/src/components/ui/popover.tsx
frontend/src/components/ui/progress.tsx
frontend/src/components/ui/radio-group.tsx
frontend/src/components/ui/resizable.tsx
frontend/src/components/ui/scroll-area.tsx
frontend/src/components/ui/select.tsx
frontend/src/components/ui/separator.tsx
frontend/src/components/ui/sheet.tsx
frontend/src/components/ui/sidebar.tsx
frontend/src/components/ui/skeleton.tsx
frontend/src/components/ui/slider.tsx
frontend/src/components/ui/sonner.tsx
frontend/src/components/ui/switch.tsx
frontend/src/components/ui/table.tsx
frontend/src/components/ui/tabs.tsx
frontend/src/components/ui/textarea.tsx
frontend/src/components/ui/toggle-group.tsx
frontend/src/components/ui/toggle.tsx
frontend/src/components/ui/tooltip.tsx
frontend/src/components/ui/use-mobile.ts
frontend/src/components/ui/utils.ts
frontend/src/components/VirtualStudyRoom.tsx
frontend/src/config/api.ts
frontend/src/contexts/AuthContext.tsx
frontend/src/guidelines/Guidelines.md
frontend/src/index.css
frontend/src/main.tsx
frontend/src/styles/globals.css
frontend/src/utils/api.ts
frontend/src/utils/cookieStorage.ts
frontend/src/utils/messageGenerator.ts
frontend/src/utils/tasksApi.ts
frontend/src/utils/voiceMapping.ts
frontend/vite.config.ts
letta_integration.py
models.py
personalities.py
pomodoro.py
README.md
requirements.txt
routes/accounts.py
routes/chat.py
routes/composio.py
routes/health.py
routes/pomodoro.py
routes/preferences.py
routes/tasks.py
routes/voice.py
routes/websocket.py
run_with_composio.py
shared_state.py
tasks.py
test.html
websocket.py
```

### Dependencies

- frontend/package.json: @radix-ui/react-accordion@^1.2.3, @radix-ui/react-alert-dialog@^1.1.6, @radix-ui/react-aspect-ratio@^1.1.2, @radix-ui/react-avatar@^1.1.3, @radix-ui/react-checkbox@^1.1.4, @radix-ui/react-collapsible@^1.1.3, @radix-ui/react-context-menu@^2.2.6, @radix-ui/react-dialog@^1.1.6, @radix-ui/react-dropdown-menu@^2.1.6, @radix-ui/react-hover-card@^1.1.6, @radix-ui/react-label@^2.1.2, @radix-ui/react-menubar@^1.1.6, @radix-ui/react-navigation-menu@^1.2.5, @radix-ui/react-popover@^1.1.6, @radix-ui/react-progress@^1.1.2, @radix-ui/react-radio-group@^1.2.3, @radix-ui/react-scroll-area@^1.2.3, @radix-ui/react-select@^2.1.6, @radix-ui/react-separator@^1.1.2, @radix-ui/react-slider@^1.2.3, @radix-ui/react-slot@^1.1.2, @radix-ui/react-switch@^1.1.3, @radix-ui/react-tabs@^1.1.3, @radix-ui/react-toggle@^1.1.2, @radix-ui/react-toggle-group@^1.1.2, @radix-ui/react-tooltip@^1.1.8, @types/node@^20.10.0, @vitejs/plugin-react-swc@^3.10.2, class-variance-authority@^0.7.1, clsx@*, cmdk@^1.1.1, embla-carousel-react@^8.6.0, input-otp@^1.4.2, lucide-react@^0.487.0, motion@*, next-themes@^0.4.6, react@^18.3.1, react-day-picker@^8.10.1, react-dnd@*, react-dnd-html5-backend@*, react-dom@^18.3.1, react-hook-form@^7.55.0, react-resizable-panels@^2.1.7, recharts@^2.15.2, sonner@^2.0.3, tailwind-merge@*, vaul@^1.1.2, vite@6.3.5
- requirements.txt: composio, fastapi, fish-audio-sdk, letta_client, pydantic, pydantic-settings, python-dotenv, python-multipart, requests, uvicorn[standard]

### Recent commits (newest first)

- Update README.md
- Give cookies for pizza
- Add chat functionality.
- Make frontend use backend for voice pref
- Fix voice
- Link personalities
- Added chat history to backend
- Prevent onboarding from appearing every time.
- Integrate tasks, and add some missing files
- Integrate tasks
- Fix blank page, integrate login, remember to add tasks
- Add ability to run frontend
- Add frontend
- Add tasks
- Fix creation from template
- Make agents use ADHD_Agent template
- Integrate yaxin's work into the refactor
- Refactor
- Disable voice by default to prevent unexpected audio
- Resolve merge conflicts in app.py and test.html

## Key source files (fetched from GitHub, selected and truncated for size)

### frontend/src/Attributions.md

```markdown
This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md).

This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license).
```

### frontend/src/guidelines/Guidelines.md

```markdown
**Add your own guidelines here**
<!--

System Guidelines

Use this file to provide the AI with rules and guidelines you want it to follow.
This template outlines a few examples of things you can add. You can add your own sections and format it to suit your needs

TIP: More context isn't always better. It can confuse the LLM. Try and add the most important rules you need

# General guidelines

Any general rules you want the AI to follow.
For example:

* Only use absolute positioning when necessary. Opt for responsive and well structured layouts that use flexbox and grid by default
* Refactor code as you go to keep code clean
* Keep file sizes small and put helper functions and components in their own files.

--------------

# Design system guidelines
Rules for how the AI should make generations look like your company's design system

Additionally, if you select a design system to use in the prompt box, you can reference
your design system's components, tokens, variables and components.
For example:

* Use a base font-size of 14px
* Date formats should always be in the format “Jun 10”
* The bottom toolbar should only ever have a maximum of 4 items
* Never use the floating action button with the bottom toolbar
* Chips should always come in sets of 3 or more
* Don't use a dropdown if there are 2 or fewer options

You can also create sub sections and add more specific details
For example:


## Button
The Button component is a fundamental interactive element in our design system, designed to trigger actions or navigate
users through the application. It provides visual feedback and clear affordances to enhance user experience.

### Usage
Buttons should be used for important actions that users need to take, such as form submissions, confirming choices,
or initiating processes. They communicate interactivity and should have clear, action-oriented labels.

### Variants
* Primary Button
  * Purpose : Used for the main action in a section or page
  * Visual Style : Bold, filled with the primary brand color
  * Usage : One primary button per section to guide users toward the most important action
* Secondary Button
  * Purpose : Used for alternative or supporting actions
  * Visual Style : Outlined with the primary color, transparent background
  * Usage : Can appear alongside a primary button for less important actions
* Tertiary Button
  * Purpose : Used for the least important actions
  * Visual Style : Text-only with no border, using primary color
  * Usage : For actions that should be available but not emphasized
-->

```

### requirements.txt

```
fastapi
uvicorn[standard]
requests
pydantic
pydantic-settings
python-dotenv
python-multipart
letta_client
fish-audio-sdk
composio
```

### frontend/package.json

```

  {
      "name": "ADHD Focus & Body Doubling",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
          "@radix-ui/react-accordion": "^1.2.3",
          "@radix-ui/react-alert-dialog": "^1.1.6",
          "@radix-ui/react-aspect-ratio": "^1.1.2",
          "@radix-ui/react-avatar": "^1.1.3",
          "@radix-ui/react-checkbox": "^1.1.4",
          "@radix-ui/react-collapsible": "^1.1.3",
          "@radix-ui/react-context-menu": "^2.2.6",
          "@radix-ui/react-dialog": "^1.1.6",
          "@radix-ui/react-dropdown-menu": "^2.1.6",
          "@radix-ui/react-hover-card": "^1.1.6",
          "@radix-ui/react-label": "^2.1.2",
          "@radix-ui/react-menubar": "^1.1.6",
          "@radix-ui/react-navigation-menu": "^1.2.5",
          "@radix-ui/react-popover": "^1.1.6",
          "@radix-ui/react-progress": "^1.1.2",
          "@radix-ui/react-radio-group": "^1.2.3",
          "@radix-ui/react-scroll-area": "^1.2.3",
          "@radix-ui/react-select": "^2.1.6",
          "@radix-ui/react-separator": "^1.1.2",
          "@radix-ui/react-slider": "^1.2.3",
          "@radix-ui/react-slot": "^1.1.2",
          "@radix-ui/react-switch": "^1.1.3",
          "@radix-ui/react-tabs": "^1.1.3",
          "@radix-ui/react-toggle": "^1.1.2",
          "@radix-ui/react-toggle-group": "^1.1.2",
          "@radix-ui/react-tooltip": "^1.1.8",
          "class-variance-authority": "^0.7.1",
          "clsx": "*",
          "cmdk": "^1.1.1",
          "embla-carousel-react": "^8.6.0",
          "input-otp": "^1.4.2",
          "lucide-react": "^0.487.0",
          "motion": "*",
          "next-themes": "^0.4.6",
          "react": "^18.3.1",
          "react-day-picker": "^8.10.1",
          "react-dnd": "*",
          "react-dnd-html5-backend": "*",
          "react-dom": "^18.3.1",
          "react-hook-form": "^7.55.0",
          "react-resizable-panels": "^2.1.7",
          "recharts": "^2.15.2",
          "sonner": "^2.0.3",
          "tailwind-merge": "*",
          "vaul": "^1.1.2"
      },
      "devDependencies": {
          "@types/node": "^20.10.0",
          "@vitejs/plugin-react-swc": "^3.10.2",
          "vite": "6.3.5"
      },
      "scripts": {
          "dev": "vite",
          "build": "vite build"
      }
  }
```

### app.py

```python
import asyncio
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

# Import all route modules
from routes import voice, accounts, chat, preferences, pomodoro, composio, websocket, health, tasks

# Import core modules
from database import init_db
from websocket import voice_orchestrator, followup_scanner

# ===================== FastAPI & CORS =====================
app = FastAPI(title="BodyDouble — Letta + Fish Audio")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"], allow_credentials=True,
    allow_methods=["*"], allow_headers=["*"],
)

# Include all routers
app.include_router(voice.router)
app.include_router(accounts.router)
app.include_router(chat.router)
app.include_router(preferences.router)
app.include_router(pomodoro.router)
app.include_router(composio.router)
app.include_router(websocket.router)
app.include_router(health.router)
app.include_router(tasks.router)

# Initialize database
init_db()

# ===================== Startup tasks =====================
@app.on_event("startup")
async def startup():
    # Start background tasks
    asyncio.create_task(voice_orchestrator())
    asyncio.create_task(followup_scanner())

```

### frontend/src/main.tsx

```typescript

  import { createRoot } from "react-dom/client";
  import App from "./App.tsx";
  import "./index.css";

  createRoot(document.getElementById("root")!).render(<App />);
  
```

### frontend/src/App.tsx

```typescript
import React, { useState, useEffect, useCallback } from 'react';
import { fetchTasks, createTask, updateTask as updateTaskApi, deleteTask as deleteTaskApi } from './utils/tasksApi';
import { getVoiceToneFromModelId, VoiceTone } from './utils/voiceMapping';
import { saveDailyTasks, loadDailyTasks } from './utils/cookieStorage';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs';
import { Toaster, toast } from './components/ui/sonner';
import DailyTasksTab from './components/DailyTasksTab';
import AllTasksTab from './components/AllTasksTab';
import Register from './components/Register';
import IntroTutorial from './components/IntroTutorial';
import PersonalityCustomization from './components/PersonalityCustomization';
import GlobalChat from './components/GlobalChat';
import { Button } from './components/ui/button';
import { User, Settings, Volume2, VolumeX } from 'lucide-react';
import { AuthProvider, useAuth } from './contexts/AuthContext';

export interface Subtask {
  id: string;
  title: string;
  completed: boolean;
}

export interface Task {
  id: string;
  title: string;
  description?: string;
  urgency: number; // 0-100
  importance: number; // 0-100
  deadline?: Date;
  subtasks: Subtask[];
  source: 'manual' | 'gmail' | 'canvas';
  position?: { x: number; y: number }; // Position in graph
  orderInCaterpillar?: number;
}

export interface SessionNote {
  id: string;
  timestamp: Date;
  duration: number; // in minutes
  subtaskId: string;
  taskTitle: string;
  subtaskTitle: string;
  teachback: string;
  reflection: string;
}

function App() {
  const [tasks, setTasks] = useState<Task[]>([]);

  const [dailyTasks, setDailyTasks] = useState<string[]>([]); // Subtask IDs for today
  const [sessionNotes, setSessionNotes] = useState<SessionNote[]>([]);
  const [showRegister, setShowRegister] = useState(false);
  const { user, isAuthenticated, logout, loading } = useAuth();
  
  // Check if intro was already shown (from localStorage)
  const [showIntro, setShowIntro] = useState(() => {
    if (typeof window !== 'undefined') {
      return !localStorage.getItem('introCompleted');
    }
    return true;
  });
  const [showPersonalityCustomization, setShowPersonalityCustomization] = useState(false);
  const [personality, setPersonality] = useState<'gentle' | 'funny' | 'pushy'>('gentle');
  const [voiceTone, setVoiceTone] = useState<VoiceTone>('ariana');
  const [voiceEnabled, setVoiceEnabled] = useState(false);
  
  const userName = user?.username || 'Guest';
  const [tasksLoading, setTasksLoading] = useState(false);

  const loadPreferences = useCallback(async () => {
    if (!user?.username) return;
    
    try {
      const response = await fetch(`http://localhost:8000/prefs/${user.username}`);
      if (response.ok) {
        const data = await response.json();
        if (data.personality?.id) {
          setPersonality(data.personality.id as 'gentle' | 'funny' | 'pushy');
        }
        if (data.voice_model) {
          const voiceToneFromModel = getVoiceToneFromModelId(data.voice_model);
          if (voiceToneFromModel) {
            setVoiceTone(voiceToneFromModel);
          }
        }
      }
    } catch (error) {
      console.error('Failed to load preferences:', error);
    }
  }, [user?.username]);

  const loadTasks = useCallback(async () => {
    if (!user?.username) {
      console.log('No username, skipping task load');
      return;
    }
    
    console.log('Loading tasks for user:', user.username);
    setTasksLoading(true);
    try {
      const fetchedTasks = await fetchTasks();
      console.log('Fetched tasks:', fetchedTasks);
      setTasks(fetchedTasks || []);
    } catch (error) {
      console.error('Failed to load tasks:', error);
      // Fallback to mock data if backend fails
      console.log('Using fallback mock data');
      setTasks([
        {
          id: '1',
          title: 'Complete Math Assignment',
          description: 'Chapter 5 problems',
          urgency: 80,
          importance: 90,
          deadline: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000),
          source: 'canvas',
          subtasks: [
            { id: '1-1', title: 'Read Chapter 5', completed: false },
            { id: '1-2', title: 'Solve problems 1-10', completed: false },
            { id: '1-3', title: 'Review answers', completed: false },
          ],
          position: { x: 75, y: 85 },
          orderInCaterpillar: 0,
        },
        {
          id: '2',
          title: 'Study for Biology Quiz',
          description: 'Cell division and genetics',
          urgency: 60,
          importance: 70,
          deadline: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
          source: 'gmail',
          subtasks: [
            { id: '2-1', title: 'Review lecture notes', completed: false },
            { id: '2-2', title: 'Make flashcards', completed: false },
            { id: '2-3', title: 'Practice quiz questions', completed: false },
            { id: '2-4', title: 'Study with group', completed: false },
          ],
          position: { x: 55, y: 65 },
          orderInCaterpillar: 1,
        },
        {
          id: '3',
          title: 'Write English Essay',
          description: 'Analysis of Shakespeare',
          urgency: 40,
          importance: 85,
          deadline: new Date(Date.now() + 10 * 24 * 60 * 60 * 1000),
          source: 'manual',
          subtasks: [
            { id: '3-1', title: 'Research and outline', completed: false },
            { id: '3-2', title: 'Write first draft', completed: false },
            { id: '3-3', title: 'Revise and edit', completed: false },
            { id: '3-4', title: 'Final proofread', completed: false },
            { id: '3-5', title: 'Submit', completed: false },
          ],
          position: { x: 35, y: 80 },
          orderInCaterpillar: 2,
        },
      ]);

[truncated — 8033 more characters]
```

### shared_state.py

```python
from typing import Dict
from models import UserPrefs

# Global user preferences shared across all modules
user_prefs: Dict[str, UserPrefs] = {}

```

### run_with_composio.py

```python
# run_with_composio.py
from typing import List
from fastapi import FastAPI

from app import app as base_app         # 复用你现有的 app
from composio_plugin import router as composio_router, start_watchers
from users.router import router as users_router
from users.service import users_with_connected_accounts  # 用来读取已完成 OAuth 的用户

app: FastAPI = base_app
app.include_router(users_router)        # /users/*
app.include_router(composio_router)     # /composio/*

@app.on_event("startup")
async def _start_watchers():
    # 从 DB 读出已完成 Gmail/Calendar 授权的用户列表（初版可以为空）
    uids: List[str] = users_with_connected_accounts()
    if uids:
        await start_watchers(uids)

```

### config.py

```python
import os
from typing import Optional
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    # Fish Audio
    FISH_API_KEY: str
    FISH_MODEL: str = "s1"
    FISH_VOICE_REFERENCE_ID: Optional[str] = None

    # Letta
    LETTA_API_KEY: Optional[str] = None
    LETTA_BASE_URL: Optional[str] = None
    LETTA_AGENT_ID: Optional[str] = None

    # Composio (Gmail)
    COMPOSIO_API_KEY: Optional[str] = None
    COMPOSIO_GMAIL_AUTH_CONFIG: Optional[str] = None  
    COMPOSIO_CANVAS_AUTH_CONFIG: Optional[str] = None 
    COMPOSIO_GOOGLECAL_AUTH_CONFIG: Optional[str] = None 
    COMPOSIO_GOOGLEDRIVE_AUTH_CONFIG: Optional[str] = None

    # Product logic
    FOLLOWUP_DELAY_SEC: int = int(os.getenv("FOLLOWUP_DELAY_SEC", "600"))
    DEFAULT_FOCUS_MIN: int = int(os.getenv("DEFAULT_FOCUS_MIN", "25"))
    DEFAULT_BREAK_MIN: int = int(os.getenv("DEFAULT_BREAK_MIN", "5"))

    class Config:
        env_file = ".env"


settings = Settings()

```

[95 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]