commit 202e51c8a18398ef7cf3ab813ac2effb9925941a Author: Hardik Sankhla Date: Fri Jun 27 05:56:40 2025 +0530 Initial commits diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..b88eab2 --- /dev/null +++ b/backend/.env @@ -0,0 +1,48 @@ +# D:\GNPL-Private\TimeCraft\backend\.env + +# .env - Environment variables for the FastAPI Backend + +# --- Frontend URL for CORS configuration --- +# Set this to the URL where your React frontend is hosted. +# For local development, it's typically http://localhost:3000. +FRONTEND_URL=http://localhost:3000 + +# --- LLM Provider Configuration --- +# Choose which Large Language Model provider to use. +# Options: +# - GENERIC_API: For any LLM accessible via a standard HTTP API (e.g., OpenAI, Google Gemini, custom APIs) +# - HUGGINGFACE: For models loaded locally using the 'transformers' library +# - OLLAMA: For local Ollama server deployments +LLM_PROVIDER=OLLAMA + +# --- Settings for GENERIC_API Provider (currently commented out as LLM_PROVIDER is OLLAMA) --- +# These variables are required if LLM_PROVIDER is set to GENERIC_API. +# LLM_API_KEY: Your API key for the chosen generic LLM service. +# LLM_API_URL: The API endpoint URL for the chosen generic LLM service. +# Examples: +# LLM_API_KEY=sk-your_openai_api_key_here +# LLM_API_URL=https://api.openai.com/v1/chat/completions + +# For Google Gemini API (if using with a generic setup, though often has specific client libraries) +# LLM_API_KEY=your_google_gemini_api_key_here +# LLM_API_URL=https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent + +# LLM_API_KEY=your_generic_llm_api_key_here +# LLM_API_URL=https://api.openai.com/v1/chat/completions + +# --- Settings for HUGGINGFACE Provider (currently commented out as LLM_PROVIDER is OLLAMA) --- +# These variables are required if LLM_PROVIDER is set to HUGGINGFACE. +# HF_MODEL_NAME: The model identifier from Hugging Face Hub (e.g., google/gemma-2b-it, meta-llama/Llama-2-7b-chat-hf). +# Ensure you have sufficient RAM/VRAM for the chosen model. +# HUGGING_FACE_HUB_TOKEN: Your Hugging Face access token, often required for private models or higher rate limits. +# Uncomment and set this if necessary. +# HF_MODEL_NAME=google/gemma-2b-it +# HUGGING_FACE_HUB_TOKEN=hf_your_hugging_face_token_here + +# --- Settings for OLLAMA Provider (currently active as LLM_PROVIDER is OLLAMA) --- +# These variables are required if LLM_PROVIDER is set to OLLAMA. +# OLLAMA_MODEL_NAME: The name of the model downloaded in your local Ollama instance (e.g., llama2, mistral). +# OLLAMA_API_URL: The URL of your local Ollama API. Default is typically http://localhost:11434/api/generate. +# NOTE: Use "ollama" as the hostname inside Docker Compose, not "localhost" +OLLAMA_MODEL_NAME=qwen3:4b # CHANGED: Model updated to deepseek-r1:1.5b +OLLAMA_API_URL=http://ollama:11434/api/generate diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..94a2eab --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,25 @@ +# D:\GNPL-Private\TimeCraft\backend\Dockerfile + +# Use a slim, official Python image to reduce overall image size +FROM python:3.10-slim-buster + +# Set the working directory inside the container +WORKDIR /app + +# Copy the requirements file into the container +COPY requirements.txt . + +# Install dependencies without caching to keep the image lightweight +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the application source code into the container +COPY ./app /app/app +COPY main.py /app/main.py + +# Expose the port Uvicorn will use to serve FastAPI +EXPOSE 8000 + +# Define the container startup command to run the FastAPI app via Uvicorn +# - main:app -> refers to 'app' inside main.py +# - 0.0.0.0 -> binds to all interfaces so it's accessible outside container +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..2ca1b28 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,177 @@ +# 🧠 AI-Powered Schedule and Task Planning Backend + +This repository contains the backend service for an intelligent schedule and task planning application. It leverages a **Large Language Model (LLM)** (currently using **Qwen1.5–4B via Ollama**) to transform natural language tasks with complex recurrence rules into structured, conflict-free daily schedules. + +--- + +## ✨ Features + +- **Intelligent Scheduling**: Uses a powerful LLM to understand and structure time-bound tasks. +- **Flexible Task Input**: Supports daily, weekly, monthly recurrence with specific time rules. +- **Advanced Recurrence Handling**: Supports β€œevery 2nd Saturday” or β€œlast Monday of month”. +- **Structured JSON Output**: Returns clean machine-readable schedules. +- **Pluggable LLM Backend**: Switch between Ollama, HuggingFace, or any generic API. +- **FastAPI + Swagger UI**: For live API testing and documentation. +- **Dockerized**: Easily run the entire system with Docker Compose. + +--- + +## πŸš€ Technologies Used + +- **FastAPI** – high-performance API framework +- **Pydantic** – data validation & schema +- **HTTPLX** – async HTTP client for LLM +- **Ollama** – running Qwen1.5–4B locally +- **Docker + Compose** – containerization +- **Python 3.10+** + +--- + +## πŸ“‚ Project Structure + +```bash +backend/ +β”œβ”€β”€ main.py # FastAPI entry point +β”œβ”€β”€ requirements.txt # Python dependencies +β”œβ”€β”€ Dockerfile # Docker image config +β”œβ”€β”€ docker-compose.yml # Docker orchestration +β”œβ”€β”€ .env # Runtime config +└── app/ + β”œβ”€β”€ models/ + β”‚ └── task.py # Task schemas + β”œβ”€β”€ api/ + β”‚ └── endpoints.py # FastAPI endpoints + └── services/ + └── llm_service.py # LLM interaction layer +``` + +--- + +## πŸ”§ Configuration + +`.env` example: + +```env +FRONTEND_URL=http://localhost:3000 +LLM_PROVIDER=OLLAMA +OLLAMA_MODEL_NAME=qwen:4b +OLLAMA_API_URL=http://localhost:11434/api/generate +``` + +--- + +## πŸ“… Weekly Task Schedule Visualization (Sample Week: July 14–20, 2025) + +| Time | Mon (14) | Tue (15) | Wed (16) | Thu (17) | Fri (18) | Sat (19) | Sun (20) | +|--------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------| +| 06:00–07:00 | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | +| 07:00–08:00 | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | +| 08:30–09:00 | β˜• Coffee Break | β˜• Coffee Break | β˜• Coffee Break | β˜• Coffee Break | β˜• Coffee Break | | | +| 09:00–09:15 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | | | +| 09:15–10:00 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | | | +| 10:00–11:00 | 🟒 Flex / Board Review | πŸ”’ πŸ§‘β€πŸ’Ό Exec Meeting | 🟒 Financial Reporting | πŸ”’ πŸ§‘β€πŸ’Ό Deep Code Review | 🟒 🎯 Team Activity | | | +| 11:00–12:00 | 🟒 Report Prep ⭐ | 🟒 Report Prep ⭐ | 🟒 Code Review / Reporting | πŸ”’ Code Finalization πŸ§‘β€πŸ’Ό | 🟒 🎯 Team Activity Cont. | πŸ”’ 🧘 HR Audit (2nd Sat) | | +| 12:00–13:00 | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | +| 13:00–14:00 | ⭐ Board Meeting Prep | ⭐ Board Meeting Prep | ⭐ Board Meeting Prep | 🟒 Buffer | 🟒 Buffer | 🟒 Flex Task | 🟒 Flex Task | +| 14:00–17:00 | 🟒 Deep Work Block | 🟒 Deep Work | 🟒 Deep Work | 🟒 Deep Work | 🟒 Deep Work | 🟒 Flex Time | 🟒 Flex Time | +| 18:00–19:00 | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | +| 21:00–22:00 | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | + +**Legend**: +πŸ”’ Fixed β€’ ⚠️ Conflict β€’ ⭐ Preferred β€’ 🟒 Flexible β€’ 🧘 Personal β€’ πŸ§‘β€πŸ’Ό Work β€’ 🎯 High Impact + +--- + +## πŸ“¬ API Endpoint: `/api/v1/schedule/tasks` + +**POST Request** with list of tasks β†’ returns intelligent JSON schedule. + +Example input: + +```json +[ + { + "type": "daily", + "task_name": "Daily Stand-up", + "task_description": "Critical morning sync.", + "time_settings": { + "start_time": "09:00", + "end_time": "09:15" + }, + "category": "work", + "repeat_on_1": "Every weekday", + "repeat_on_2": ["M", "T", "W", "T", "F"], + "reminder_on": { + "is_active": true, + "numeric_input": 10, + "time_unit": "Minutes" + } + } +] +``` + +Example output: + +```json +{ + "scheduled_tasks": [ + { + "task_name": "Daily Stand-up", + "date": "2025-07-14", + "start_time": "09:00", + "end_time": "09:15", + "category": "work", + "original_task_id": "task1" + } + ] +} +``` + +--- + +## πŸ§ͺ Testing + +### Swagger UI +Visit: `http://localhost:8000/docs` β†’ Try `/api/v1/schedule/tasks` + +### Curl +```bash +curl -X POST http://localhost:8000/api/v1/schedule/tasks \ +-H "Content-Type: application/json" \ +-d '[{"type":"daily", "task_name":"Daily Stand-up", "task_description":"Sync","time_settings":{"start_time":"09:00","end_time":"09:15"},"category":"work","repeat_on_1":"Every weekday","repeat_on_2":["M","T","W","T","F"],"reminder_on":{"is_active":true,"numeric_input":10,"time_unit":"Minutes"}}]' +``` + +--- + +## βš™οΈ LLM Configuration + +**Current Provider**: `OLLAMA` +**Model Used**: `qwen:4b` +**API URL**: `http://localhost:11434/api/generate` + +Switch to `HUGGINGFACE` or `GENERIC_API` by editing `.env`. + +--- + +## 🧠 Troubleshooting + +- **422 Unprocessable Entity**: Add fallback logic in `llm_service.py` for null times. +- **500 Server Error**: LLM may not be running or wrong model name. +- **Fix Conflicts**: Use better prompts or larger models for conflict resolution. + +--- + +## πŸ’‘ Future Enhancements + +- Conflict resolver + dependency chains +- User sessions & database storage +- Calendar frontend (React) +- Notification/reminders +- Team role scheduling + +--- + +## πŸ“„ License + +MIT License – See `LICENSE` file. + diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..f2aa76f Binary files /dev/null and b/backend/__pycache__/main.cpython-313.pyc differ diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/__pycache__/__init__.cpython-313.pyc b/backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..4dc3530 Binary files /dev/null and b/backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/__pycache__/__init__.cpython-313.pyc b/backend/app/api/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..b8052e2 Binary files /dev/null and b/backend/app/api/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/api/__pycache__/endpoints.cpython-313.pyc b/backend/app/api/__pycache__/endpoints.cpython-313.pyc new file mode 100644 index 0000000..44220df Binary files /dev/null and b/backend/app/api/__pycache__/endpoints.cpython-313.pyc differ diff --git a/backend/app/api/endpoints.py b/backend/app/api/endpoints.py new file mode 100644 index 0000000..3b4bf46 --- /dev/null +++ b/backend/app/api/endpoints.py @@ -0,0 +1,65 @@ +# D:\GNPL-Private\TimeCraft\backend\app\api\endpoints.py + +from fastapi import APIRouter, HTTPException, status +from typing import List + +from app.models.task import TaskCreateRequest, ScheduleResponse +from app.services.llm_service import get_scheduled_tasks + +# Initialize the API router +router = APIRouter() + +@router.post( + "/schedule/tasks", + response_model=ScheduleResponse, # Define the response model for automatic serialization/validation + status_code=status.HTTP_200_OK, # Set the default successful status code + summary="Create and Schedule Tasks using LLM", + description="Receives a list of user-defined tasks, sends them to an LLM for intelligent scheduling, and returns a structured schedule." +) +async def create_and_schedule_tasks(tasks: List[TaskCreateRequest]): + """ + Handles the POST request to create and schedule tasks. + + Args: + tasks (List[TaskCreateRequest]): A list of task objects received from the frontend. + + Returns: + ScheduleResponse: A structured response containing the scheduled tasks. + + Raises: + HTTPException: If no tasks are provided, or if there's an error during LLM interaction + or internal processing. + """ + # Validate if any tasks were provided in the request body + if not tasks: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="No tasks provided for scheduling. Please submit at least one task." + ) + + try: + # Call the LLM service to get the scheduled tasks + scheduled_response = await get_scheduled_tasks(tasks) + return scheduled_response + except RuntimeError as e: + # Catch errors specifically from LLM interaction issues (e.g., network, API issues) + print(f"LLM interaction failed: {e}") # Log the error for debugging + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to schedule tasks due to an issue with the LLM service. Please try again later. Details: {e}" + ) + except ValueError as e: + # Catch errors related to invalid data or parsing issues from the LLM's response + print(f"Data validation or parsing error: {e}") # Log the error + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, # 422 for semantically erroneous request syntax + detail=f"The LLM returned an invalid schedule structure or there was an internal data processing error. Details: {e}" + ) + except Exception as e: + # Catch any other unexpected errors + print(f"An unexpected error occurred in endpoint: {e}") # Log the error + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"An unexpected error occurred while processing your request. Please contact support. Details: {e}" + ) + diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/__pycache__/__init__.cpython-313.pyc b/backend/app/models/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..4c36374 Binary files /dev/null and b/backend/app/models/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/models/__pycache__/task.cpython-313.pyc b/backend/app/models/__pycache__/task.cpython-313.pyc new file mode 100644 index 0000000..c5bea91 Binary files /dev/null and b/backend/app/models/__pycache__/task.cpython-313.pyc differ diff --git a/backend/app/models/task.py b/backend/app/models/task.py new file mode 100644 index 0000000..666f96b --- /dev/null +++ b/backend/app/models/task.py @@ -0,0 +1,69 @@ +# D:\GNPL-Private\TimeCraft\backend\app\models\task.py + +from datetime import time +from enum import Enum +from typing import List, Optional, Union +from pydantic import BaseModel, Field, conint, conlist + +# Type aliases for Pydantic constrained types for better readability +PositiveInt = conint(gt=0) +WeekdayList = conlist(str, min_length=0, max_length=7) + +# Enum for primary recurrence periods +class RecurrenceType(str, Enum): + daily = "daily" + weekly = "weekly" + monthly = "monthly" + +# Enum for task categories +class TaskCategory(str, Enum): + personal = "personal" + work = "work" + errands = "errands" + extra = "extra" + +# Enum for time units used in reminders or durations +class TimeUnit(str, Enum): + minutes = "Minutes" + hours = "Hours" + days = "Days" + +# Pydantic model for time settings of a task +class TimeSettings(BaseModel): + start_time: Optional[time] = Field(None, description="Specific start time (HH:MM)") + end_time: Optional[time] = Field(None, description="Specific end time (HH:MM)") + duration: Optional[str] = Field(None, description="Duration of the task (e.g., '1 Hour', '30 Minutes'). Used if start_time and end_time are empty or to validate them.") + +# Pydantic model for reminder settings +class ReminderOn(BaseModel): + is_active: bool = Field(False, description="Whether a reminder is set (true/false)") + numeric_input: Optional[PositiveInt] = Field(None, description="Numeric value for reminder (e.g., 10 minutes before)") + time_unit: Optional[TimeUnit] = Field(None, description="Unit for reminder time (Minutes, Hours, Days)") + +# Main Pydantic model for the task creation request from the frontend +class TaskCreateRequest(BaseModel): + type: RecurrenceType = Field(..., description="Defines the primary recurrence period (daily, weekly, monthly)") + task_name: str = Field(..., min_length=1, description="A descriptive name for the task") + task_description: Optional[str] = Field(None, description="Detailed notes or description of the task") + time_settings: TimeSettings = Field(..., description="Time settings for the task, including start, end, and duration") + category: TaskCategory = Field(..., description="Categorizes the task (personal, work, errands, extra)") + repeat_on_1: Optional[str] = Field(None, description="CRITICAL for complex recurrence patterns. Free-form text field for custom recurrence rules (e.g., 'Every second Monday')") + repeat_on_2: Optional[WeekdayList] = Field( + None, + description="Checkbox selector for days of the week (S, M, T, W, T, F, S). Users can select multiple days." + ) + reminder_on: ReminderOn = Field(..., description="Reminder settings for the task") + +# Pydantic model for a single scheduled task returned by the LLM +class ScheduledTask(BaseModel): + task_name: str = Field(..., description="Name of the scheduled task") + date: str = Field(..., description="Scheduled date in ISO-MM-DD format") + start_time: Optional[str] = Field(None, description="Scheduled start time in HH:MM format") + end_time: Optional[str] = Field(None, description="Scheduled end time in HH:MM format") + category: TaskCategory = Field(..., description="Category of the scheduled task") + original_task_id: Optional[str] = Field(None, description="Optional ID to link back to the original task request, useful for updates/tracking") + # reminder_on: Optional[ReminderOn] = Field(None, description="Reminder settings for the scheduled task") # Uncomment if LLM output includes this + +# Pydantic model for the overall schedule response from the LLM +class ScheduleResponse(BaseModel): + scheduled_tasks: List[ScheduledTask] = Field(..., description="List of intelligently assigned scheduled tasks") diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/__pycache__/__init__.cpython-313.pyc b/backend/app/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..ce3bc30 Binary files /dev/null and b/backend/app/services/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/services/__pycache__/llm_service.cpython-313.pyc b/backend/app/services/__pycache__/llm_service.cpython-313.pyc new file mode 100644 index 0000000..5c8a72d Binary files /dev/null and b/backend/app/services/__pycache__/llm_service.cpython-313.pyc differ diff --git a/backend/app/services/llm_service.py b/backend/app/services/llm_service.py new file mode 100644 index 0000000..680e76d --- /dev/null +++ b/backend/app/services/llm_service.py @@ -0,0 +1,153 @@ +# D:\GNPL-Private\TimeCraft\backend\app\services\llm_service.py + +import os +import json +import httpx +from typing import List +from datetime import date +from dotenv import load_dotenv +from app.models.task import TaskCreateRequest, ScheduleResponse + +load_dotenv() + +LLM_PROVIDER = os.getenv("LLM_PROVIDER", "OLLAMA") +LLM_API_KEY = os.getenv("LLM_API_KEY") +LLM_API_URL = os.getenv("LLM_API_URL") +HF_MODEL_NAME = os.getenv("HF_MODEL_NAME", "google/gemma-2b-it") +OLLAMA_MODEL_NAME = os.getenv("OLLAMA_MODEL_NAME", "deepseek-r1:1.5b") +OLLAMA_API_URL = os.getenv("OLLAMA_API_URL", "http://localhost:11434/api/generate") + +tokenizer = None +model = None + +if LLM_PROVIDER == "HUGGINGFACE": + try: + from transformers import AutoTokenizer, AutoModelForCausalLM + import torch + tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_NAME) + model = AutoModelForCausalLM.from_pretrained(HF_MODEL_NAME, torch_dtype=torch.float16) + if torch.cuda.is_available(): + model.to('cuda') + print(f"[LLM] Hugging Face model '{HF_MODEL_NAME}' loaded.") + except Exception as e: + print(f"[ERROR] Failed to load Hugging Face model: {e}") + raise RuntimeError("Hugging Face model loading failed.") + +def infer_priority(task: TaskCreateRequest) -> str: + desc = task.task_description.lower() + if any(k in desc for k in ["non-negotiable", "critical", "mandatory", "must", "cannot be moved", "very high priority"]): + return "high" + if any(k in desc for k in ["optional", "nice to have", "if possible", "fun"]): + return "low" + return "medium" + +async def get_scheduled_tasks(tasks: List[TaskCreateRequest]) -> ScheduleResponse: + today_str = date.today().isoformat() + prompt_tasks_data = [ + { + **json.loads(task.model_dump_json()), + "original_task_id": f"task{index + 1}", + "priority": infer_priority(task) + } + for index, task in enumerate(tasks) + ] + + system_message = ( + f"You are an AI-powered schedule and task planning assistant. Your job is to create a conflict-free, " + f"intelligent 30-day schedule from today ({today_str}).\n\n" + "Parse task details precisely: type, time_settings (start_time, end_time, duration), category, and priority.\n" + "Avoid time conflicts. If two fixed-time tasks clash, prioritize the 'high' priority one. Try to shift " + "'medium' priority tasks to nearby times. Skip low priority if no space.\n\n" + "Respect fixed times first. For flexible tasks, use hints.\n" + "Return strictly valid JSON:\n" + "{\n" + " \"scheduled_tasks\": [\n" + " {\n" + " \"task_name\": \"string\",\n" + " \"date\": \"YYYY-MM-DD\",\n" + " \"start_time\": \"HH:MM\",\n" + " \"end_time\": \"HH:MM\",\n" + " \"category\": \"personal | work | errands | extra\",\n" + " \"original_task_id\": \"string\"\n" + " }\n" + " ]\n" + "}" + ) + + user_message = f"Here are the tasks to schedule:\n{json.dumps(prompt_tasks_data, indent=2)}" + full_prompt = f"{system_message}\n\n{user_message}" + llm_output_content = "" + + try: + if LLM_PROVIDER == "GENERIC_API": + if not LLM_API_URL or not LLM_API_KEY: + raise ValueError("Missing LLM_API_URL or LLM_API_KEY in .env") + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {LLM_API_KEY}" + } + + payload = { + "model": "your-preferred-llm-model-name", + "messages": [ + {"role": "system", "content": system_message}, + {"role": "user", "content": user_message} + ], + "response_format": {"type": "json_object"} + } + + async with httpx.AsyncClient(timeout=httpx.Timeout(600.0, connect=60.0)) as client: + response = await client.post(LLM_API_URL, headers=headers, json=payload) + response.raise_for_status() + llm_output_content = response.json().get("choices", [{}])[0].get("message", {}).get("content", "") + + elif LLM_PROVIDER == "HUGGINGFACE": + import torch + input_ids = tokenizer(full_prompt, return_tensors="pt").input_ids + if torch.cuda.is_available(): + input_ids = input_ids.to("cuda") + outputs = model.generate(input_ids, max_new_tokens=1024, pad_token_id=tokenizer.eos_token_id) + llm_output_content = tokenizer.decode(outputs[0], skip_special_tokens=True) + + elif LLM_PROVIDER == "OLLAMA": + payload = { + "model": OLLAMA_MODEL_NAME, + "prompt": full_prompt, + "stream": False, + "format": "json" + } + + async with httpx.AsyncClient(timeout=httpx.Timeout(600.0, connect=60.0)) as client: + print("[LLM] Sending prompt to Ollama...") + response = await client.post(OLLAMA_API_URL, json=payload) + response.raise_for_status() + llm_output_content = response.json().get("response", "") + + print("[LLM] Received response (first 500 chars):") + print(llm_output_content[:500]) + + else: + raise ValueError(f"Unsupported LLM_PROVIDER: {LLM_PROVIDER}") + + json_start = llm_output_content.find('{') + json_end = llm_output_content.rfind('}') + if json_start == -1 or json_end == -1: + raise ValueError("No valid JSON object found in LLM output.") + json_string = llm_output_content[json_start:json_end + 1] + parsed_data = json.loads(json_string) + return ScheduleResponse(**parsed_data) + + except httpx.HTTPStatusError as e: + print(f"[ERROR] Ollama returned HTTP {e.response.status_code}: {e.response.text}") + raise RuntimeError(f"LLM API returned error {e.response.status_code}: {e.response.text}") + except httpx.RequestError as e: + print(f"[ERROR] Request to LLM service failed: {e}") + raise RuntimeError(f"Failed to connect to LLM service. Details: {e}") + except json.JSONDecodeError as e: + print(f"[ERROR] JSON decoding failed: {e}") + print(f"[DEBUG] Raw output: {llm_output_content}") + raise ValueError("LLM did not return valid JSON.") + except Exception as e: + print(f"[ERROR] Unexpected exception: {e}") + raise RuntimeError(f"Failed to schedule tasks due to an issue with the LLM service. Details: {e}") diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..c7d49c3 --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,40 @@ +services: + backend: + build: + context: . + ports: + - "8000:8000" + volumes: + - ./.env:/app/.env + restart: unless-stopped + depends_on: + ollama: + condition: service_healthy + + ollama: + image: ollama/ollama + ports: + - "11434:11434" + volumes: + - ollama_data:/root/.ollama + container_name: ollama + restart: unless-stopped + + # Override default entrypoint to allow shell command chaining + entrypoint: ["/bin/sh", "-c"] + command: > + "ollama serve & + sleep 5 && + until ollama list > /dev/null 2>&1; do sleep 3; done && + ollama pull qwen3:4b && + wait" + + healthcheck: + test: ["CMD", "ollama", "list"] + interval: 15s + timeout: 10s + retries: 6 + start_period: 120s + +volumes: + ollama_data: diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..3918c44 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,55 @@ +# D:\GNPL-Private\TimeCraft\backend\main.py + +import os +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from dotenv import load_dotenv # For loading environment variables + +from app.api import endpoints # Import your API router + +# Load environment variables from the .env file. +# This should be called early in your application's lifecycle. +load_dotenv() + +# Initialize the FastAPI application instance +# Add additional metadata for professional API documentation +app = FastAPI( + title="AI-Powered Schedule and Task Planning Backend", + description="FastAPI backend for ingesting user tasks, using an LLM for intelligent scheduling, and returning structured schedules.", + version="1.0.0", + docs_url="/docs", # Enable interactive API documentation (Swagger UI) + redoc_url="/redoc", # Enable ReDoc documentation + openapi_url="/openapi.json" # Specify the OpenAPI schema URL +) + +# Configure CORS (Cross-Origin Resource Sharing) middleware. +# This is crucial for allowing your React frontend (running on a different port/domain) +# to make requests to your FastAPI backend. +origins = [ + os.getenv("FRONTEND_URL", "http://localhost:3000"), # Get frontend URL from .env, default to React's development port + # Add other origins if your frontend is deployed elsewhere or has multiple access points + # e.g., "https://your-production-frontend.com", + # "http://localhost:8080" # If testing with another local frontend setup +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, # List of allowed origins + allow_credentials=True, # Allow cookies and authentication headers to be sent + allow_methods=["*"], # Allow all HTTP methods (GET, POST, PUT, DELETE, etc.) + allow_headers=["*"], # Allow all headers in the request +) + +# Include the API router defined in app.api.endpoints. +# All routes defined in endpoints.py will be prefixed with "/api/v1". +app.include_router(endpoints.router, prefix="/api/v1") + +# Define a root endpoint for basic health check or welcome message +@app.get("/", summary="Root Endpoint", description="Provides a welcome message for the API.") +async def root(): + """ + Root endpoint for the API. + Returns a simple welcome message to indicate the API is running. + """ + return {"message": "Welcome to the AI-Powered Schedule and Task Planning API! Access docs at /docs"} + diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..b133673 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,9 @@ +# D:\GNPL-Private\TimeCraft\backend\requirements.txt + +fastapi +uvicorn[standard] +pydantic # Specify a compatible Pydantic version +python-dotenv +httpx +#transformers +#torch diff --git a/documentation/README.md b/documentation/README.md new file mode 100644 index 0000000..35008a4 --- /dev/null +++ b/documentation/README.md @@ -0,0 +1,275 @@ +# Workflow + +## Input: Json + +```json + +COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX +COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX +COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX + + +[ + { + "type": "daily", + "task_name": "Critical Daily Stand-up", + "task_description": "Non-negotiable daily sync for core project team. Early birds only.", + "time_settings": { + "start_time": "09:00", + "end_time": "09:15", + "duration": null + }, + "category": "work", + "repeat_on_1": "Every weekday, but skip public holidays (LLM to infer)", + "repeat_on_2": ["M", "T", "W", "T", "F"], + "reminder_on": { + "is_active": true, + "numeric_input": 5, + "time_unit": "Minutes" + } + }, + { + "type": "daily", + "task_name": "Daily Team Scrum", + "task_description": "New mandatory daily team scrum, might conflict with other meetings. Needs 1 hour.", + "time_settings": { + "start_time": "09:00", + "end_time": "10:00", + "duration": null + }, + "category": "work", + "repeat_on_1": "Every Monday to Friday. Very high priority.", + "repeat_on_2": ["M", "T", "W", "T", "F"], + "reminder_on": { + "is_active": true, + "numeric_input": 10, + "time_unit": "Minutes" + } + }, + { + "type": "weekly", + "task_name": "Executive Leadership Meeting", + "task_description": "Mandatory weekly meeting with executive board. Fixed slot.", + "time_settings": { + "start_time": "09:00", + "end_time": "10:00", + "duration": null + }, + "category": "work", + "repeat_on_1": "Every Tuesday, but starting from the 3rd week of the month only. Cannot be moved.", + "repeat_on_2": ["T"], + "reminder_on": { + "is_active": true, + "numeric_input": 15, + "time_unit": "Minutes" + } + }, + { + "type": "monthly", + "task_name": "Monthly HR Check-in", + "task_description": "Meet with HR for personal development. Fixed slot, 1 hour.", + "time_settings": { + "start_time": "11:00", + "end_time": "12:00", + "duration": null + }, + "category": "personal", + "repeat_on_1": "Every second Saturday of the month.", + "repeat_on_2": ["S"], + "reminder_on": { + "is_active": true, + "numeric_input": 1, + "time_unit": "Days" + } + }, + { + "type": "monthly", + "task_name": "Board Meeting Prep & Review", + "task_description": "Extensive preparation for monthly board meeting. Flexible, 8-hour task, needs continuous block if possible.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "8 Hours" + }, + "category": "work", + "repeat_on_1": "Any weekday in the first week of every month, as long as it's not Monday or Friday. Prefer early morning. Highly flexible on exact time within that day.", + "repeat_on_2": ["T", "W", "T"], + "reminder_on": { + "is_active": true, + "numeric_input": 2, + "time_unit": "Days" + } + }, + { + "type": "daily", + "task_name": "Personal Workout Session", + "task_description": "Daily exercise, needs 45 minutes, flexible around work hours. Prioritize morning but accept evening.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "45 Minutes" + }, + "category": "personal", + "repeat_on_1": "Daily, prefer 07:00-08:00 AM OR 18:00-19:00 PM, but can be done anytime between 6 AM and 10 PM. Prioritize morning.", + "repeat_on_2": ["S", "M", "T", "W", "T", "F", "S"], + "reminder_on": { + "is_active": true, + "numeric_input": 30, + "time_unit": "Minutes" + } + }, + { + "type": "weekly", + "task_name": "Team Building Activity", + "task_description": "Organize a fun team activity. Needs 2 hours, afternoon only.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "2 Hours" + }, + "category": "extra", + "repeat_on_1": "Last Friday of every second month (starting from this month), flexible between 2 PM and 5 PM.", + "repeat_on_2": ["F"], + "reminder_on": { + "is_active": true, + "numeric_input": 1, + "time_unit": "Days" + } + }, + { + "type": "monthly", + "task_name": "Complex Financial Report", + "task_description": "Prepare the complex financial report for audit. Requires 5 hours, ideally split over two consecutive days if necessary.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "5 Hours" + }, + "category": "work", + "repeat_on_1": "Any two consecutive weekdays in the third week of the month, prioritizing Tuesday-Wednesday or Wednesday-Thursday. Flexible 09:00-17:00.", + "repeat_on_2": ["M", "T", "W", "T", "F"], + "reminder_on": { + "is_active": true, + "numeric_input": 7, + "time_unit": "Days" + } + }, + { + "type": "daily", + "task_name": "Morning Coffee Break", + "task_description": "A short 15-minute break. Highly flexible, but must be before 11 AM, avoiding any fixed meetings.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "15 Minutes" + }, + "category": "personal", + "repeat_on_1": "Daily, any time between 09:15 and 11:00 AM, avoiding other fixed meetings.", + "repeat_on_2": ["M", "T", "W", "T", "F"], + "reminder_on": { + "is_active": false, + "numeric_input": null, + "time_unit": null + } + }, + { + "type": "weekly", + "task_name": "Deep Dive Code Review", + "task_description": "Dedicated slot for reviewing complex PRs. Needs 3 hours, strictly afternoon.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "3 Hours" + }, + "category": "work", + "repeat_on_1": "Every Thursday, after 11:00 AM, but before 4:00 PM. Cannot overlap with any other work meetings.", + "repeat_on_2": ["T"], + "reminder_on": { + "is_active": true, + "numeric_input": 4, + "time_unit": "Hours" + } + }, + { + "type": "monthly", + "task_name": "Personal Finance Audit", + "task_description": "Review personal budget and investments. 1 hour, weekday only.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "1 Hour" + }, + "category": "personal", + "repeat_on_1": "The 15th of every month, if it's a weekday. Otherwise, the closest preceding weekday.", + "repeat_on_2": ["M", "T", "W", "T", "F"], + "reminder_on": { + "is_active": true, + "numeric_input": 24, + "time_unit": "Hours" + } + }, + { + "type": "daily", + "task_name": "Evening Wind-Down", + "task_description": "Read a book or meditate. 30 minutes, before bed. No fixed time.", + "time_settings": { + "start_time": null, + "end_time": null, + "duration": "30 Minutes" + }, + "category": "personal", + "repeat_on_1": "Daily, between 9 PM and 11 PM. Avoid any fixed late-night calls.", + "repeat_on_2": ["S", "M", "T", "W", "T", "F", "S"], + "reminder_on": { + "is_active": false, + "numeric_input": null, + "time_unit": null + } + } +] + +``` + +# πŸ“… Weekly Task Schedule Visualization + +**Sample Week: July 14–20, 2025** + +| Time | Mon (14) | Tue (15) | Wed (16) | Thu (17) | Fri (18) | Sat (19) | Sun (20) | +|-------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------| +| 06:00–07:00 | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | 🟒 🧘 Workout (Flexible) | +| 07:00–08:00 | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | +| 08:30–09:00 | 🟒 β˜• Coffee Break | 🟒 β˜• Coffee Break | 🟒 β˜• Coffee Break | 🟒 β˜• Coffee Break | 🟒 β˜• Coffee Break | | | +| 09:00–09:15 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | πŸ”’ ⚠️ Stand-up + Scrum 🎯 | | | +| 09:15–10:00 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | | | +| 10:00–11:00 | 🟒 Board Prep / Overflow Slot | πŸ”’ πŸ§‘β€πŸ’Ό Exec Leadership Meeting | 🟒 Report Split (Day 2) | πŸ”’ πŸ§‘β€πŸ’Ό Deep Code Review | 🟒 🎯 Team Activity Slot | | | +| 11:00–12:00 | 🟒 Report Prep ⭐ | 🟒 Report Prep ⭐ | 🟒 Report or Code Review | πŸ”’ πŸ§‘β€πŸ’Ό Code Review Ends | 🟒 Team Activity Cont. | πŸ”’ 🧘 HR Check-in (2nd Sat) | | +| 12:00–13:00 | 🟒 🍽 Lunch / Overflow | 🟒 🍽 Lunch / Overflow | 🟒 🍽 Lunch / Overflow | 🟒 🍽 Lunch / Overflow | 🟒 🍽 Lunch / Overflow | 🟒 🍽 Lunch / Overflow | 🟒 🍽 Lunch / Overflow | +| 13:00–14:00 | 🟒 ⭐ Board Meeting Prep | 🟒 ⭐ Board Meeting Prep | 🟒 ⭐ Board Meeting Prep | 🟒 Board Prep / Flex | 🟒 Team Activity Overflow | 🟒 Free | 🟒 Free | +| 14:00–17:00 | 🟒 Deep Work / Report Block | 🟒 Deep Work | 🟒 Deep Work | 🟒 Deep Work | 🟒 Deep Work | 🟒 Flex Time | 🟒 Flex Time | +| 18:00–19:00 | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | +| 21:00–22:00 | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | + +--- + +### πŸ”΅ Legend +- πŸ”’ = Fixed time slot +- ⚠️ = Conflict or overlap +- ⭐ = Preferred or high-priority +- 🧘 = Personal task +- πŸ§‘β€πŸ’Ό = Work task +- 🎯 = High-impact / urgent +- 🟒 = Flexible slot +- β˜• = Break +- 🍽 = Lunch + +--- + +### 🧠 Notes & Insights +- **09:00 Conflict:** Critical Daily Stand-up and Team Scrum overlap every weekday β†’ consider merging or shifting. +- **Board Prep & Financial Reports** are scheduled with max flexibility to avoid blocking. +- **Workouts** are morning-preferred with evening fallback for sustainability. +- **Personal wellness tasks** are spread across early morning and late evening with no overlaps. + +--- + +Want this in `.csv`, `.ics`, or as a visual HTML calendar? Just let me know.