{
  "id": "rxzOnW84b1nDQLyt",
  "name": "Smart Gap-Time Cafe Concierge with AI Recommendations",
  "tags": [],
  "nodes": [
    {
      "id": "78eee6b5-5f17-402f-accd-31401dd83b88",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1440,
        96
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 12
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "62acf1b5-049e-4935-9aac-34e3814102a0",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -1024,
        96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "currentLocation",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Your current location (e.g., 35.6812,139.7671 or address)__>"
            },
            {
              "id": "id-2",
              "name": "googleMapsApiKey",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Your Google Maps API Key__>"
            },
            {
              "id": "id-3",
              "name": "googlePlacesApiKey",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Your Google Places API Key__>"
            },
            {
              "id": "id-4",
              "name": "minimumGapMinutes",
              "type": "number",
              "value": 30
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "49fa712f-c2af-4d67-a05a-34e6f4e94897",
      "name": "Get Next Calendar Event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -544,
        96
      ],
      "parameters": {
        "limit": 1,
        "options": {
          "orderBy": "startTime",
          "recurringEventHandling": "expand"
        },
        "timeMin": "={{ $now.toISO() }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "user@example.com"
        },
        "operation": "getAll"
      },
      "typeVersion": 1.3
    },
    {
      "id": "ac36ecd0-e473-462e-9baf-463e6e8e130e",
      "name": "Get Travel Time (Google Maps API)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -80,
        96
      ],
      "parameters": {
        "url": "=https://maps.googleapis.com/maps/api/distancematrix/json?origins={{ $('Workflow Configuration').first().json.currentLocation }}&destinations={{ encodeURIComponent($json.location || $json.summary) }}&mode=transit&key={{ $('Workflow Configuration').first().json.googleMapsApiKey }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "5d5e1d96-5da9-41f8-8d6f-17749e46e56b",
      "name": "Calculate Gap Time",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        96
      ],
      "parameters": {
        "jsCode": "// Extract calendar event data from Get Next Calendar Event node\nconst calendarEvent = $('Get Next Calendar Event').first().json;\n\n// Extract travel time data from Google Maps API response\nconst travelData = $('Get Travel Time (Google Maps API)').first().json;\n\n// Get travel time in seconds from the API response\nconst travelTimeSeconds = travelData.rows?.[0]?.elements?.[0]?.duration?.value || 0;\n\n// Calculate travel time in minutes\nconst travelTimeMinutes = Math.ceil(travelTimeSeconds / 60);\n\n// Get event start time\nconst eventStartTime = calendarEvent.start?.dateTime || calendarEvent.start?.date;\n\n// Calculate time until event starts (in minutes)\nconst currentTime = new Date();\nconst eventStart = new Date(eventStartTime);\nconst timeUntilEventMinutes = Math.floor((eventStart - currentTime) / (1000 * 60));\n\n// Calculate gap time (time until event minus travel time)\nconst gapTimeMinutes = timeUntilEventMinutes - travelTimeMinutes;\n\n// Return the calculated data\nreturn [\n  {\n    json: {\n      gapTimeMinutes: gapTimeMinutes,\n      travelTimeMinutes: travelTimeMinutes,\n      timeUntilEventMinutes: timeUntilEventMinutes,\n      eventName: calendarEvent.summary || 'Untitled Event',\n      eventLocation: calendarEvent.location || 'No location specified',\n      eventStartTime: eventStartTime,\n      currentTime: currentTime.toISOString()\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "a5893f83-b729-457c-b7e5-8fa0012af49e",
      "name": "Check If Gap >= 30 Minutes",
      "type": "n8n-nodes-base.if",
      "position": [
        784,
        96
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.gapTimeMinutes }}",
              "rightValue": "={{ $('Workflow Configuration').first().json.minimumGapMinutes }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "360afc4b-0e8f-465c-8f90-717124296437",
      "name": "Get User Preferences",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1216,
        0
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1FNdOaKemFDqo5bOtkhSeHVQGCEuBwK6_7wKiUKk1Zzo/edit#gid=0",
          "cachedResultName": "\u30b7\u30fc\u30c81"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1FNdOaKemFDqo5bOtkhSeHVQGCEuBwK6_7wKiUKk1Zzo",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1FNdOaKemFDqo5bOtkhSeHVQGCEuBwK6_7wKiUKk1Zzo/edit?usp=drivesdk",
          "cachedResultName": "\u2615\ufe0f \u9699\u9593\u6642\u9593\u30b3\u30f3\u30b7\u30a7\u30eb\u30b8\u30e5 - Smart Gap-Time Concierge with AI & Google Sheets\u2028\ud83c\udfaf \u30b3\u30f3\u30bb\u30d7\u30c8"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "caeafb1c-fded-4cb4-bfc9-14ddd2be8a48",
      "name": "Search Nearby Cafes (Google Places API)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1616,
        0
      ],
      "parameters": {
        "url": "=https://maps.googleapis.com/maps/api/place/nearbysearch/json?location={{ $('Workflow Configuration').first().json.currentLocation }}&radius=1000&type=cafe&key={{ $('Workflow Configuration').first().json.googlePlacesApiKey }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "4c5b27fe-aa6f-409f-ac23-73309fb82549",
      "name": "Send Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        2448,
        0
      ],
      "parameters": {
        "text": "=\u2615\ufe0f *\u6b21\u306e\u4e88\u5b9a\u307e\u3067 {{ $('Calculate Gap Time').first().json.gapTimeMinutes }}\u5206\u306e\u7a7a\u304d\u6642\u9593\u304c\u3042\u308a\u307e\u3059\uff01*\n\n*\u304a\u3059\u3059\u3081\u30ab\u30d5\u30a7:* {{ $json.cafeName }}\n*\u8a55\u4fa1:* \u2b50\ufe0f {{ $json.rating }}\n\n{{ $json.reason }}\n\n\ud83d\udccd <{{ $json.mapsUrl }}|Google Maps\u3067\u958b\u304f>\n\n*\u6b21\u306e\u4e88\u5b9a:* {{ $('Calculate Gap Time').first().json.eventName }}\n*\u958b\u59cb\u6642\u523b:* {{ new Date($('Calculate Gap Time').first().json.eventStartTime).toLocaleString('ja-JP') }}\n*\u79fb\u52d5\u6642\u9593:* {{ $('Calculate Gap Time').first().json.travelTimeMinutes }}\u5206",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09TPHE5C15",
          "cachedResultName": "all-n8n\u30c6\u30b9\u30c8"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "f9c8f0cb-2bab-4c1f-9b58-21121243eab9",
      "name": "Send Urgent Move Alert (Slack)",
      "type": "n8n-nodes-base.slack",
      "position": [
        1616,
        176
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f *\u3059\u3050\u306b\u79fb\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\uff01*\n\n\u6b21\u306e\u4e88\u5b9a\u307e\u3067 {{ $('Calculate Gap Time').first().json.gapTimeMinutes }}\u5206\u3057\u304b\u3042\u308a\u307e\u305b\u3093\u3002\n\u79fb\u52d5\u6642\u9593: {{ $('Calculate Gap Time').first().json.travelTimeMinutes }}\u5206\n\n*\u6b21\u306e\u4e88\u5b9a:* {{ $('Calculate Gap Time').first().json.eventName }}\n*\u5834\u6240:* {{ $('Calculate Gap Time').first().json.eventLocation }}\n*\u958b\u59cb\u6642\u523b:* {{ new Date($('Calculate Gap Time').first().json.eventStartTime).toLocaleString('ja-JP') }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09TPHE5C15",
          "cachedResultName": "all-n8n\u30c6\u30b9\u30c8"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "4ca48f3b-4f90-4568-bfbc-b8f660f3d172",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1984,
        0
      ],
      "parameters": {
        "text": "=\u73fe\u5728\u306e\u7d14\u7c8b\u306a\u7a7a\u304d\u6642\u9593\u306f{{ $('Calculate Gap Time').first().json.gapTimeMinutes }}\u5206\u3067\u3059\u3002\n\n\u30e6\u30fc\u30b6\u30fc\u306e\u597d\u307f:\\n{{ JSON.stringify($('Get User Preferences').all(), null, 2) }}\\n\n\n\u5468\u8fba\u306e\u30ab\u30d5\u30a7\u5019\u88dc:\\n{{ JSON.stringify($('Search Nearby Cafes (Google Places API)').first().json.results?.slice(0, 10), null, 2) }}\\n\n\n\u4e0a\u8a18\u306e\u7a7a\u304d\u6642\u9593\u3001\u597d\u307f\u3001\u30ab\u30d5\u30a7\u5019\u88dc\u306e\u4e2d\u304b\u3089\u3001\u30e6\u30fc\u30b6\u30fc\u306b\u6700\u9069\u306a\u30ab\u30d5\u30a7\u30921\u3064\u9078\u3073\u3001\u79fb\u52d5\u6642\u9593\u3092\u8003\u616e\u3057\u305f\u4e0a\u3067\u3001\u304a\u3059\u3059\u3081\u7406\u7531\u3092\u4e00\u8a00\u6dfb\u3048\u3066JSON\u5f62\u5f0f\u3067\u63d0\u6848\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n**\u51fa\u529b\u30d5\u30a9\u30fc\u30de\u30c3\u30c8:**\n{\n  \"cafeName\": \"\u30ab\u30d5\u30a7\u540d\",\n  \"rating\": \"\u8a55\u4fa1\",\n  \"reason\": \"\u304a\u3059\u3059\u3081\u7406\u7531\uff08\u65e5\u672c\u8a9e\uff09\",\n  \"mapsUrl\": \"Google Maps URL\"\n}",
        "options": {
          "systemMessage": "=\"content\": \"\u3042\u306a\u305f\u306f\u512a\u79c0\u306a\u30ab\u30d5\u30a7\u30b3\u30f3\u30b7\u30a7\u30eb\u30b8\u30e5AI\u3067\u3059\u3002\u30e6\u30fc\u30b6\u30fc\u306e\u7a7a\u304d\u6642\u9593\u3068\u597d\u307f\u306b\u57fa\u3065\u3044\u3066\u6700\u9069\u306a\u30ab\u30d5\u30a7\u3092\u63d0\u6848\u3057\u3066\u304f\u3060\u3055\u3044\u3002\\n\\n**\u30bf\u30b9\u30af:**\\n1. \u30e6\u30fc\u30b6\u30fc\u306e\u597d\u307f\uff08Google Sheets Tool\u3067\u53d6\u5f97\u53ef\u80fd\uff09\u3092\u7406\u89e3\u3059\u308b\u3002\\n2. \u73fe\u5728\u5730\u5468\u8fba\u306e\u30ab\u30d5\u30a7\u60c5\u5831\uff08Google Places Tool\u3067\u53d6\u5f97\u53ef\u80fd\uff09\u3092\u53d6\u5f97\u3059\u308b\u3002\\n3. \u73fe\u5728\u306e\u7a7a\u304d\u6642\u9593: {{ $('Calculate Gap Time').first().json.gapTimeMinutes }}\u5206 \u3092\u8003\u616e\u306b\u5165\u308c\u308b\u3002\\n4. \u4e0a\u8a18\u306e\u60c5\u5831\u3068\u30e6\u30fc\u30b6\u30fc\u306e\u597d\u307f\u3092\u7167\u3089\u3057\u5408\u308f\u305b\u3001\u6700\u3082\u304a\u3059\u3059\u3081\u306e\u30ab\u30d5\u30a7\u30921\u3064\u9078\u3073\u3001\u305d\u306e\u7406\u7531\u3068Google Maps\u3078\u306e\u30ea\u30f3\u30af\u3092JSON\u5f62\u5f0f\u3067\u51fa\u529b\u3059\u308b\u3002\\n\\n**\u51fa\u529b\u30d5\u30a9\u30fc\u30de\u30c3\u30c8:**\\n{\\n  \\\"cafeName\\\": \\\"\u30ab\u30d5\u30a7\u540d\\\",\\n  \\\"rating\\\": \\\"\u8a55\u4fa1\\\",\\n  \\\"reason\\\": \\\"\u304a\u3059\u3059\u3081\u7406\u7531\uff08\u65e5\u672c\u8a9e\uff09\\\",\\n  \\\"mapsUrl\\\": \\\"Google Maps URL\\\"\\n}\""
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "25f7706b-01b6-44b1-ab27-72123441e352",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        1984,
        176
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "f4953b46-e3c0-4f82-9b0f-8fdd7b4a6127",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2032,
        -464
      ],
      "parameters": {
        "width": 416,
        "height": 688,
        "content": "This workflow acts as a Smart Gap-Time Cafe Concierge. It automatically checks your Google Calendar for upcoming events, calculates your available \"gap time\" before the next event, and then suggests a nearby cafe using Google Places API and AI recommendations. If there's not enough gap time, it sends an urgent alert to move. Finally, it notifies you via Slack with the cafe recommendation or the urgent alert.\n\n**Workflow Summary:**\n1. **Schedule Trigger:** Initiates the workflow daily at 12 PM.\n2. **Workflow Configuration:** Sets up essential variables like your current location, Google Maps/Places API keys, and minimum gap time.\n3. **Get Next Calendar Event:** Fetches your next upcoming event from Google Calendar.\n4. **Get Travel Time (Google Maps API):** Calculates transit time to the next event's location.\n5. **Calculate Gap Time:** Determines the actual free time you have before needing to leave for your next event.\n6. **Check If Gap >= 30 Minutes:** A conditional check to see if the gap time is sufficient for a cafe visit.\n7. **Get User Preferences:** Retrieves your cafe preferences from a Google Sheet.\n8. **Search Nearby Cafes (Google Places API):** Finds nearby cafes based on your current location.\n9. **AI Agent:** Uses an AI model (OpenRouter Chat Model) to recommend the best cafe based on your gap time, preferences, and nearby cafe options.\n10. **Send Slack Notification:** Sends a Slack message with the recommended cafe details.\n11. **Send Urgent Move Alert (Slack):** Sends an urgent Slack message if the gap time is too short."
      },
      "typeVersion": 1
    },
    {
      "id": "156d4021-c505-44fc-970f-14bb05c5a690",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1584,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 432,
        "content": "This node initiates the workflow at a scheduled time.\n\n**Configuration:**\n- The workflow is set to run daily at 12 PM (noon). You can adjust this schedule to fit your needs, for example, running it multiple times a day or at a different specific time."
      },
      "typeVersion": 1
    },
    {
      "id": "ac396cb1-15a8-4cf0-8dcc-de773606ff92",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1168,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 432,
        "content": "This node is crucial for setting up the core variables and API keys required for the workflow to function correctly.\n\n**Configuration:**\n- **currentLocation:** Replace `<__PLACEHOLDER_VALUE__Your current location (e.g., 35.6812,139.7671 or address)__>` with your actual current location (latitude, longitude, or a precise address). This is used for calculating travel times and searching for nearby places.\n- **googleMapsApiKey:** Replace `<__PLACEHOLDER_VALUE__Your Google Maps API Key__>` with your valid Google Maps API Key. This is necessary for the \"Get Travel Time\" node.\n- **googlePlacesApiKey:** Replace `<__PLACEHOLDER_VALUE__Your Google Places API Key__>` with your valid Google Places API Key. This is essential for the \"Search Nearby Cafes\" node.\n- **minimumGapMinutes:** Defines the minimum amount of free time (in minutes) required to consider suggesting a cafe. Currently set to 30 minutes."
      },
      "typeVersion": 1
    },
    {
      "id": "24fe91f4-3e01-48e4-82b9-a73d530c5c5b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 432,
        "content": "This node makes an HTTP request to the Google Maps Distance Matrix API to calculate the transit time from your `currentLocation` (defined in \"Workflow Configuration\") to the location of your next calendar event.\n\n**Configuration:**\n- **URL:** Dynamically constructed using your `currentLocation` and the event's location/summary, along with the `googleMapsApiKey`.\n- **Mode:** Set to `transit` to get public transport travel times.\n- **Important:** Ensure your Google Maps API Key has access to the Distance Matrix API."
      },
      "typeVersion": 1
    },
    {
      "id": "0655032e-22ad-4f87-99ce-5e77ee876d1f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -736,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 432,
        "content": "This node connects to your Google Calendar to fetch details of your very next upcoming event.\n\n**Configuration:**\n- **Calendar:** Ensure the correct Google Calendar account (`bumpbeck913@gmail.com` in this example) is selected.\n- **Limit:** Set to 1, to retrieve only the single closest upcoming event.\n- **Time Minimum:** Set to the current time, so it only looks for future events.\n- **Options:** Configured to order events by start time and expand recurring events."
      },
      "typeVersion": 1
    },
    {
      "id": "87a24962-f6d5-46a2-928c-d2bfcf781265",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 432,
        "content": "This Code node processes the data from the \"Get Next Calendar Event\" and \"Get Travel Time\" nodes to calculate your available gap time.\n\n**Functionality:**\n1. Extracts the next calendar event's start time and location.\n2. Retrieves the calculated travel time to the event.\n3. Calculates the time until the event starts from the current moment.\n4. Determines the 'gap time' by subtracting the travel time from the time until the event starts.\n5. Outputs useful variables like `gapTimeMinutes`, `travelTimeMinutes`, `timeUntilEventMinutes`, `eventName`, `eventLocation`, and `eventStartTime`."
      },
      "typeVersion": 1
    },
    {
      "id": "a7c208fc-a7fd-474b-906c-ea1a31bd7969",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 432,
        "content": "This If node acts as a conditional gate, determining the next step based on the calculated `gapTimeMinutes`.\n\n**Functionality:**\n- It checks if the `gapTimeMinutes` (from \"Calculate Gap Time\") is greater than or equal to the `minimumGapMinutes` (from \"Workflow Configuration\").\n- **If True:** The workflow proceeds to \"Get User Preferences\" to find cafe recommendations.\n- **If False:** The workflow proceeds to \"Send Urgent Move Alert (Slack)\", indicating insufficient time."
      },
      "typeVersion": 1
    },
    {
      "id": "807feb13-ec41-4c8a-aaba-3ae0f20aa41e",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 432,
        "content": "This node retrieves your personal cafe preferences from a Google Sheet.\n\n**Configuration:**\n- **Document ID:** Link to your specific Google Sheet (e.g., \"\u2615\ufe0f \u9699\u9593\u6642\u9593\u30b3\u30f3\u30b7\u30a7\u30eb\u30b8\u30e5 - Smart Gap-Time Concierge\").\n- **Sheet Name:** Specify the sheet within the document (e.g., \"\u30b7\u30fc\u30c81\").\n- **Purpose:** This information will be used by the AI Agent to tailor cafe recommendations to your liking (e.g., \"likes quiet places,\" \"prefers espresso,\" \"needs Wi-Fi\")."
      },
      "typeVersion": 1
    },
    {
      "id": "f401c66f-4af4-4ad8-8b43-4062394d69cd",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 432,
        "content": "This node queries the Google Places API to find cafes near your `currentLocation` (defined in \"Workflow Configuration\").\n\n**Configuration:**\n- **URL:** Dynamically constructed using your `currentLocation`, a search `radius` (1000 meters), `type` (cafe), and `googlePlacesApiKey`.\n- **Purpose:** It provides a list of potential cafe candidates to the AI Agent for selection.\n- **Important:** Ensure your Google Places API Key has access to the Nearby Search API."
      },
      "typeVersion": 1
    },
    {
      "id": "61e755d5-0e3c-4402-a342-140d8d0ec491",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1888,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 432,
        "content": "This node leverages an AI model to provide intelligent cafe recommendations.\n\n**Functionality:**\n- **Input:** It takes the `gapTimeMinutes`, your `user preferences` (from Google Sheets), and the `nearby cafe candidates` (from Google Places API).\n- **Prompt:** The prompt instructs the AI to act as a cafe concierge, evaluate the inputs, and select the single best cafe recommendation.\n- **Output Format:** The AI is asked to output the recommendation in a specific JSON format, including `cafeName`, `rating`, `reason` (in Japanese), and `mapsUrl`."
      },
      "typeVersion": 1
    },
    {
      "id": "1d5303e5-d1da-41fc-8f15-6ce6d3197cfe",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 416,
        "content": "This node defines the AI language model used by the \"AI Agent\" for generating cafe recommendations.\n\n**Configuration:**\n- This node connects to the OpenRouter API, which provides access to various large language models.\n- **Important:** Ensure your OpenRouter account credentials are set up correctly. The specific model used is configured within the \"AI Agent\" node's settings."
      },
      "typeVersion": 1
    },
    {
      "id": "5985b210-1dff-4aed-9dd0-f5c8f9d16c65",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2304,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 432,
        "content": "This node sends a Slack message with the recommended cafe details when there's sufficient gap time.\n\n**Configuration:**\n- **Channel ID:** **IMPORTANT:** Replace `C09TPHE5C15` with the actual ID of your Slack channel where you want to receive notifications (e.g., by selecting `#general` from the dropdown).\n- **Text:** The message is dynamically generated, including the gap time, recommended cafe name, rating, reason, Google Maps URL, next event details, and travel time.\n- **Purpose:** To inform you of the recommended cafe."
      },
      "typeVersion": 1
    },
    {
      "id": "aac5c5d8-57c7-44fe-9a40-be13c2310f0b",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 416,
        "content": "This node sends an urgent Slack message if the calculated gap time is too short to visit a cafe.\n\n**Configuration:**\n- **Channel ID:** **IMPORTANT:** Replace `C09TPHE5C15` with the actual ID of your Slack channel where you want to receive notifications.\n- **Text:** The message warns you to move immediately, stating the remaining time, travel time, and details of your next event.\n- **Purpose:** To ensure you don't miss your next appointment due to insufficient time."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "4c0f0dc9-fbf5-497b-8369-97670e3b78f0",
  "connections": {
    "AI Agent": {
      "main": [
        [
          {
            "node": "Send Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Gap Time": {
      "main": [
        [
          {
            "node": "Check If Gap >= 30 Minutes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User Preferences": {
      "main": [
        [
          {
            "node": "Search Nearby Cafes (Google Places API)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Get Next Calendar Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Next Calendar Event": {
      "main": [
        [
          {
            "node": "Get Travel Time (Google Maps API)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Gap >= 30 Minutes": {
      "main": [
        [
          {
            "node": "Get User Preferences",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Urgent Move Alert (Slack)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Travel Time (Google Maps API)": {
      "main": [
        [
          {
            "node": "Calculate Gap Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Nearby Cafes (Google Places API)": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}