Spaces:
Configuration error
Configuration error
""" | |
Personal Task Manager Agent for Hugging Face AI Agent Course Final Project | |
This agent helps users manage tasks, set priorities, and organize information | |
through natural language understanding and simple task management functionality. | |
""" | |
import json | |
import re | |
import datetime | |
from typing import List, Dict, Any, Optional, Tuple | |
class Task: | |
"""Represents a single task with properties like description, priority, due date, etc.""" | |
def __init__(self, description: str, priority: str = "medium", | |
due_date: Optional[str] = None, category: str = "general"): | |
self.description = description | |
self.priority = priority.lower() # "high", "medium", or "low" | |
self.due_date = due_date # Format: YYYY-MM-DD | |
self.category = category.lower() | |
self.completed = False | |
self.creation_date = datetime.datetime.now().strftime("%Y-%m-%d") | |
def to_dict(self) -> Dict[str, Any]: | |
"""Convert task to dictionary for JSON serialization""" | |
return { | |
"description": self.description, | |
"priority": self.priority, | |
"due_date": self.due_date, | |
"category": self.category, | |
"completed": self.completed, | |
"creation_date": self.creation_date | |
} | |
def from_dict(cls, data: Dict[str, Any]) -> 'Task': | |
"""Create a Task object from a dictionary""" | |
task = cls( | |
description=data["description"], | |
priority=data["priority"], | |
due_date=data["due_date"], | |
category=data["category"] | |
) | |
task.completed = data["completed"] | |
task.creation_date = data["creation_date"] | |
return task | |
def __str__(self) -> str: | |
"""String representation of the task""" | |
status = "✓" if self.completed else "□" | |
due_str = f" (Due: {self.due_date})" if self.due_date else "" | |
return f"{status} [{self.priority.upper()}] {self.description}{due_str} - {self.category}" | |
class TaskManager: | |
"""Manages a collection of tasks with functionality to add, update, and query tasks""" | |
def __init__(self): | |
self.tasks: List[Task] = [] | |
def add_task(self, task: Task) -> int: | |
"""Add a new task and return its index""" | |
self.tasks.append(task) | |
return len(self.tasks) - 1 | |
def update_task(self, index: int, **kwargs) -> bool: | |
"""Update task properties at the given index""" | |
if 0 <= index < len(self.tasks): | |
task = self.tasks[index] | |
for key, value in kwargs.items(): | |
if hasattr(task, key): | |
setattr(task, key, value) | |
return True | |
return False | |
def complete_task(self, index: int) -> bool: | |
"""Mark a task as completed""" | |
return self.update_task(index, completed=True) | |
def delete_task(self, index: int) -> bool: | |
"""Delete a task at the given index""" | |
if 0 <= index < len(self.tasks): | |
self.tasks.pop(index) | |
return True | |
return False | |
def get_tasks(self, | |
category: Optional[str] = None, | |
priority: Optional[str] = None, | |
completed: Optional[bool] = None) -> List[Tuple[int, Task]]: | |
"""Get tasks with optional filtering, returns list of (index, task) tuples""" | |
result = [] | |
for i, task in enumerate(self.tasks): | |
if (category is None or task.category == category.lower()) and \ | |
(priority is None or task.priority == priority.lower()) and \ | |
(completed is None or task.completed == completed): | |
result.append((i, task)) | |
return result | |
def get_all_tasks(self) -> List[Tuple[int, Task]]: | |
"""Get all tasks with their indices""" | |
return [(i, task) for i, task in enumerate(self.tasks)] | |
def get_categories(self) -> List[str]: | |
"""Get list of unique categories""" | |
return list(set(task.category for task in self.tasks)) | |
def save_to_file(self, filename: str) -> bool: | |
"""Save tasks to a JSON file""" | |
try: | |
with open(filename, 'w') as f: | |
json.dump([task.to_dict() for task in self.tasks], f, indent=2) | |
return True | |
except Exception as e: | |
print(f"Error saving tasks: {e}") | |
return False | |
def load_from_file(self, filename: str) -> bool: | |
"""Load tasks from a JSON file""" | |
try: | |
with open(filename, 'r') as f: | |
data = json.load(f) | |
self.tasks = [Task.from_dict(item) for item in data] | |
return True | |
except Exception as e: | |
print(f"Error loading tasks: {e}") | |
return False | |
class TaskManagerAgent: | |
""" | |
AI agent that understands natural language requests related to task management | |
and performs appropriate actions using the TaskManager. | |
""" | |
def __init__(self): | |
self.task_manager = TaskManager() | |
# Common patterns for understanding user intent | |
self.patterns = { | |
"add_task": [ | |
r"add (?:a )?(?:new )?task(?: to)?(?: do)?(?: called)?(?: to)?(?: about)? (.+)", | |
r"create (?:a )?(?:new )?task(?: to)?(?: do)?(?: called)?(?: to)?(?: about)? (.+)", | |
r"remind me to (.+)", | |
], | |
"list_tasks": [ | |
r"(?:show|list|display|get)(?: all)?(?: my)? tasks", | |
r"what (?:are my|do i have for) tasks", | |
r"show me what i need to do", | |
], | |
"complete_task": [ | |
r"(?:mark|set) task (?:number )?(\d+) (?:as )?(?:done|complete|finished)", | |
r"complete task (?:number )?(\d+)", | |
r"i (?:have )?(?:done|completed|finished) task (?:number )?(\d+)", | |
], | |
"delete_task": [ | |
r"(?:delete|remove) task (?:number )?(\d+)", | |
r"get rid of task (?:number )?(\d+)", | |
], | |
"filter_tasks": [ | |
r"(?:show|list|display|get) (?:all )?(\w+) tasks", | |
r"(?:show|list|display|get) tasks (?:with|that are) (\w+)", | |
], | |
"help": [ | |
r"(?:help|assist|guide) me", | |
r"what can you do", | |
r"how do (?:you|i|this) work", | |
] | |
} | |
def _extract_task_details(self, description: str) -> Dict[str, Any]: | |
"""Extract task details like priority, due date, and category from description""" | |
details = {"description": description} | |
# Extract priority | |
priority_match = re.search(r"(?:priority|important|urgency):?\s*(high|medium|low)", description, re.I) | |
if priority_match: | |
details["priority"] = priority_match.group(1).lower() | |
# Remove the priority text from description | |
details["description"] = re.sub(r"(?:priority|important|urgency):?\s*(high|medium|low)", "", details["description"], flags=re.I).strip() | |
# Extract due date (simple format: YYYY-MM-DD or MM/DD/YYYY) | |
date_match = re.search(r"(?:due|by|on):?\s*(\d{4}-\d{2}-\d{2}|\d{1,2}/\d{1,2}/\d{4})", description, re.I) | |
if date_match: | |
date_str = date_match.group(1) | |
# Convert MM/DD/YYYY to YYYY-MM-DD if needed | |
if "/" in date_str: | |
month, day, year = date_str.split("/") | |
date_str = f"{year}-{month.zfill(2)}-{day.zfill(2)}" | |
details["due_date"] = date_str | |
# Remove the date text from description | |
details["description"] = re.sub(r"(?:due|by|on):?\s*(\d{4}-\d{2}-\d{2}|\d{1,2}/\d{1,2}/\d{4})", "", details["description"], flags=re.I).strip() | |
# Extract category | |
category_match = re.search(r"(?:category|tag|type):?\s*(\w+)", description, re.I) | |
if category_match: | |
details["category"] = category_match.group(1).lower() | |
# Remove the category text from description | |
details["description"] = re.sub(r"(?:category|tag|type):?\s*(\w+)", "", details["description"], flags=re.I).strip() | |
return details | |
def process_query(self, query: str) -> str: | |
""" | |
Process a natural language query and perform the appropriate task management action. | |
Returns a response string. | |
""" | |
# Check for intent matches | |
for intent, patterns in self.patterns.items(): | |
for pattern in patterns: | |
match = re.search(pattern, query, re.I) | |
if match: | |
# Call the appropriate method based on intent | |
if intent == "add_task" and match.group(1): | |
return self._handle_add_task(match.group(1)) | |
elif intent == "list_tasks": | |
return self._handle_list_tasks() | |
elif intent == "complete_task" and match.group(1): | |
return self._handle_complete_task(int(match.group(1))) | |
elif intent == "delete_task" and match.group(1): | |
return self._handle_delete_task(int(match.group(1))) | |
elif intent == "filter_tasks" and match.group(1): | |
return self._handle_filter_tasks(match.group(1)) | |
elif intent == "help": | |
return self._handle_help() | |
# If no pattern matches, try to understand as a general query | |
return self._handle_general_query(query) | |
def _handle_add_task(self, description: str) -> str: | |
"""Handle adding a new task""" | |
details = self._extract_task_details(description) | |
task = Task( | |
description=details["description"], | |
priority=details.get("priority", "medium"), | |
due_date=details.get("due_date"), | |
category=details.get("category", "general") | |
) | |
index = self.task_manager.add_task(task) | |
return f"Added task {index}: {task}" | |
def _handle_list_tasks(self) -> str: | |
"""Handle listing all tasks""" | |
tasks = self.task_manager.get_all_tasks() | |
if not tasks: | |
return "You don't have any tasks yet." | |
result = "Here are your tasks:\n" | |
for index, task in tasks: | |
result += f"{index}: {task}\n" | |
return result | |
def _handle_complete_task(self, index: int) -> str: | |
"""Handle marking a task as complete""" | |
if self.task_manager.complete_task(index): | |
return f"Marked task {index} as completed." | |
return f"Task {index} not found." | |
def _handle_delete_task(self, index: int) -> str: | |
"""Handle deleting a task""" | |
if self.task_manager.delete_task(index): | |
return f"Deleted task {index}." | |
return f"Task {index} not found." | |
def _handle_filter_tasks(self, filter_term: str) -> str: | |
"""Handle filtering tasks by category or priority""" | |
# Check if filter is a priority | |
if filter_term.lower() in ["high", "medium", "low"]: | |
tasks = self.task_manager.get_tasks(priority=filter_term.lower()) | |
filter_type = "priority" | |
else: | |
# Assume it's a category | |
tasks = self.task_manager.get_tasks(category=filter_term.lower()) | |
filter_type = "category" | |
if not tasks: | |
return f"No tasks found with {filter_type} '{filter_term}'." | |
result = f"Tasks with {filter_type} '{filter_term}':\n" | |
for index, task in tasks: | |
result += f"{index}: {task}\n" | |
return result | |
def _handle_help(self) -> str: | |
"""Handle help request""" | |
return """ | |
I can help you manage your tasks. Here's what you can ask me to do: | |
- Add a task: "Add a new task to buy groceries" | |
- Add with details: "Add task to call mom priority:high due:2023-05-20 category:personal" | |
- List tasks: "Show me my tasks" or "What do I need to do?" | |
- Complete a task: "Mark task 2 as done" or "I completed task 3" | |
- Delete a task: "Delete task 1" or "Remove task 4" | |
- Filter tasks: "Show high priority tasks" or "List personal tasks" | |
- Get help: "Help me" or "What can you do?" | |
Try one of these commands to get started! | |
""" | |
def _handle_general_query(self, query: str) -> str: | |
"""Handle queries that don't match specific patterns""" | |
# Check if it might be a task addition without explicit "add task" prefix | |
if not any(re.search(pattern, query, re.I) for patterns in self.patterns.values() for pattern in patterns): | |
# If query is short and looks like a task, add it | |
if len(query.split()) <= 10 and not query.endswith("?"): | |
return self._handle_add_task(query) | |
return f"I'm not sure how to help with '{query}'. Type 'help' to see what I can do." | |
def save_state(self, filename: str = "tasks.json") -> bool: | |
"""Save the current state of tasks to a file""" | |
return self.task_manager.save_to_file(filename) | |
def load_state(self, filename: str = "tasks.json") -> bool: | |
"""Load tasks from a file""" | |
return self.task_manager.load_from_file(filename) | |
# Example usage for testing | |
if __name__ == "__main__": | |
agent = TaskManagerAgent() | |
# Test with some example queries | |
test_queries = [ | |
"add task to buy groceries", | |
"add task to call mom priority:high due:2023-05-20 category:personal", | |
"show my tasks", | |
"mark task 0 as done", | |
"show high priority tasks", | |
"help" | |
] | |
for query in test_queries: | |
print(f"\nQuery: {query}") | |
response = agent.process_query(query) | |
print(f"Response: {response}") | |