[Part-2] Text to Action: Words to Calendar Events

Welcome back to the “Text to Action” series, where we build intelligent systems that transform natural language into real-world actionable outcomes using AI.

In Part 1, we established our foundation by creating an Express.js backend that connects to Google Calendar’s API. This gave us the ability to programmatically create calendar events through an exposed API endpoint.

Today, we’re adding the AI magic — enabling users to simply type natural language descriptions like “Schedule a team meeting tomorrow at 3 pm” and have our system intelligently transform such words into adding an actual calendar event action.

What We’re Building

We’re bridging the gap between natural human language and structured machine data. This ability to parse and transform natural language is at the heart of modern AI assistants and automation tools.

The end goal remains the same: create a system where users can type or say “create a party event at 5 pm on March 20” and instantly see it appear in their Google Calendar.

This tutorial will show you how to:

  1. Set up a local language model using Ollama
  2. Design an effective prompt for text-to-json conversion
  3. Build a natural language API endpoint
  4. Create a user-friendly interface for text input
  5. Handle date, time, and timezone complexities

The complete code is available on GitHub.

Prerequisites

Before starting, please make sure you have:

  • Completed Part 1 setup
  • Node.js and npm installed
  • Ollama installed locally
  • The llama3.2:latest model pulled via Ollama
# Install Ollama from https://ollama.com/
# Then pull the model:
ollama pull llama3.2:latest

Architecture Overview

Here’s how our complete system works:

  1. User enters natural language text through the UI or API call.
  2. System sends text to Ollama with a carefully designed prompt.
  3. Ollama extracts structured data (event details, time, date, etc.).
  4. System passes the structured data to Google Calendar API.
  5. Google Calendar creates the event.
  6. User receives confirmation with event details.

The magic happens in the middle steps, where we convert unstructured text to structured data that APIs can understand.

Step 1: Creating the NLP Service

First, let’s install the axios package for making HTTP requests to our local Ollama instance:

Now, create a new file nlpService.js to handle the natural language processing. Here are the key parts (full code available on GitHub):

const axios = require('axios');
// Main function to convert text to calendar event
const textToCalendarEvent = async (text, timezone) => {
  try {
    const ollamaEndpoint = process.env.OLLAMA_ENDPOINT || 'http://localhost:11434/api/generate';
    const ollamaModel = process.env.OLLAMA_MODEL || 'llama3.2:latest';
    
    const { data } = await axios.post(ollamaEndpoint, {
      model: ollamaModel,
      prompt: buildPrompt(text, timezone),
      stream: false
    });
    
    return parseResponse(data.response);
  } catch (error) {
    console.error('Error calling Ollama:', error.message);
    throw new Error('Failed to convert text to calendar event');
  }
};
// The core prompt engineering part
const buildPrompt = (text, timezone) => {
  // Get current date in user's timezone
  const today = new Date();
  const formattedDate = today.toISOString().split('T')[0]; // YYYY-MM-DD
  
  // Calculate tomorrow's date
  const tomorrow = new Date(today.getTime() + 24*60*60*1000);
  const tomorrowFormatted = tomorrow.toISOString().split('T')[0];
  
  // Get timezone information
  const tzString = timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
  
  return `
You are a system that converts natural language text into JSON for calendar events.
TODAY'S DATE IS: ${formattedDate}
USER'S TIMEZONE IS: ${tzString}
Given a text description of an event, extract the event information and return ONLY a valid JSON object with these fields:
- summary: The event title
- description: A brief description of the event
- startDateTime: ISO 8601 formatted start time
- endDateTime: ISO 8601 formatted end time
Rules:
- TODAY'S DATE IS ${formattedDate} - all relative dates must be calculated from this date
- Use the user's timezone for all datetime calculations
- "Tomorrow" means ${tomorrowFormatted}
- For dates without specified times, assume 9:00 AM
- If duration is not specified, assume 1 hour for meetings/calls and 2 hours for other events
- Include timezone information in the ISO timestamp
Examples:
Input: "Schedule a team meeting tomorrow at 2pm for 45 minutes"
Output:
{"summary":"Team Meeting","description":"Team Meeting","startDateTime":"${tomorrowFormatted}T14:00:00${getTimezoneOffset(tzString)}","endDateTime":"${tomorrowFormatted}T14:45:00${getTimezoneOffset(tzString)}"}
Now convert the following text to a calendar event JSON:
"${text}"
REMEMBER: RESPOND WITH RAW JSON ONLY. NO ADDITIONAL TEXT OR FORMATTING.
`;
};
// Helper functions for timezone handling and response parsing
// ... (See GitHub repository for full implementation)
module.exports = { textToCalendarEvent };

The key innovation here is our prompt design that:

  1. Anchors the model to today’s date
  2. Provides timezone awareness
  3. Gives clear rules for handling ambiguous cases
  4. Shows examples of desired output format

Step 2: Calendar Utility Function

Utility module for calendar operations. Here’s the simplified version (utils/calendarUtils.js):

const { google } = require('googleapis');

// Function to create a calendar event using the Google Calendar API
const createCalendarEvent = async ({ 
  auth, 
  calendarId = 'primary', 
  summary, 
  description, 
  startDateTime, 
  endDateTime 
}) => {
  const calendar = google.calendar({ version: 'v3', auth });
  
  const { data } = await calendar.events.insert({
    calendarId,
    resource: {
      summary,
      description: description || summary,
      start: { dateTime: startDateTime },
      end: { dateTime: endDateTime }
    }
  });
  
  return {
    success: true,
    eventId: data.id,
    eventLink: data.htmlLink
  };
};

module.exports = { createCalendarEvent };

Step 3: Updating the Express App

Now, let’s update our app.js file to include the new natural language endpoint:

// Import the new modules
const { textToCalendarEvent } = require('./nlpService');
const { createCalendarEvent } = require('./utils/calendarUtils');

// Add this new endpoint after the existing /api/create-event endpoint
app.post('/api/text-to-event', async (req, res) => {
  try {
    const { text } = req.body;
    
    if (!text) {
      return res.status(400).json({
        error: 'Missing required field: text'
      });
    }

    // Get user timezone from request headers or default to system timezone
    const timezone = req.get('X-Timezone') || Intl.DateTimeFormat().resolvedOptions().timeZone;

    // Convert the text to a structured event with timezone awareness
    const eventData = await textToCalendarEvent(text, timezone);
    const { summary, description = summary, startDateTime, endDateTime } = eventData;
    
    // Create the calendar event using the extracted data
    const result = await createCalendarEvent({
      auth: oauth2Client,
      summary,
      description,
      startDateTime,
      endDateTime
    });
    
    // Add the parsed data for reference
    res.status(201).json({
      ...result,
      eventData
    });
  } catch (error) {
    console.error('Error creating event from text:', error);
    res.status(error.code || 500).json({
      error: error.message || 'Failed to create event from text'
    });
  }
});

Step 4: Building the User Interface

We’ll create a dedicated HTML page for natural language input (public/text-to-event.html). Here’s a simplified version showing the main components:




  
  
  Text to Calendar Event
  


  

  
  

Simply describe your event in natural language, and we'll create it in your Google Calendar.

Step 2: Describe Your Event

Try these examples:

Schedule a team meeting tomorrow at 2pm for 45 minutes

Create a dentist appointment on April 15 from 10am to 11:30am

Set up a lunch with Sarah next Friday at noon

This site uses Akismet to reduce spam. Learn how your comment data is processed.