H2P3B / src /services /chat_service.py
muhammadshaheryar's picture
Add application file
dd1b74d
"""
Chat Service for the AI Chatbot with Reusable Intelligence
Handles core chat functionality and integrates with other services
"""
from typing import Dict, Any, List, Optional
from datetime import datetime
import uuid
import os
import re
import google.generativeai as genai
from models.message import Message, MessageCreate
from models.conversation import Conversation
from models.task import Task
from services.translation_service import translation_service, Language
from services.voice_processing_service import voice_processing_service
from services.voice_synthesis_service import voice_synthesis_service
from tools.mcp_server import execute_mcp_tool
# Configure the Gemini API
genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
model = genai.GenerativeModel('gemini-pro')
class ChatService:
"""Service class for handling chat operations"""
def __init__(self):
pass
async def process_user_message(
self,
message: str,
conversation_id: Optional[str],
message_type: str = "text",
language: str = "en",
user_preferences: Optional[Dict[str, Any]] = None,
user_id: Optional[int] = None
) -> Dict[str, Any]:
"""
Process a user message and generate an appropriate response
"""
# Generate or use conversation ID
conv_id = conversation_id or str(uuid.uuid4())
# Handle voice transcripts
if message_type == "voice_transcript":
# Process the voice input to clean text and extract intent
voice_result = await voice_processing_service.process_voice_input(message)
# Use the cleaned text for further processing
processed_message = voice_result.cleaned_text
extracted_intent = voice_result.extracted_intent
confidence_score = voice_result.confidence_score
# If confidence is too low, indicate that the voice input wasn't clear
if confidence_score < 0.3:
response = "I'm sorry, I couldn't clearly understand your voice input. Could you please repeat or type your message?"
return {
"response": response,
"conversation_id": conv_id,
"message_id": str(uuid.uuid4()),
"language": language,
"timestamp": datetime.utcnow().isoformat(),
"voice_processing": {
"intent": extracted_intent,
"confidence": confidence_score,
"processing_time": voice_result.processing_time
}
}
else:
processed_message = message
# Determine language for response
response_language = user_preferences.get('language_preference', language) if user_preferences else language
# Process translation if needed
if translation_service.is_urdu_present(processed_message) or response_language == "ur":
# Translate message to appropriate language for processing
processed_message, detected_lang = translation_service.translate_message_for_response(
processed_message, response_language
)
else:
detected_lang = translation_service.detect_language(processed_message)
# Check if this is a task-related message with enhanced pattern matching
message_lower = processed_message.lower()
task_keywords = [
"task", "kam", "todo", "to do", "to-do", "work", "job", "assignment", "activity", "chore",
"add", "create", "make", "set up", "include", "put in", "need to", "want to", "should do",
"list", "show", "display", "view", "see", "what", "all", "my",
"complete", "done", "finish", "finished", "completed", "mark as done",
"delete", "remove", "cancel", "eliminate", "get rid of", "hatao", "nikalo",
"edit", "update", "change", "modify", "adjust", "revise", "improve", "bnana", "shamil",
"dikhao", "kya hai", "karna", "kar sakta hun", "manage", "organize"
]
# Check for presence of task-related keywords
is_task_related = any(keyword in message_lower for keyword in task_keywords)
# Additional check for common task patterns that might not contain direct keywords
if not is_task_related:
# Check for patterns like "I need to..." or "I want to..." which often indicate task creation
pattern_indicators = [
r'i need to\s+\w+',
r'i want to\s+\w+',
r'i have to\s+\w+',
r'i should\s+\w+',
r'must\s+\w+',
r'have to\s+\w+',
r'got to\s+\w+'
]
for pattern in pattern_indicators:
if re.search(pattern, message_lower, re.IGNORECASE):
is_task_related = True
break
# Generate response based on message content and intent
# For task-related messages, use the dedicated task handler
if is_task_related:
response = await self._handle_task_request(processed_message, conv_id, user_id=user_id)
else:
response = await self._generate_response(processed_message, conv_id, extracted_intent if message_type == "voice_transcript" else "text_message")
# Translate response back to user's preferred language if needed
if response_language == "ur" and detected_lang == "en":
response = translation_service.translate_text(response, Language.URDU, Language.ENGLISH)
elif response_language == "en" and detected_lang == "ur":
response = translation_service.translate_text(response, Language.ENGLISH, Language.URDU)
# Create response object
response_obj = {
"response": response,
"conversation_id": conv_id,
"message_id": str(uuid.uuid4()),
"language": response_language,
"timestamp": datetime.utcnow().isoformat()
}
# Add voice processing info if this was a voice input
if message_type == "voice_transcript":
response_obj["voice_processing"] = {
"original_message": message,
"cleaned_text": processed_message,
"extracted_intent": extracted_intent,
"confidence": confidence_score,
"processing_time": voice_result.processing_time
}
# Add processed tasks if any
if is_task_related:
user_tasks = await self.get_user_tasks(conv_id, user_id=user_id)
# Format tasks using the task-formatter skill principles
formatted_tasks = await self.format_tasks_for_display(user_tasks, format_style="list")
# Include both raw tasks and formatted version
response_obj["processed_tasks"] = user_tasks
response_obj["formatted_tasks"] = formatted_tasks
# Generate voice output if user prefers voice response
if user_preferences and user_preferences.get('voice_enabled', False):
try:
# Generate speech from the response text
audio_file_path = await voice_synthesis_service.synthesize_speech(
text=response,
voice_id=user_preferences.get('voice_id', 'MF3mGyEYCl7XYWbV9V6O') # Default to Adam
)
if audio_file_path:
response_obj["audio_url"] = f"/api/audio/{os.path.basename(audio_file_path)}"
except Exception as e:
print(f"Error generating voice output: {e}")
return response_obj
async def _generate_response(self, message: str, conversation_id: str, intent: str = "text_message") -> str:
"""
Generate an appropriate response based on the message and intent using Gemini API
"""
message_lower = message.lower()
# Handle task-related messages
if "task" in message_lower or "kam" in message_lower:
return await self._handle_task_request(message, conversation_id)
# Handle greetings
elif "hello" in message_lower or "hi" in message_lower or "hey" in message_lower or \
"ہیلو" in message_lower or "ہائے" in message_lower:
return "Hello! How can I assist you today?" if not translation_service.is_urdu_present(message) else "ہیلو! آج میں آپ کی کیسے مدد کر سکتا ہوں؟"
# Handle questions
elif "?" in message or "kya" in message_lower or "kyun" in message_lower or "kese" in message_lower:
return "That's a great question! How else can I assist you?" if not translation_service.is_urdu_present(message) else "یہ ایک عمدہ سوال ہے! میں اور کیسے مدد کر سکتا ہوں؟"
# Use Gemini API for general responses
else:
try:
# Prepare the prompt for Gemini
prompt = f"You are a helpful AI assistant. Respond to the following message: '{message}'"
# Generate response using Gemini
response = model.generate_content(prompt)
# Extract the text response
ai_response = response.text if response.text else f"I received your message: '{message}'. How can I help you?"
# Handle potential encoding issues for multilingual support
try:
ai_response.encode('utf-8').decode('utf-8')
except UnicodeError:
# If there's a Unicode error, fall back to the original message
ai_response = f"I received your message: '{message}'. How can I help you?"
return ai_response
except Exception as e:
print(f"Error calling Gemini API: {e}")
return f"I received your message: '{message}'. How can I help you?"
async def _handle_task_request(self, message: str, conversation_id: str, user_id: Optional[int] = None) -> str:
"""
Handle task-related requests in the chat
"""
import re
message_lower = message.lower()
# Enhanced task extraction patterns
# Extract potential task title from the message with more flexible patterns
task_title = None
# Check for add/create task patterns
add_patterns = [
r'(?:add|create|make|set up|put in|include|add to list|create for me|need to|want to|would like to)\s+(?:a |the |an |my )?(?:task|kam|work|job|thing|to-do|todo|item)\s+(.+?)(?:\.|!|\?|and|but|please|now|$)',
r'(?:add|create|make|set up|put in|include|add to list|create for me|need to|want to|would like to)\s+(?:a |the |an |my )?(.+?)(?:\.|!|\?|and|but|please|now|$)',
r'(?:i need to|i want to|i would like to|i should|must|have to)\s+(.+?)(?:\.|!|\?|and|but|please|now|$)',
r'(?:task|kam|work|job|thing|to-do|todo|item):\s*(.+?)(?:\.|!|\?|and|but|please|now|$)',
r'(.+?)\s+(?:needs to be done|should be done|must be done|to be done|on my list|in my tasks|for me|as a task)',
]
if any(keyword in message_lower for keyword in ["add", "create", "shamil", "bnana", "need to", "want to", "i should", "must", "have to", "create for me"]):
for pattern in add_patterns:
match = re.search(pattern, message_lower, re.IGNORECASE)
if match:
task_title = match.group(1).strip()
# Clean up common words that might be captured incorrectly
task_title = re.sub(r'^to\s+', '', task_title) # Remove leading "to" from "i need to do X"
task_title = re.sub(r'\s+$', '', task_title) # Remove trailing spaces
if task_title and len(task_title) > 1: # Ensure we have a meaningful title
break
# Enhanced task identification patterns for edit/update
edit_patterns = [
r'(?:edit|update|change|modify|adjust|revise|improve)\s+(?:task|kam|work|job|item|#?(\d+)|(.+?))\s+(?:to|and|with|by|so that)\s+(.+?)(?:\.|!|\?|$)',
r'(?:edit|update|change|modify|adjust|revise|improve)\s+(?:task|kam|work|job|item|#?(\d+)|(.+?))',
r'(?:update|change|modify)\s+(?:the )?(.+?)(?:\s+to|\s+with|\s+and)\s+(.+?)(?:\.|!|\?|$)'
]
# Check for edit/update task patterns
edit_task_id = None
edit_field = None
edit_value = None
for pattern in edit_patterns:
match = re.search(pattern, message_lower, re.IGNORECASE)
if match:
# Extract the task identifier (could be number or name)
groups = match.groups()
if groups[0]: # Task ID number
edit_task_id = groups[0]
elif groups[1]: # Task name
edit_task_id = groups[1]
# If there's a third group, it might be the update details
if len(groups) > 2 and groups[2]:
edit_details = groups[2]
# Try to extract field and value for update
field_pattern = r'(?:status|priority|due date|due|date|title|description)\s+(.+?)(?:\.|!|\?|$)'
field_match = re.search(field_pattern, edit_details)
if field_match:
edit_value = field_match.group(1).strip()
break
# Determine the type of task request with enhanced pattern matching
if any(keyword in message_lower for keyword in ["add", "create", "shamil", "bnana", "need to", "want to", "i should", "must", "have to", "create for me"]):
if task_title:
# Create the task with extracted title
try:
new_task = await self.create_task(title=task_title, conversation_id=conversation_id, user_id=user_id)
return f"Task '{new_task['title']}' has been added successfully!" if not translation_service.is_urdu_present(message) else f"کام '{new_task['title']}' کامیابی سے شامل کر دیا گیا ہے!"
except Exception as e:
return f"Error adding task: {str(e)}"
else:
# If no specific task title found, ask for it
return "I can help you add a task. What would you like to add?" if not translation_service.is_urdu_present(message) else "میں آپ کے کام شامل کرنے میں مدد کر سکتا ہوں۔ آپ کیا شامل کرنا چاہیں گے؟"
elif any(keyword in message_lower for keyword in ["list", "show", "dikhao", "kya hai", "what", "display", "view", "see"]):
# Show existing tasks
tasks = await self.get_user_tasks(conversation_id, user_id=user_id)
if tasks:
# Format tasks using the task-formatter skill
formatted_tasks = await self.format_tasks_for_display(tasks, format_style="list")
return f"Here are your tasks:\n\n{formatted_tasks}" if not translation_service.is_urdu_present(message) else f"یہ آپ کے کام ہیں:\n\n{formatted_tasks}"
else:
return "You don't have any tasks yet. Would you like to add one?" if not translation_service.is_urdu_present(message) else "آپ کے کوئی کام نہیں ہیں۔ کیا آپ ایک شامل کرنا چاہیں گے؟"
elif any(keyword in message_lower for keyword in ["complete", "done", "finished", "finish", "hogaya", "khatam", "completed", "marked done"]):
# Try to identify which task to complete from the message
# Look for task number or title mentioned in the message
tasks = await self.get_user_tasks(conversation_id, user_id=user_id)
if tasks:
# Look for numbers in the message to identify task
number_match = re.search(r'#?(\d+)', message_lower)
if not number_match:
number_match = re.search(r'\b(\d+)\b', message_lower)
if number_match and int(number_match.group(1)) <= len(tasks):
task_index = int(number_match.group(1)) - 1
target_task = tasks[task_index]
try:
updated_task = await self.update_task(task_id=target_task['id'], status='completed', user_id=user_id)
return f"Task '{updated_task['title']}' has been marked as completed!" if not translation_service.is_urdu_present(message) else f"کام '{updated_task['title']}' کو مکمل کے بطور نشان زد کر دیا گیا ہے!"
except Exception as e:
return f"Error completing task: {str(e)}"
else:
# Try to match by title if no number found
matched_task = None
for task in tasks:
if task['title'].lower() in message_lower or message_lower in task['title'].lower():
matched_task = task
break
if matched_task:
try:
updated_task = await self.update_task(task_id=matched_task['id'], status='completed', user_id=user_id)
return f"Task '{updated_task['title']}' has been marked as completed!" if not translation_service.is_urdu_present(message) else f"کام '{updated_task['title']}' کو مکمل کے بطور نشان زد کر دیا گیا ہے!"
except Exception as e:
return f"Error completing task: {str(e)}"
else:
# If no specific task identified, ask for it
return "Which task would you like to mark as complete? Please specify the task number or name." if not translation_service.is_urdu_present(message) else "آپ کون سا کام مکمل کرنا چاہیں گے؟ براہ کرم کام کا نمبر یا نام بتائیں۔"
else:
return "You don't have any tasks to complete. Would you like to add one first?" if not translation_service.is_urdu_present(message) else "آپ کے پاس مکمل کرنے کے لیے کوئی کام نہیں ہے۔ کیا آپ پہلے کوئی کام شامل کرنا چاہیں گے؟"
elif any(keyword in message_lower for keyword in ["delete", "remove", "hatao", "nikalo", "cancel", "eliminate", "get rid of"]):
# Try to identify which task to delete from the message
tasks = await self.get_user_tasks(conversation_id, user_id=user_id)
if tasks:
# Look for numbers in the message to identify task
number_match = re.search(r'#?(\d+)', message_lower)
if not number_match:
number_match = re.search(r'\b(\d+)\b', message_lower)
if number_match and int(number_match.group(1)) <= len(tasks):
task_index = int(number_match.group(1)) - 1
target_task = tasks[task_index]
try:
result = await self.delete_task(task_id=target_task['id'], user_id=user_id)
return f"Task '{target_task['title']}' has been deleted successfully!" if not translation_service.is_urdu_present(message) else f"کام '{target_task['title']}' کامیابی سے حذف کر دیا گیا ہے!"
except Exception as e:
return f"Error deleting task: {str(e)}"
else:
# Try to match by title if no number found
matched_task = None
for task in tasks:
if task['title'].lower() in message_lower or message_lower in task['title'].lower():
matched_task = task
break
if matched_task:
try:
result = await self.delete_task(task_id=matched_task['id'], user_id=user_id)
return f"Task '{matched_task['title']}' has been deleted successfully!" if not translation_service.is_urdu_present(message) else f"کام '{matched_task['title']}' کامیابی سے حذف کر دیا گیا ہے!"
except Exception as e:
return f"Error deleting task: {str(e)}"
else:
# If no specific task identified, ask for it
return "Which task would you like to delete? Please specify the task number or name." if not translation_service.is_urdu_present(message) else "آپ کون سا کام حذف کرنا چاہیں گے؟ براہ کرم کام کا نمبر یا نام بتائیں۔"
else:
return "You don't have any tasks to delete. Would you like to add one first?" if not translation_service.is_urdu_present(message) else "آپ کے پاس حذف کرنے کے لیے کوئی کام نہیں ہے۔ کیا آپ پہلے کوئی کام شامل کرنا چاہیں گے؟"
elif any(keyword in message_lower for keyword in ["edit", "update", "change", "modify", "adjust", "revise", "improve"]):
# Handle edit/update requests
tasks = await self.get_user_tasks(conversation_id, user_id=user_id)
if tasks:
# Try to identify which task to edit
number_match = re.search(r'#?(\d+)', message_lower)
if not number_match:
number_match = re.search(r'\b(\d+)\b', message_lower)
if number_match and int(number_match.group(1)) <= len(tasks):
task_index = int(number_match.group(1)) - 1
target_task = tasks[task_index]
# Extract what needs to be updated
update_fields = {}
# Check for status updates
if any(status in message_lower for status in ['pending', 'in progress', 'completed', 'cancelled']):
for status in ['pending', 'in progress', 'completed', 'cancelled']:
if status in message_lower:
update_fields['status'] = status.replace(' ', '_') if status != 'in progress' else 'in_progress'
break
# Check for priority updates
if any(priority in message_lower for priority in ['low', 'medium', 'high', 'urgent']):
for priority in ['low', 'medium', 'high', 'urgent']:
if priority in message_lower:
update_fields['priority'] = priority
break
# Check for due date updates
date_pattern = r'(\d{4}-\d{2}-\d{2}|\d{1,2}[/-]\d{1,2}[/-]\d{2,4})'
date_match = re.search(date_pattern, message_lower)
if date_match:
update_fields['due_date'] = date_match.group(1)
# Check for title updates
title_pattern = r'(?:title|name|rename|called|named)\s+(.+?)(?:\.|!|\?|$)'
title_match = re.search(title_pattern, message_lower, re.IGNORECASE)
if title_match:
update_fields['title'] = title_match.group(1).strip()
if update_fields:
try:
updated_task = await self.update_task(task_id=target_task['id'], user_id=user_id, **update_fields)
changes_desc = ", ".join([f"{key}: {value}" for key, value in update_fields.items()])
return f"Task '{updated_task['title']}' has been updated with: {changes_desc}!" if not translation_service.is_urdu_present(message) else f"کام '{updated_task['title']}' کو اپ ڈیٹ کر دیا گیا ہے: {changes_desc}!"
except Exception as e:
return f"Error updating task: {str(e)}"
else:
return "What would you like to update for this task? You can change status, priority, due date, or title." if not translation_service.is_urdu_present(message) else "آپ اس کام کو کیا اپ ڈیٹ کرنا چاہیں گے؟ آپ حالت، ترجیح، تاریخ یا عنوان تبدیل کر سکتے ہیں۔"
else:
# Try to match by title if no number found
matched_task = None
for task in tasks:
if task['title'].lower() in message_lower or message_lower in task['title'].lower():
matched_task = task
break
if matched_task:
# Extract what needs to be updated (same logic as above)
update_fields = {}
# Check for status updates
if any(status in message_lower for status in ['pending', 'in progress', 'completed', 'cancelled']):
for status in ['pending', 'in progress', 'completed', 'cancelled']:
if status in message_lower:
update_fields['status'] = status.replace(' ', '_') if status != 'in progress' else 'in_progress'
break
# Check for priority updates
if any(priority in message_lower for priority in ['low', 'medium', 'high', 'urgent']):
for priority in ['low', 'medium', 'high', 'urgent']:
if priority in message_lower:
update_fields['priority'] = priority
break
# Check for due date updates
date_pattern = r'(\d{4}-\d{2}-\d{2}|\d{1,2}[/-]\d{1,2}[/-]\d{2,4})'
date_match = re.search(date_pattern, message_lower)
if date_match:
update_fields['due_date'] = date_match.group(1)
# Check for title updates
title_pattern = r'(?:title|name|rename|called|named)\s+(.+?)(?:\.|!|\?|$)'
title_match = re.search(title_pattern, message_lower, re.IGNORECASE)
if title_match:
update_fields['title'] = title_match.group(1).strip()
if update_fields:
try:
updated_task = await self.update_task(task_id=matched_task['id'], user_id=user_id, **update_fields)
changes_desc = ", ".join([f"{key}: {value}" for key, value in update_fields.items()])
return f"Task '{updated_task['title']}' has been updated with: {changes_desc}!" if not translation_service.is_urdu_present(message) else f"کام '{updated_task['title']}' کو اپ ڈیٹ کر دیا گیا ہے: {changes_desc}!"
except Exception as e:
return f"Error updating task: {str(e)}"
else:
return "What would you like to update for this task? You can change status, priority, due date, or title." if not translation_service.is_urdu_present(message) else "آپ اس کام کو کیا اپ ڈیٹ کرنا چاہیں گے؟ آپ حالت، ترجیح، تاریخ یا عنوان تبدیل کر سکتے ہیں۔"
else:
return "Which task would you like to edit? Please specify the task number or name." if not translation_service.is_urdu_present(message) else "آپ کون سا کام ایڈیٹ کرنا چاہیں گے؟ براہ کرم کام کا نمبر یا نام بتائیں۔"
else:
return "You don't have any tasks to edit. Would you like to add one first?" if not translation_service.is_urdu_present(message) else "آپ کے پاس ایڈیٹ کرنے کے لیے کوئی کام نہیں ہے۔ کیا آپ پہلے کوئی کام شامل کرنا چاہیں گے؟"
else:
return "I can help you manage your tasks. You can ask me to add, list, complete, delete, or edit tasks." if not translation_service.is_urdu_present(message) else "میں آپ کے کاموں کا نظم کرنے میں مدد کر سکتا ہوں۔ آپ مجھ سے کام شامل کرنے، فہرست دینے، مکمل کرنے، حذف کرنے یا تبدیل کرنے کو کہ سکتے ہیں۔"
async def create_task(self, title: str, description: Optional[str] = None,
status: str = "pending", priority: str = "medium",
due_date: Optional[str] = None,
conversation_id: Optional[str] = None, user_id: Optional[int] = None) -> Dict[str, Any]:
"""
Create a new task using MCP tools
"""
try:
# Use MCP tools to create task
result = execute_mcp_tool(
"add_task",
title=title,
description=description,
status=status,
priority=priority,
due_date=due_date,
conversation_id=conversation_id,
user_id=user_id
)
return result
except Exception as e:
raise Exception(f"Error creating task: {str(e)}")
async def get_user_tasks(self, conversation_id: str, user_id: Optional[int] = None) -> List[Dict[str, Any]]:
"""
Get user tasks for the conversation
"""
try:
# Use MCP tools to fetch tasks
tasks_data = execute_mcp_tool(
"list_tasks",
conversation_id=conversation_id,
user_id=user_id
)
return tasks_data
except Exception as e:
raise Exception(f"Error retrieving tasks: {str(e)}")
async def update_task(self, task_id: str, user_id: Optional[int] = None, **updates) -> Dict[str, Any]:
"""
Update an existing task using MCP tools
"""
try:
# Use MCP tools to update task
result = execute_mcp_tool(
"update_task",
task_id=task_id,
user_id=user_id,
**updates
)
return result
except Exception as e:
raise Exception(f"Error updating task: {str(e)}")
async def delete_task(self, task_id: str, user_id: Optional[int] = None) -> Dict[str, Any]:
"""
Delete a task using MCP tools
"""
try:
# Use MCP tools to delete task
result = execute_mcp_tool("delete_task", task_id=task_id, user_id=user_id)
return result
except Exception as e:
raise Exception(f"Error deleting task: {str(e)}")
async def format_tasks_for_display(self, tasks: List[Dict[str, Any]], format_style: str = "table") -> str:
"""
Format tasks for display using task-formatter skill principles
"""
if not tasks:
return "No tasks found." if format_style != "urdu" else "کوئی کام نہیں ملا۔"
if format_style == "table":
# Create a markdown-style table for tasks
table_lines = [
"## Your Tasks",
"| # | Title | Status | Priority | Due Date |",
"|---|-------|--------|----------|----------|"
]
for idx, task in enumerate(tasks, 1):
# Add status emoji
status_emoji = {
"pending": "⏳",
"in_progress": "🔄",
"completed": "✅",
"cancelled": "❌"
}.get(task.get('status', 'pending'), "📋")
# Add priority indicator
priority_indicator = {
"low": "▶️",
"medium": "◆",
"high": "⭐",
"urgent": "🔥"
}.get(task.get('priority', 'medium'), "🔹")
due_date = task.get('due_date', 'None')
if due_date:
# Format the date properly
if isinstance(due_date, str):
# If it's already a string, just take the date part
formatted_date = due_date.split('T')[0] if 'T' in due_date else due_date
else:
# If it's a datetime object, format it
formatted_date = str(due_date).split(' ')[0] if hasattr(due_date, '__str__') else 'None'
else:
formatted_date = 'None'
table_lines.append(
f"| {idx} | {task.get('title', 'Untitled')} | "
f"{status_emoji} {task.get('status', 'pending')} | "
f"{priority_indicator} {task.get('priority', 'medium')} | "
f"{formatted_date} |"
)
return "\n".join(table_lines)
elif format_style == "list":
# Numbered list format with more details
task_list = []
for idx, task in enumerate(tasks, 1):
status_emoji = {
"pending": "⏳",
"in_progress": "🔄",
"completed": "✅",
"cancelled": "❌"
}.get(task.get('status', 'pending'), "📋")
priority_indicator = {
"low": "▶️",
"medium": "◆",
"high": "⭐",
"urgent": "🔥"
}.get(task.get('priority', 'medium'), "🔹")
due_date = task.get('due_date', 'None')
if due_date:
if isinstance(due_date, str):
formatted_date = due_date.split('T')[0] if 'T' in due_date else due_date
else:
formatted_date = str(due_date).split(' ')[0] if hasattr(due_date, '__str__') else 'None'
else:
formatted_date = 'None'
task_list.append(
f"{idx}. {status_emoji} {task.get('title', 'Untitled')} "
f"[{priority_indicator} {task.get('priority', 'medium')}] "
f"({task.get('status', 'pending')}) - Due: {formatted_date}"
)
return "\n".join(task_list)
elif format_style == "compact":
# Compact format suitable for voice responses
if len(tasks) == 0:
return "You have no tasks."
elif len(tasks) == 1:
task = tasks[0]
return f"You have 1 task: {task.get('title', 'Untitled')}. Status: {task.get('status', 'pending')}."
else:
return f"You have {len(tasks)} tasks. Task 1: {tasks[0].get('title', 'Untitled')}. Use 'show tasks' to see the full list."
else:
# Simple list format
task_list = []
for idx, task in enumerate(tasks, 1):
task_list.append(f"{idx}. {task.get('title', 'Untitled')} - {task.get('status', 'pending')}")
return "\n".join(task_list)
# Singleton instance
chat_service = ChatService()
def get_chat_service() -> ChatService:
"""Get the singleton chat service instance"""
return chat_service