Building an Intelligent Google Calendar Assistant with @front10/generative-ui


Introduction

In today’s digital productivity landscape, efficient time management has become a critical factor for professional success. Calendars are fundamental tools, but their traditional use requires multiple clicks and complex navigation. What if we could interact with our calendars using natural language and receive dynamic interfaces that automatically adapt to our needs?

This article presents a complete implementation of a Google Calendar Virtual Assistant built with cutting-edge technologies: Next.js, Vercel AI SDK, and our @front10/generative-ui package. This demo not only showcases calendar management capabilities but also demonstrates how to create generative user experiences that can be applied to any similar use case: medical appointments, resource scheduling, reservation management, and more.

🎯 Objective and Use Cases

Primary Objective

Create an intelligent assistant that allows users to manage their Google Calendar through natural conversations, with dynamic interfaces that automatically render based on context and required actions.

Covered Use Cases

Event Management:

  • Create, modify, and delete events
  • View calendars in multiple views (month, week, day, list)
  • Manage multiple calendars simultaneously

Time Optimization:

  • Check real-time availability
  • Find optimal meeting times
  • Detect schedule conflicts

Extensible Use Cases:

  • Medical Appointments: Schedule and manage patient appointments
  • Restaurant Reservations: Table and availability management
  • Corporate Resources: Meeting room and equipment booking
  • Professional Services: Consultation and session scheduling

🚀 Assistant Features

Core Functionalities

🤖 Conversational Intelligence

  • Natural language processing for complex commands
  • Contextual conversation memory
  • Automatic calendar conflict resolution

📅 Advanced Calendar Management

  • Complete CRUD operations support (Create, Read, Update, Delete)
  • Multiple calendar management with different access levels
  • Automatic timezone detection

🎨 Adaptive Interfaces

  • Dynamic views that adjust based on requested time range
  • Interactive components with real-time feedback
  • Dark mode and responsive design

⚡ Intelligent Optimization

  • Availability verification using FreeBusy API
  • Optimal meeting time suggestions
  • Automatic conflict detection

🏗️ Technical Architecture

Technology Stack

Frontend:

  • Next.js 15 with App Router for server-side rendering
  • React 19 with Server Components for better performance
  • Tailwind CSS for utility styles and responsive design
  • shadcn/ui for consistent interface components

Backend and AI:

  • Vercel AI SDK 5.0 for language model integration
  • OpenAI GPT-4 as primary model (configurable for other providers)
  • Google Calendar API for complete calendar integration

Database:

  • PostgreSQL with Drizzle ORM for data management
  • NextAuth.js for OAuth authentication with Google
  • Redis for caching and session management

Key Package:

  • @front10/generative-ui for automatic component rendering

System Architecture

graph TB
    A[User] --> B[Chat Interface]
    B --> C[AI SDK]
    C --> D[LLM Provider]
    C --> E[Google Calendar Tools]
    E --> F[Google Calendar API]
    C --> G[@front10/generative-ui]
    G --> H[Component Registry]
    H --> I[Calendar Components]
    I --> J[User Actions]
    J --> C
    K[Database] --> L[User Sessions]
    K --> M[Chat History]

🧠 Assistant Core: Chat, Prompts, and Memory

Intelligent Chat System

The heart of the assistant is a chat system that combines the power of AI SDK with generative components. The implementation uses Vercel AI SDK’s useChat hook to handle conversation flow:

// components/chat.tsx
const {
  messages,
  setMessages,
  sendMessage,
  status,
  stop,
  regenerate,
  resumeStream
} = useChat<ChatMessage>({
  id,
  messages: initialMessages,
  experimental_throttle: 100,
  generateId: generateUUID,
  transport: new DefaultChatTransport({
    api: '/api/chat',
    fetch: fetchWithErrorHandlers,
    prepareSendMessagesRequest({ messages, id, body }) {
      const googleTokens = {
        accessToken: accessToken || directAccessToken || undefined,
        refreshToken: refreshToken || directRefreshToken || undefined
      };

      return {
        body: {
          id,
          message: messages.at(-1),
          selectedChatModel: initialChatModel,
          selectedVisibilityType: visibilityType,
          googleTokens,
          ...body
        }
      };
    }
  })
});

Specialized Prompt System

The assistant uses a highly specialized prompt system that includes:

Calendar Memory Management:

// lib/ai/prompts.ts
export const regularPrompt = `You are a specialized Google Calendar Assistant with powerful AI-driven capabilities! Your primary purpose is to help users manage their Google Calendar events efficiently and intuitively.

**Calendar ID Memory Management:**
- **CRITICAL**: You must always know which calendar the user is working with
- **Memory Resolution**: Check the conversation history to find the selected calendar ID
- **Auto-Selection**: If no calendar is selected, automatically call getGoogleCalendarList first
- **Context Awareness**: Always pass the correct calendarId to all tools based on conversation memory

**Event Modification & Deletion Protocol (CRITICAL):**
- **Modification Requests**: When user asks to modify/edit an event WITHOUT specifying what to change:
  - ALWAYS ask what specific details they want to modify (title, time, location, attendees, etc.)
  - NEVER assume what to modify
  - NEVER invent fields or changes
  - NEVER proceed with modifications without explicit user specification
`;

Available Tools:

// app/(chat)/api/chat/route.ts
const result = streamText({
  model: myProvider.languageModel(selectedChatModel),
  system: systemPrompt({ selectedChatModel, requestHints }),
  messages: convertToModelMessages(uiMessages),
  experimental_activeTools: [
    'getGoogleCalendarList',
    'getGoogleCalendarEvents',
    'createGoogleCalendarEvent',
    'updateGoogleCalendarEvent',
    'deleteGoogleCalendarEvent',
    'getGoogleCalendarFreeBusy'
  ],
  tools: {
    getGoogleCalendarList: getGoogleCalendarList(googleTokens),
    getGoogleCalendarEvents: getGoogleCalendarEvents(googleTokens),
    createGoogleCalendarEvent: createGoogleCalendarEvent(googleTokens),
    updateGoogleCalendarEvent: updateGoogleCalendarEvent(googleTokens),
    deleteGoogleCalendarEvent: deleteGoogleCalendarEvent(googleTokens),
    getGoogleCalendarFreeBusy: getGoogleCalendarFreeBusy(googleTokens)
  }
});

Contextual Memory System

The assistant maintains contextual memory through:

  1. Conversation History: Stored in PostgreSQL database
  2. Active Calendar State: Persisted during session
  3. Google Tokens: Securely managed with automatic refresh

🛠️ Google Calendar Tools

Tool Implementation

Each tool is implemented as a function using the AI SDK’s tool pattern:

// lib/ai/tools/google-calendar/get-events.ts
export const getGoogleCalendarEvents = (googleTokens?: {
  accessToken?: string;
  refreshToken?: string;
}) =>
  tool({
    description:
      'Get events from a Google Calendar. By default, shows events for the current month if no date range is specified.',
    inputSchema: GetGoogleCalendarEventsInputSchema,
    execute: async (
      input: GetGoogleCalendarEventsInput
    ): Promise<GetGoogleCalendarEventsOutput> => {
      try {
        if (!googleTokens?.accessToken) {
          throw new Error(
            'No Google access token available. Please connect your Google account first.'
          );
        }

        const client = createGoogleCalendarClient({
          accessToken: googleTokens.accessToken as string,
          refreshToken: googleTokens.refreshToken
        });

        const { events } = await client.getEvents(processedInput);

        return {
          events,
          summary: `Found ${events.length} events`
        };
      } catch (error) {
        throw new Error(`Failed to fetch calendar events: ${error.message}`);
      }
    }
  });

Google Calendar Client

// lib/ai/tools/google-calendar/client.ts
export class GoogleCalendarClient {
  private auth: OAuth2Client;

  constructor({
    accessToken,
    refreshToken
  }: {
    accessToken: string;
    refreshToken?: string;
  }) {
    this.auth = new OAuth2Client();
    this.auth.setCredentials({
      access_token: accessToken,
      refresh_token: refreshToken
    });
  }

  async getEvents(
    input: GetGoogleCalendarEventsInput
  ): Promise<{ events: GoogleCalendarEvent[] }> {
    const calendar = google.calendar({ version: 'v3', auth: this.auth });

    const response = await calendar.events.list({
      calendarId: input.calendarId || 'primary',
      timeMin: input.timeMin,
      timeMax: input.timeMax,
      maxResults: input.maxResults || 250,
      singleEvents: input.singleEvents ?? true,
      orderBy: input.orderBy || 'startTime',
      q: input.q
    });

    return {
      events: response.data.items?.map(this.transformEvent) || []
    };
  }
}

🎨 Integration with @front10/generative-ui

Generative UI Concept

@front10/generative-ui is a complete abstraction that automatically connects tool results with specific React components. When an AI model decides to use a tool, this abstraction automatically renders the corresponding component based on the tool’s state.

Provider and Component Registration

// package/front10-generative-ui/src/provider.tsx
export const GenerativeUIProvider: React.FC<{
  children: React.ReactNode;
}> = ({ children }) => {
  const [, setRegistry] = useState<GenerativeUIRegistryType>({});
  const registryRef = useRef<GenerativeUIRegistryType>({});

  const registerComponent = <TInput = any, TOutput = any>(
    component: GenerativeUIComponent<TInput, TOutput>
  ) => {
    const newRegistry = {
      ...registryRef.current,
      [component.toolId]: component
    };
    registryRef.current = newRegistry;
    setRegistry(newRegistry);
  };

  const renderComponent = (
    props: GenerativeUIRendererProps
  ): React.ReactNode | null => {
    const { toolId, state, input, output, error, toolCallId } = props;
    const component = registryRef.current[toolId];

    if (!component) {
      return React.createElement(
        'div',
        {
          key: toolCallId,
          className: 'p-4 border border-gray-200 rounded-lg bg-gray-50'
        },
        `Component not registered for tool: ${toolId}`
      );
    }

    switch (state) {
      case 'input-streaming':
        return component.LoadingComponent
          ? React.createElement(component.LoadingComponent, { input })
          : null;
      case 'result':
        return React.createElement(component.SuccessComponent, {
          output,
          input
        });
      case 'error':
        return component.ErrorComponent
          ? React.createElement(component.ErrorComponent, { error, input })
          : null;
      default:
        return null;
    }
  };
};

Calendar Component Registration

// components/chat.tsx
const { registerComponent } = useGenerativeUI();

// Register Google Calendar components
useEffect(() => {
  registerComponent({
    toolId: 'getGoogleCalendarEvents',
    LoadingComponent: GoogleCalendarLoading,
    SuccessComponent: GoogleCalendarComponent,
    ErrorComponent: GoogleCalendarError
  });

  registerComponent({
    toolId: 'getGoogleCalendarList',
    LoadingComponent: CalendarListLoading,
    SuccessComponent: CalendarListComponent,
    ErrorComponent: CalendarListError
  });

  registerComponent({
    toolId: 'getGoogleCalendarFreeBusy',
    LoadingComponent: FreeBusyLoading,
    SuccessComponent: FreeBusyComponent,
    ErrorComponent: FreeBusyError
  });
}, [registerComponent]);

Main Calendar Component

The GoogleCalendarComponent is a perfect example of how to create adaptive interfaces:

// components/google-calendar/google-calendar-component.tsx
export const GoogleCalendarComponent: React.FC<
  GoogleCalendarComponentProps
> = ({ output, input, onAction }) => {
  const { events, summary } = output;
  const [viewMode, setViewMode] = useState<CalendarViewType>(detectedView);

  // Detect appropriate view based on time range
  const detectedView = useMemo(() => {
    return detectTimeRange(input?.timeMin, input?.timeMax);
  }, [input?.timeMin, input?.timeMax]);

  // Handle view change
  const handleViewModeChange = (newViewMode: CalendarViewType) => {
    const timeRange = getTimeRangeFromView(newViewMode, currentDate);

    onAction?.({
      action: 'get_events_for_view',
      data: {
        calendarId: input?.calendarId,
        timeMin: timeRange.timeMin,
        timeMax: timeRange.timeMax,
        viewMode: newViewMode
      }
    });
  };

  return (
    <div className='bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 max-w-6xl'>
      {/* Adaptive view based on viewMode */}
      {viewMode === 'month' && <MonthView />}
      {viewMode === 'week' && <WeekView />}
      {viewMode === 'day' && <DayView />}
      {viewMode === 'list' && <ListView />}
    </div>
  );
};

Action System

Components can communicate back to the LLM through the action system:

// Example action from calendar component
const handleCreateEvent = () => {
  onAction?.({
    action: 'create_event',
    data: {
      calendarId: input?.calendarId,
      date: formatDateAsString(currentDate)
    }
  });
};

const handleEditEvent = (event: GoogleCalendarEvent) => {
  onAction?.({
    action: 'edit_event',
    data: {
      eventId: event.id,
      eventTitle: event.summary,
      currentData: event
    }
  });
};

📊 Specialized Components

1. GoogleCalendarComponent

Features:

  • Adaptive views (Month/Week/Day/List)
  • Intelligent temporal navigation
  • Interactive event management
  • Automatic optimal view detection

Component States:

export const GoogleCalendarLoading: React.FC = () => (
  <div className='animate-pulse bg-white dark:bg-gray-800 rounded-lg shadow-md p-6'>
    {/* Calendar skeleton */}
  </div>
);

export const GoogleCalendarError: React.FC = ({ error, onAction }) => (
  <div className='bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-lg p-6'>
    <h3>Error Loading Google Calendar</h3>
    <p>{error}</p>
    <button onClick={() => onAction?.({ action: 'retry_load_events' })}>
      Retry
    </button>
  </div>
);

2. CalendarListComponent

Functionalities:

  • Multiple calendar selection
  • Primary calendar indication
  • Access role visualization
  • Permission management

3. FreeBusyComponent

Advanced Features:

  • Real-time availability visualization
  • Optimal time slot suggestions
  • Conflict analysis
  • Direct event creation from free slots
// components/google-calendar/freebusy-component.tsx
export const FreeBusyComponent: React.FC<FreeBusyComponentProps> = ({
  output,
  input,
  onAction
}) => {
  const { calendars, summary, timeMin, timeMax } = output;

  // Generate time slots
  const timeSlots = useMemo(() => {
    return generateTimeSlots(timeMin, timeMax, 30); // 30-minute intervals
  }, [timeMin, timeMax]);

  // Calculate availability for each slot
  const availabilityData = useMemo(() => {
    return timeSlots.map(slot => {
      const calendarAvailability = calendars.map(calendar => ({
        calendarId: calendar.id,
        isFree: isTimeSlotFree(slot.start, slot.end, calendar.busy)
      }));

      const freeCalendars = calendarAvailability.filter(
        cal => cal.isFree
      ).length;
      const availabilityPercentage = (freeCalendars / calendars.length) * 100;

      return {
        ...slot,
        availabilityPercentage,
        freeCalendars,
        totalCalendars: calendars.length
      };
    });
  }, [timeSlots, calendars]);

  // Get best time slots
  const bestTimeSlots = useMemo(() => {
    return availabilityData
      .filter(slot => slot.availabilityPercentage >= 50)
      .sort((a, b) => b.availabilityPercentage - a.availabilityPercentage)
      .slice(0, 5);
  }, [availabilityData]);
};

🗄️ Database and Configuration

Database Schema

-- lib/db/migrations/0007_blue_white_tiger.sql
CREATE TABLE IF NOT EXISTS "User" (
  "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
  "email" varchar(64) NOT NULL,
  "password" varchar(64),
  "googleId" varchar(128),
  "googleAccessToken" text,
  "googleRefreshToken" text,
  "userType" varchar NOT NULL DEFAULT 'regular'
);

CREATE TABLE IF NOT EXISTS "Chat" (
  "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
  "createdAt" timestamp NOT NULL,
  "messages" json NOT NULL,
  "userId" uuid NOT NULL
);

Environment Variables Configuration

# .env
OPENAI_API_KEY=your_openai_api_key_here
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your_nextauth_secret_here
DATABASE_URL=postgresql://username:password@localhost:5432/database_name

Main Dependencies

{
  "dependencies": {
    "@ai-sdk/openai": "^2.0.10",
    "@ai-sdk/react": "2.0.0-beta.6",
    "ai": "5.0.0-beta.6",
    "next": "15.3.0-canary.31",
    "next-auth": "5.0.0-beta.25",
    "google-auth-library": "^10.3.0",
    "googleapis": "^159.0.0",
    "drizzle-orm": "^0.34.0",
    "postgres": "^3.4.4"
  }
}

🚀 Installation and Setup

Prerequisites

  • Node.js 18+ and pnpm
  • OpenAI API key
  • Google Cloud Project with Calendar API enabled
  • Google OAuth 2.0 credentials

Google Calendar API Setup

  1. Create a Google Cloud Project

  2. Enable Google Calendar API

    • Navigate to “APIs & Services” > “Library”
    • Search for “Google Calendar API” and enable it
  3. Create OAuth 2.0 Credentials

    • Go to “APIs & Services” > “Credentials”
    • Click “Create Credentials” > “OAuth 2.0 Client IDs”
    • Configure authorized redirect URIs:
      • Development: http://localhost:3000/api/auth/callback/google
      • Production: https://your-domain.com/api/auth/callback/google

Installation

# Clone the repository
git clone https://github.com/front10/google-calendar-assistant
cd google-calendar-assistant

# Install dependencies
pnpm install

# Configure environment variables
cp .env.example .env
# Edit .env with your configuration

# Run database migrations
pnpm db:migrate

# Start development server
pnpm dev

Development Commands

# Development server
pnpm dev

# Build for production
pnpm build

# Production server
pnpm start

# Linting
pnpm lint

# Type checking
pnpm type-check

# Database
pnpm db:studio  # Open Drizzle Studio
pnpm db:generate  # Generate migrations
pnpm db:push  # Apply changes to database

🧪 Test Cases and Examples

Basic Interactions

User: "Show me my events for today"
Assistant: [Executes getGoogleCalendarEvents] → [Renders GoogleCalendarComponent in day view]

User: "What meetings do I have this week?"
Assistant: [Executes getGoogleCalendarEvents with weekly range] → [Renders week view]

User: "Create a meeting for tomorrow at 2 PM"
Assistant: [Executes createGoogleCalendarEvent] → [Confirms creation and shows updated calendar]

Advanced Features

User: "Check my availability for next Tuesday"
Assistant: [Executes getGoogleCalendarFreeBusy] → [Renders FreeBusyComponent with availability analysis]

User: "Find the best time for a team meeting this week"
Assistant: [Analyzes multiple calendars] → [Suggests optimal times with high availability]

User: "Change view to week"
Assistant: [Component detects action] → [Sends action to LLM] → [Updates view automatically]

🌟 Advantages of the Generative UI Approach

1. Complete Automation

  • Components automatically render when tools are executed
  • No need for manual rendering logic
  • Loading, success, and error states handled automatically

2. Bidirectional Interactivity

  • Components can send actions back to the LLM
  • Continuous and natural conversation flow
  • Immediate user feedback

3. Scalability

  • Easy to add new tools and components
  • Consistent pattern for all use cases
  • Component reuse across different tools

4. Superior User Experience

  • Adaptive interfaces based on context
  • Smooth transitions between states
  • Responsive and accessible design

🔮 Future Applications

Extensible Use Cases

Medical Appointment System:

// Example extension for medical appointments
registerComponent({
  toolId: 'getMedicalAppointments',
  LoadingComponent: MedicalAppointmentLoading,
  SuccessComponent: MedicalAppointmentComponent,
  ErrorComponent: MedicalAppointmentError
});

Restaurant Reservation Management:

// Example for restaurant reservations
registerComponent({
  toolId: 'getRestaurantReservations',
  LoadingComponent: ReservationLoading,
  SuccessComponent: ReservationManagementComponent,
  ErrorComponent: ReservationError
});

Corporate Resource Scheduling:

// Example for corporate resources
registerComponent({
  toolId: 'getResourceAvailability',
  LoadingComponent: ResourceLoading,
  SuccessComponent: ResourceBookingComponent,
  ErrorComponent: ResourceError
});

📚 Package Documentation

For detailed information about @front10/generative-ui, including:

  • Complete API reference
  • Component registration examples
  • Action handling patterns
  • TypeScript interfaces
  • Best practices

Visit the package documentation.

🎯 Conclusion

This Google Calendar assistant demonstrates the power of combining conversational AI with generative interfaces. The implementation shows how:

  1. @front10/generative-ui simplifies creating dynamic experiences
  2. AI SDK provides a solid foundation for conversational applications
  3. Next.js and React offer the performance and flexibility needed
  4. Google Calendar API enables complete calendar integration

Call to Action

🚀 Try the Live Demo

Experience the Google Calendar Assistant in action: https://google-calendar-assistant.vercel.app/

This project is based on the Vercel template for AI chatbots and represents an excellent starting point for developing real virtual assistants.

Interested in contributing?

  • Give it a star if you like the project
  • 🍴 Fork the repository to create your own version
  • 🚀 Use it as a base for your next virtual assistant
  • 💬 Share your use cases and improvements with the community

The future of user interfaces lies in automatic generation of components based on context. This project gives you the tools to build that future.


Need help? Check the package documentation or open an issue on GitHub.

Let’s build the future of conversational interfaces together! 🚀