Google Calendar Integration
Integrează VAI Portal cu Google Calendar pentru programare inteligentă, management automat al întâlnirilor și sincronizare perfectă cu calendarul tău.
Smart Scheduling
Programează automat întâlniri bazate pe disponibilitate și preferințe.
Intelligent Reminders
Trimite notificări și reminder-e personalizate pentru întâlniri.
Time Zone Support
Gestionează automat fusuri orare diferite pentru echipe globale.
Calendar Sync
Sincronizează evenimentele între multiple calendare și platforme.
Arhitectura Integrării
VAI Portal
AI Agents
Calendar API
Scheduling Engine
Google Calendar
Calendar Management
Tipuri de Întâlniri Automate
Sales Meetings
Întâlniri de vânzare cu clienți potențiali
Support Sessions
Sesiuni de suport tehnic și consultanță
Team Check-ins
Meeting-uri regulate de echipă și status
Follow-up Calls
Apeluri de follow-up și verificare status
Cazuri de Utilizare
Meeting Scheduling
Programează automat întâlniri din conversațiile cu agenții AI.
VAI Agent → Schedule Request → Google Calendar → Event Creation
↓ ↓ ↓
Time Analysis Availability Calendar Update
Follow-up Reminders
Generează reminder-e automate pentru follow-up-uri și sarcini.
VAI Agent → Task Analysis → Calendar Event → Reminder Set
↓ ↓ ↓
Priority Check Due Date Notification
Availability Management
Gestionează disponibilitatea pentru programări și consultanțe.
VAI Agent → Availability Check → Calendar API → Slot Booking
↓ ↓ ↓
Free Time Calendar Query Event Creation
Exemple de Implementare
Google Calendar API Integration
// Google Calendar API Integration
const { google } = require('googleapis');
const { OAuth2Client } = require('google-auth-library');
class GoogleCalendarIntegration {
constructor() {
this.auth = new OAuth2Client(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URI
);
this.calendar = google.calendar({ version: 'v3', auth: this.auth });
}
async authenticateUser(userToken) {
try {
// Verify and decode user token
const ticket = await this.auth.verifyIdToken({
idToken: userToken,
audience: process.env.GOOGLE_CLIENT_ID
});
const payload = ticket.getPayload();
const userId = payload['sub'];
// Get access token for calendar operations
const { tokens } = await this.auth.refreshToken();
return {
userId,
accessToken: tokens.access_token,
email: payload.email
};
} catch (error) {
console.error('Authentication error:', error);
throw new Error('Failed to authenticate with Google');
}
}
async scheduleMeetingFromVAI(meetingData, userCredentials) {
const {
title,
description,
duration,
participants,
preferredTime,
meetingType,
conversationId
} = meetingData;
try {
// Set authenticated user
this.auth.setCredentials(userCredentials);
// Find available time slots
const availableSlots = await this.findAvailableSlots(
participants,
preferredTime,
duration
);
if (availableSlots.length === 0) {
return {
success: false,
message: 'Nu am găsit intervale orare disponibile.',
suggestions: await this.suggestAlternativeTimes(participants, preferredTime, duration)
};
}
// Create calendar event
const event = {
summary: title,
description: this.generateEventDescription(description, conversationId, meetingType),
start: {
dateTime: availableSlots[0].start,
timeZone: 'Europe/Bucharest'
},
end: {
dateTime: availableSlots[0].end,
timeZone: 'Europe/Bucharest'
},
attendees: participants.map(participant => ({
email: participant.email,
displayName: participant.name
})),
conferenceData: {
createRequest: {
requestId: `vai-${conversationId}-${Date.now()}`,
conferenceSolutionKey: {
type: 'hangoutsMeet'
}
}
},
reminders: {
useDefault: false,
overrides: [
{ method: 'email', minutes: 24 * 60 }, // 1 day before
{ method: 'popup', minutes: 30 }, // 30 minutes before
{ method: 'popup', minutes: 10 } // 10 minutes before
]
},
extendedProperties: {
private: {
vai_conversation_id: conversationId,
vai_meeting_type: meetingType,
vai_agent_type: meetingData.agentType,
vai_created_by: 'ai_agent'
}
}
};
// Insert event
const response = await this.calendar.events.insert({
calendarId: 'primary',
resource: event,
conferenceDataVersion: 1,
sendUpdates: 'all'
});
// Create follow-up reminders
await this.createFollowUpReminders(response.data, meetingData);
// Log to VAI system
await this.logMeetingCreation(response.data, meetingData);
return {
success: true,
event: {
id: response.data.id,
title: response.data.summary,
start: response.data.start.dateTime,
end: response.data.end.dateTime,
meetUrl: response.data.hangoutLink,
attendees: participants
},
message: 'Întâlnirea a fost programată cu succes!'
};
} catch (error) {
console.error('Error scheduling meeting:', error);
return {
success: false,
message: 'A apărut o eroare la programarea întâlnirii.',
error: error.message
};
}
}
async findAvailableSlots(participants, preferredTime, duration) {
const timeMin = new Date(preferredTime);
const timeMax = new Date(preferredTime.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 days ahead
try {
// Get availability for all participants
const availabilityPromises = participants.map(participant =>
this.getUserAvailability(participant.email, timeMin, timeMax)
);
const availabilityResults = await Promise.all(availabilityPromises);
// Find common available slots
const commonSlots = this.findCommonAvailableSlots(
availabilityResults,
timeMin,
timeMax,
duration
);
return commonSlots;
} catch (error) {
console.error('Error finding available slots:', error);
return [];
}
}
async getUserAvailability(email, timeMin, timeMax) {
try {
const response = await this.calendar.freebusy.query({
requestBody: {
timeMin: timeMin.toISOString(),
timeMax: timeMax.toISOString(),
items: [{ id: email }]
}
});
return response.data.calendars[email];
} catch (error) {
console.error(`Error getting availability for ${email}:`, error);
return { busy: [] };
}
}
findCommonAvailableSlots(availabilityResults, timeMin, timeMax, duration) {
const slots = [];
const interval = 30 * 60 * 1000; // 30 minutes intervals
const workDayStart = 9 * 60 * 60 * 1000; // 9 AM
const workDayEnd = 17 * 60 * 60 * 1000; // 5 PM
for (let time = timeMin.getTime(); time < timeMax.getTime(); time += interval) {
const slotStart = new Date(time);
const slotEnd = new Date(time + duration * 60 * 1000);
// Check if slot is within work hours
const slotStartHour = slotStart.getHours() * 60 * 60 * 1000 + slotStart.getMinutes() * 60 * 1000;
const slotEndHour = slotEnd.getHours() * 60 * 60 * 1000 + slotEnd.getMinutes() * 60 * 1000;
if (slotStartHour < workDayStart || slotEndHour > workDayEnd) {
continue;
}
// Check if all participants are available
const allAvailable = availabilityResults.every(result => {
if (!result || !result.busy) return true;
return !result.busy.some(busySlot => {
const busyStart = new Date(busySlot.start);
const busyEnd = new Date(busySlot.end);
return (slotStart < busyEnd && slotEnd > busyStart);
});
});
if (allAvailable) {
slots.push({
start: slotStart.toISOString(),
end: slotEnd.toISOString()
});
}
}
return slots;
}
async suggestAlternativeTimes(participants, preferredTime, duration) {
const alternatives = [];
const daysAhead = 7;
for (let day = 1; day <= daysAhead; day++) {
const alternativeDate = new Date(preferredTime);
alternativeDate.setDate(alternativeDate.getDate() + day);
alternativeDate.setHours(9, 0, 0, 0);
const slots = await this.findAvailableSlots(participants, alternativeDate, duration);
if (slots.length > 0) {
alternatives.push({
date: alternativeDate.toLocaleDateString('ro-RO'),
slots: slots.slice(0, 3)
});
}
}
return alternatives;
}
generateEventDescription(description, conversationId, meetingType) {
return `
<div>
<h3>Întâlnire generată de VAI Portal</h3>
<p><strong>ID Conversație:</strong> ${conversationId}</p>
<p><strong>Tip întâlnire:</strong> ${meetingType}</p>
${description ? `
<h4>Detalii:</h4>
<p>${description}</p>
` : ''}
<p><em>Această întâlnire a fost programată automat de un agent AI VAI.</em></p>
</div>
`;
}
async createFollowUpReminders(event, meetingData) {
const { followUpActions, conversationId } = meetingData;
if (!followUpActions || followUpActions.length === 0) {
return;
}
for (const action of followUpActions) {
const reminderEvent = {
summary: `Follow-up: ${action.title}`,
description: `
<div>
<h4>Follow-up automat VAI</h4>
<p><strong>Acțiune:</strong> ${action.title}</p>
<p><strong>Detalii:</strong> ${action.description}</p>
<p><strong>ID Conversație originală:</strong> ${conversationId}</p>
</div>
`,
start: {
dateTime: new Date(action.dueDate).toISOString(),
timeZone: 'Europe/Bucharest'
},
end: {
dateTime: new Date(
new Date(action.dueDate).getTime() + 30 * 60 * 1000
).toISOString(),
timeZone: 'Europe/Bucharest'
},
reminders: {
useDefault: false,
overrides: [
{ method: 'popup', minutes: 15 },
{ method: 'email', minutes: 60 }
]
},
extendedProperties: {
private: {
vai_conversation_id: conversationId,
vai_follow_up_type: action.type,
vai_created_by: 'ai_agent'
}
}
};
await this.calendar.events.insert({
calendarId: 'primary',
resource: reminderEvent
});
}
}
async logMeetingCreation(event, meetingData) {
await fetch(`${process.env.VAI_API_URL}/calendar/log`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.VAI_API_KEY}`
},
body: JSON.stringify({
event_id: event.id,
conversation_id: meetingData.conversationId,
user_id: meetingData.userId,
meeting_type: meetingData.meetingType,
participants: meetingData.participants,
timestamp: new Date().toISOString()
})
});
}
async updateMeetingStatus(eventId, status, notes = '') {
try {
const event = await this.calendar.events.get({
calendarId: 'primary',
eventId: eventId
});
const updatedEvent = {
...event.data,
summary: `[${status.toUpperCase()}] ${event.data.summary}`,
description: `${event.data.description}
<strong>Status Update:</strong> ${status}
${notes ? `<strong>Note:</strong> ${notes}` : ''}`
};
await this.calendar.events.update({
calendarId: 'primary',
eventId: eventId,
resource: updatedEvent
});
return { success: true };
} catch (error) {
console.error('Error updating meeting status:', error);
return { success: false, error: error.message };
}
}
async getUpcomingMeetings(userId, days = 7) {
try {
const timeMin = new Date().toISOString();
const timeMax = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
const response = await this.calendar.events.list({
calendarId: 'primary',
timeMin,
timeMax,
singleEvents: true,
orderBy: 'startTime',
privateExtendedProperty: ['vai_created_by=ai_agent']
});
return response.data.items.map(event => ({
id: event.id,
title: event.summary,
start: event.start.dateTime,
end: event.end.dateTime,
meetUrl: event.hangoutLink,
conversationId: event.extendedProperties?.private?.vai_conversation_id,
meetingType: event.extendedProperties?.private?.vai_meeting_type
}));
} catch (error) {
console.error('Error getting upcoming meetings:', error);
return [];
}
}
}
module.exports = GoogleCalendarIntegration;VAI Calendar Webhook Handler
// VAI Calendar Webhook Handler
const express = require('express');
const GoogleCalendarIntegration = require('./google-calendar-integration');
const app = express();
app.use(express.json());
const calendarIntegration = new GoogleCalendarIntegration();
// Webhook endpoint for calendar events
app.post('/webhooks/calendar', async (req, res) => {
try {
const { event, userCredentials, action } = req.body;
switch (action) {
case 'schedule':
const scheduleResult = await calendarIntegration.scheduleMeetingFromVAI(
event,
userCredentials
);
return res.json(scheduleResult);
case 'update_status':
const updateResult = await calendarIntegration.updateMeetingStatus(
event.eventId,
event.status,
event.notes
);
return res.json(updateResult);
case 'get_upcoming':
const upcomingMeetings = await calendarIntegration.getUpcomingMeetings(
userCredentials.userId,
event.days || 7
);
return res.json({ success: true, meetings: upcomingMeetings });
default:
return res.status(400).json({ error: 'Invalid action' });
}
} catch (error) {
console.error('Calendar webhook error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Sync calendar events with VAI
app.post('/webhooks/calendar/sync', async (req, res) => {
try {
const { userCredentials, syncRange } = req.body;
const { startDate, endDate } = syncRange;
// Get all events in the specified range
const calendar = google.calendar({ version: 'v3', auth: calendarIntegration.auth });
calendarIntegration.auth.setCredentials(userCredentials);
const response = await calendar.events.list({
calendarId: 'primary',
timeMin: startDate,
timeMax: endDate,
singleEvents: true,
orderBy: 'startTime'
});
// Filter VAI-related events
const vaiEvents = response.data.items.filter(event =>
event.extendedProperties?.private?.vai_conversation_id
);
// Sync with VAI database
const syncPromises = vaiEvents.map(event =>
fetch(`${process.env.VAI_API_URL}/calendar/sync`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.VAI_API_KEY}`
},
body: JSON.stringify({
event_id: event.id,
conversation_id: event.extendedProperties.private.vai_conversation_id,
title: event.summary,
start: event.start.dateTime,
end: event.end.dateTime,
status: event.status,
updated: event.updated
})
})
);
await Promise.all(syncPromises);
res.json({
success: true,
synced_events: vaiEvents.length,
message: 'Calendar synchronized successfully'
});
} catch (error) {
console.error('Calendar sync error:', error);
res.status(500).json({ error: 'Sync failed' });
}
});
// Handle calendar notifications (webhooks from Google)
app.post('/webhooks/calendar/notifications', async (req, res) => {
try {
const notification = req.body;
// Verify notification is from Google
const channelId = notification.channelId;
const resourceState = notification.resourceState;
if (resourceState === 'exists') {
// Event was created or updated
const eventId = notification.resource.id.replace('https://www.googleapis.com/calendar/v3/calendars/primary/events/', '');
// Get event details
const calendar = google.calendar({ version: 'v3', auth: calendarIntegration.auth });
const event = await calendar.events.get({
calendarId: 'primary',
eventId: eventId
});
// Check if it's a VAI event
if (event.data.extendedProperties?.private?.vai_conversation_id) {
// Notify VAI system about the change
await fetch(`${process.env.VAI_API_URL}/calendar/notify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.VAI_API_KEY}`
},
body: JSON.stringify({
event_id: eventId,
conversation_id: event.data.extendedProperties.private.vai_conversation_id,
action: 'updated',
event_data: {
title: event.data.summary,
start: event.data.start.dateTime,
end: event.data.end.dateTime,
status: event.data.status
}
})
});
}
}
res.status(200).send();
} catch (error) {
console.error('Calendar notification error:', error);
res.status(500).send();
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Calendar webhook server running on port ${PORT}`);
});Ghid de Configurare
Creează Proiect Google Cloud
Generează un proiect nou în Google Cloud Console.
console.cloud.google.com → New ProjectActivează Calendar API
Activează Google Calendar API și generează credențiale OAuth 2.0.
Configurează OAuth Consent
Setează ecranul de consimțământ cu domeniile autorizate.
Integrează cu VAI
Configurează credențialele în VAI și testează integrarea.
Beneficii
Programare Inteligentă
Găsește automat cele mai bune intervale orare.
Sincronizare Perfectă
Integrare seamless cu calendarul existent.
Reminder-e Automate
Notificări inteligente pentru toate întâlnirile.
Management Global
Suport pentru fusuri orare multiple.
Pregătit pentru Google Calendar?
Integrează VAI Portal cu Google Calendar pentru a automatiza programarea și a-ți optimiza agenda inteligent.