Inapoi la Integrari
📅

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

REST API & OAuth 2.0

Tipuri de Întâlniri Automate

Sales Meetings

30-60 min

Întâlniri de vânzare cu clienți potențiali

Buffer: 15 min

Support Sessions

45-90 min

Sesiuni de suport tehnic și consultanță

Buffer: 10 min

Team Check-ins

30 min

Meeting-uri regulate de echipă și status

Buffer: 5 min

Follow-up Calls

15-30 min

Apeluri de follow-up și verificare status

Buffer: 5 min

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

1

Creează Proiect Google Cloud

Generează un proiect nou în Google Cloud Console.

console.cloud.google.com → New Project
2

Activează Calendar API

Activează Google Calendar API și generează credențiale OAuth 2.0.

3

Configurează OAuth Consent

Setează ecranul de consimțământ cu domeniile autorizate.

4

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.