Reminder_app / index.html
Navya-Sree's picture
Create index.html
fa1bd0f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI-Powered Reminder System</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/luxon@3.3.0/build/global/luxon.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
:root {
--primary: #4361ee;
--secondary: #3f37c9;
--success: #4cc9f0;
--danger: #f72585;
--warning: #f8961e;
--light: #f8f9fa;
--dark: #212529;
}
body {
background-color: #f0f2f5;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-5px);
}
.priority-low { border-left: 4px solid #6c757d; }
.priority-medium { border-left: 4px solid #ffc107; }
.priority-high { border-left: 4px solid #dc3545; }
.voice-recorder {
background: #e9ecef;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
}
#app {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.sidebar {
background: white;
border-radius: 12px;
padding: 20px;
height: fit-content;
}
.calendar-day {
border: 1px solid #dee2e6;
min-height: 120px;
padding: 10px;
cursor: pointer;
}
.calendar-day.active {
background-color: #e7f5ff;
border-color: #4361ee;
}
.notification-badge {
position: absolute;
top: -8px;
right: -8px;
}
</style>
</head>
<body>
<div id="app">
<!-- Header -->
<header class="py-4 mb-4 border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-md-6">
<h1 class="display-4 fw-bold">
<i class="bi bi-alarm"></i> AI-Powered Reminder System
</h1>
</div>
<div class="col-md-6 text-end">
<button class="btn btn-primary me-2" @click="showNotifications = !showNotifications">
<i class="bi bi-bell"></i>
<span v-if="notifications.length > 0" class="badge bg-danger notification-badge">
{{ notifications.length }}
</span>
</button>
<button class="btn btn-outline-primary" @click="showSettings = !showSettings">
<i class="bi bi-gear"></i> Settings
</button>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<div class="col-lg-4 mb-4">
<div class="sidebar sticky-top">
<h3 class="mb-4"><i class="bi bi-plus-circle"></i> Add New Reminder</h3>
<form @submit.prevent="createReminder">
<div class="mb-3">
<label class="form-label">Title *</label>
<input v-model="newReminder.title" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea v-model="newReminder.description" class="form-control" rows="2"></textarea>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Date *</label>
<input v-model="newReminder.due_date" type="date" class="form-control" required>
</div>
<div class="col-md-6">
<label class="form-label">Time *</label>
<input v-model="newReminder.due_time" type="time" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">Priority</label>
<select v-model="newReminder.priority" class="form-select">
<option value="Low">Low</option>
<option value="Medium">Medium</option>
<option value="High">High</option>
</select>
</div>
<div class="mb-3">
<label class="form-check-label">
<input type="checkbox" v-model="showVoiceRecorder" class="form-check-input">
Add Voice Reminder
</label>
</div>
<div v-if="showVoiceRecorder" class="voice-recorder mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<button class="btn btn-sm btn-danger" @click="toggleRecording" :disabled="isRecording">
<i class="bi bi-record-circle"></i>
{{ isRecording ? 'Stop Recording' : 'Start Recording' }}
</button>
<button v-if="audioUrl" class="btn btn-sm btn-outline-secondary ms-2" @click="playRecordedAudio">
<i class="bi bi-play"></i> Play
</button>
</div>
<audio ref="audioPlayer" :src="audioUrl" hidden></audio>
</div>
<div v-if="recordingStatus" class="text-muted small">{{ recordingStatus }}</div>
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-check-circle"></i> Create Reminder
</button>
</form>
</div>
</div>
<!-- Main Content -->
<div class="col-lg-8">
<!-- Dashboard Stats -->
<div class="row mb-4">
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">Total Reminders</h5>
<h2 class="text-primary">{{ reminders.length }}</h2>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">Pending Tasks</h5>
<h2 class="text-warning">{{ pendingReminders }}</h2>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">High Priority</h5>
<h2 class="text-danger">{{ highPriorityReminders }}</h2>
</div>
</div>
</div>
</div>
<!-- Calendar View -->
<div class="card mb-4">
<div class="card-header bg-white">
<h4 class="mb-0"><i class="bi bi-calendar"></i> Calendar View</h4>
</div>
<div class="card-body">
<div class="d-flex justify-content-between mb-3">
<button class="btn btn-sm btn-outline-secondary" @click="prevMonth">
<i class="bi bi-chevron-left"></i>
</button>
<h5 class="mb-0">{{ currentMonth }}</h5>
<button class="btn btn-sm btn-outline-secondary" @click="nextMonth">
<i class="bi bi-chevron-right"></i>
</button>
</div>
<div class="calendar-grid">
<div class="row text-center fw-bold border-bottom mb-2">
<div class="col p-2">Sun</div>
<div class="col p-2">Mon</div>
<div class="col p-2">Tue</div>
<div class="col p-2">Wed</div>
<div class="col p-2">Thu</div>
<div class="col p-2">Fri</div>
<div class="col p-2">Sat</div>
</div>
<div v-for="week in calendar" :key="week[0].date" class="row">
<div v-for="day in week" :key="day.date"
class="col calendar-day"
:class="{
'active': isToday(day.date),
'text-muted': day.month !== currentMonthNum
}"
@click="selectedDate = day.date">
<div class="d-flex justify-content-between">
<span>{{ day.day }}</span>
<span v-if="countRemindersForDate(day.date) > 0"
class="badge bg-primary rounded-pill">
{{ countRemindersForDate(day.date) }}
</span>
</div>
<div v-if="isToday(day.date)" class="small text-success">Today</div>
</div>
</div>
</div>
</div>
</div>
<!-- Reminders for Selected Date -->
<div class="card">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h4 class="mb-0"><i class="bi bi-list-check"></i> Reminders for {{ selectedDate }}</h4>
<div>
<button class="btn btn-sm btn-outline-primary" @click="getAIInsightsForDate">
<i class="bi bi-robot"></i> AI Insights
</button>
</div>
</div>
<div class="card-body">
<div v-if="aiInsights" class="alert alert-info mb-4">
<h5><i class="bi bi-lightbulb"></i> AI Insights</h5>
<div v-html="aiInsights"></div>
</div>
<div v-if="filteredReminders.length === 0" class="text-center py-5">
<i class="bi bi-check2-circle" style="font-size: 3rem;"></i>
<h5 class="mt-3">No reminders for this date</h5>
<p class="text-muted">Add a new reminder using the form on the left</p>
</div>
<div v-for="reminder in filteredReminders" :key="reminder.id" class="mb-3">
<div class="card" :class="'priority-' + reminder.priority.toLowerCase()">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
v-model="reminder.completed"
@change="updateReminder(reminder)">
<label class="form-check-label">
<h5 class="mb-0">{{ reminder.title }}</h5>
</label>
</div>
<div>
<button class="btn btn-sm btn-outline-secondary"
@click="showAIInsights(reminder)">
<i class="bi bi-robot"></i>
</button>
<button class="btn btn-sm btn-outline-danger ms-1"
@click="deleteReminder(reminder.id)">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<div class="mt-2">
<div class="text-muted">
<i class="bi bi-clock"></i> {{ reminder.due_time }} |
<span :class="{
'text-danger': reminder.priority === 'High',
'text-warning': reminder.priority === 'Medium',
'text-muted': reminder.priority === 'Low'
}">
{{ reminder.priority }} priority
</span>
</div>
<p v-if="reminder.description" class="mt-2">{{ reminder.description }}</p>
<div v-if="reminder.voice_note" class="mt-2">
<audio controls :src="reminder.voice_note" class="w-100"></audio>
</div>
<div v-if="reminder.ai_insights" class="mt-3">
<div class="alert alert-light">
<h6><i class="bi bi-lightbulb"></i> AI Insights</h6>
<div v-html="reminder.ai_insights"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Notifications Modal -->
<div v-if="showNotifications" class="modal fade show" style="display: block; background: rgba(0,0,0,0.5)">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title"><i class="bi bi-bell"></i> Active Notifications</h5>
<button type="button" class="btn-close btn-close-white" @click="showNotifications = false"></button>
</div>
<div class="modal-body">
<div v-if="notifications.length === 0" class="text-center py-4">
<i class="bi bi-check2-circle" style="font-size: 3rem;"></i>
<h5 class="mt-3">No active notifications</h5>
</div>
<div v-for="notification in notifications" :key="notification.id" class="alert alert-warning">
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>{{ notification.title }}</strong> is due now!
</div>
<button class="btn btn-sm btn-outline-secondary" @click="dismissNotification(notification.id)">
Dismiss
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const { createApp, ref, computed, onMounted } = Vue;
createApp({
setup() {
// Application state
const reminders = ref([]);
const notifications = ref([]);
const newReminder = ref({
title: '',
description: '',
due_date: luxon.DateTime.local().toISODate(),
due_time: luxon.DateTime.local().toFormat('HH:mm'),
priority: 'Medium',
voice_note: ''
});
const selectedDate = ref(luxon.DateTime.local().toISODate());
const currentDate = ref(luxon.DateTime.local());
const showVoiceRecorder = ref(false);
const isRecording = ref(false);
const audioUrl = ref(null);
const audioBlob = ref(null);
const recordingStatus = ref('');
const showNotifications = ref(false);
const showSettings = ref(false);
const aiInsights = ref('');
const mediaRecorder = ref(null);
const audioChunks = ref([]);
const audioContext = ref(null);
const audioPlayer = ref(null);
// Computed properties
const pendingReminders = computed(() => {
return reminders.value.filter(r => !r.completed).length;
});
const highPriorityReminders = computed(() => {
return reminders.value.filter(r => r.priority === 'High' && !r.completed).length;
});
const filteredReminders = computed(() => {
return reminders.value
.filter(r => r.due_date === selectedDate.value)
.sort((a, b) => {
if (a.completed !== b.completed) return a.completed ? 1 : -1;
if (a.priority !== b.priority) {
const priorityOrder = { 'High': 1, 'Medium': 2, 'Low': 3 };
return priorityOrder[a.priority] - priorityOrder[b.priority];
}
return a.due_time.localeCompare(b.due_time);
});
});
const currentMonth = computed(() => {
return currentDate.value.toFormat('MMMM yyyy');
});
const currentMonthNum = computed(() => {
return currentDate.value.month;
});
const calendar = computed(() => {
const startOfMonth = currentDate.value.startOf('month');
const startDate = startOfMonth.startOf('week');
const endDate = currentDate.value.endOf('month').endOf('week');
const weeks = [];
let currentDay = startDate;
while (currentDay <= endDate) {
const week = [];
for (let i = 0; i < 7; i++) {
week.push({
date: currentDay.toISODate(),
day: currentDay.day,
month: currentDay.month
});
currentDay = currentDay.plus({ days: 1 });
}
weeks.push(week);
}
return weeks;
});
// Methods
const loadReminders = async () => {
try {
const response = await axios.get('/api/reminders');
reminders.value = response.data;
checkForNotifications();
} catch (error) {
console.error('Error loading reminders:', error);
}
};
const createReminder = async () => {
try {
const response = await axios.post('/api/reminders', {
...newReminder.value,
voice_note: audioUrl.value
});
reminders.value.push(response.data);
newReminder.value = {
title: '',
description: '',
due_date: luxon.DateTime.local().toISODate(),
due_time: luxon.DateTime.local().toFormat('HH:mm'),
priority: 'Medium',
voice_note: ''
};
audioUrl.value = null;
audioBlob.value = null;
recordingStatus.value = '';
showVoiceRecorder.value = false;
alert('Reminder created successfully!');
} catch (error) {
console.error('Error creating reminder:', error);
alert('Failed to create reminder');
}
};
const updateReminder = async (reminder) => {
try {
await axios.put(`/api/reminders/${reminder.id}`, reminder);
checkForNotifications();
} catch (error) {
console.error('Error updating reminder:', error);
}
};
const deleteReminder = async (id) => {
if (!confirm('Are you sure you want to delete this reminder?')) return;
try {
await axios.delete(`/api/reminders/${id}`);
reminders.value = reminders.value.filter(r => r.id !== id);
checkForNotifications();
} catch (error) {
console.error('Error deleting reminder:', error);
}
};
const startRecording = async () => {
try {
audioChunks.value = [];
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder.value = new MediaRecorder(stream);
mediaRecorder.value.ondataavailable = event => {
audioChunks.value.push(event.data);
};
mediaRecorder.value.onstop = async () => {
const audioBlob = new Blob(audioChunks.value, { type: 'audio/wav' });
audioUrl.value = URL.createObjectURL(audioBlob);
// Convert to base64 for saving
const reader = new FileReader();
reader.readAsDataURL(audioBlob);
reader.onloadend = async () => {
const base64Audio = reader.result;
try {
const response = await axios.post('/api/save-voice-note', {
audio_data: base64Audio
});
newReminder.value.voice_note = response.data.path;
} catch (error) {
console.error('Error saving voice note:', error);
}
};
};
mediaRecorder.value.start();
isRecording.value = true;
recordingStatus.value = 'Recording... Click stop when finished';
} catch (error) {
console.error('Error starting recording:', error);
alert('Microphone access denied. Please enable microphone permissions.');
}
};
const stopRecording = () => {
if (mediaRecorder.value) {
mediaRecorder.value.stop();
mediaRecorder.value.stream.getTracks().forEach(track => track.stop());
isRecording.value = false;
recordingStatus.value = 'Recording saved. Click play to review';
}
};
const toggleRecording = () => {
if (isRecording.value) {
stopRecording();
} else {
startRecording();
}
};
const playRecordedAudio = () => {
if (audioPlayer.value && audioUrl.value) {
audioPlayer.value.play();
}
};
const countRemindersForDate = (date) => {
return reminders.value.filter(r => r.due_date === date && !r.completed).length;
};
const isToday = (date) => {
return date === luxon.DateTime.local().toISODate();
};
const nextMonth = () => {
currentDate.value = currentDate.value.plus({ months: 1 });
};
const prevMonth = () => {
currentDate.value = currentDate.value.minus({ months: 1 });
};
const checkForNotifications = () => {
const now = luxon.DateTime.local();
notifications.value = reminders.value
.filter(r => !r.completed)
.filter(r => {
const reminderDate = luxon.DateTime.fromISO(r.due_date);
const reminderTime = luxon.DateTime.fromISO(`${r.due_date}T${r.due_time}`);
// Check if reminder is due within the last minute
return reminderDate.hasSame(now, 'day') &&
Math.abs(reminderTime.diff(now, 'minutes').minutes) < 1;
})
.map(r => ({
id: r.id,
title: r.title,
time: r.due_time
}));
if (notifications.value.length > 0) {
showNotifications.value = true;
}
};
const dismissNotification = (id) => {
notifications.value = notifications.value.filter(n => n.id !== id);
};
const showAIInsights = async (reminder) => {
try {
const response = await axios.post('/api/ai-insights', {
title: reminder.title,
description: reminder.description
});
reminder.ai_insights = response.data.insights;
await updateReminder(reminder);
} catch (error) {
console.error('Error getting AI insights:', error);
}
};
const getAIInsightsForDate = async () => {
const dateReminders = reminders.value.filter(r => r.due_date === selectedDate.value);
if (dateReminders.length === 0) {
aiInsights.value = 'No reminders to analyze for this date';
return;
}
try {
const response = await axios.post('/api/ai-insights', {
title: `Reminders for ${selectedDate.value}`,
description: `You have ${dateReminders.length} reminders scheduled for this date.
${dateReminders.filter(r => r.priority === 'High').length} are high priority.`
});
aiInsights.value = response.data.insights;
} catch (error) {
console.error('Error getting AI insights:', error);
}
};
// Lifecycle hooks
onMounted(() => {
loadReminders();
// Check for notifications every minute
setInterval(() => {
checkForNotifications();
}, 60000);
});
return {
reminders,
notifications,
newReminder,
selectedDate,
currentDate,
showVoiceRecorder,
isRecording,
audioUrl,
recordingStatus,
showNotifications,
showSettings,
aiInsights,
audioPlayer,
pendingReminders,
highPriorityReminders,
filteredReminders,
currentMonth,
currentMonthNum,
calendar,
createReminder,
updateReminder,
deleteReminder,
toggleRecording,
playRecordedAudio,
countRemindersForDate,
isToday,
nextMonth,
prevMonth,
dismissNotification,
showAIInsights,
getAIInsightsForDate
};
}
}).mount('#app');
</script>
</body>
</html>