MeetCal: Пропасть между демо и продуктом
Я собрал AI-бота для планирования встреч в Телеграме за выходные. Потом восемь недель заставлял его работать по-настоящему.
Демо работало идеально. Ни один реальный пользователь так и не воспользовался им так, как оно было задумано.
Зачем MeetCal
У меня есть проблема. Когда договариваюсь о встрече в Телеграме, переключаюсь на Calendly, отправляю ссылку, жду пока человек заполнит форму, потом иду в Google Calendar проверить — пришло ли приглашение. Это три перехода из одного контекста в другой ради простейшего действия.
Calendly решает задачу, но его нет там, где уже идёт разговор. Телеграм — основной мессенджер для большей части русскоязычной аудитории. Миллиард активных пользователей, приложение открывают в среднем 21 раз в день. Встречу договариваешься там же.
Идея простая: бот + Mini App прямо в чате. Отправил ссылку — человек выбрал время — инвайт ушёл в Google Calendar. Без скачиваний, без регистрации, без выхода из мессенджера.
Первая попытка: демо за 48 часов
19 января запустил проект. Vite, React, Express, grammY для бота. Базовый Supabase. К концу первого дня Mini App открывался внутри Телеграма и показывал слоты времени.
К 20 января — таймзоны, ICS-файлы, анимации переходов. Всё это собиралось быстро, потому что стек простой и задача понятная. К 23 января подключил Google Calendar OAuth. К 25-му первая стабильная версия.
Демо работало. Ты нажимал кнопку, выбирал слот, получал приглашение в Google Calendar. “Оно живое” — это кайф, который знает каждый билдер. Ты смотришь на экран и думаешь: вот оно, готово.
Это была иллюзия.
Пропасть: где всё сломалось
Дальше захотел добавить AI-букинг. Текстом или голосом скажи боту “встреча с Олей завтра в 4” — и всё само. Казалось простым.
Первая версия: сырой fetch к OpenRouter, огромный системный промпт, модель возвращает JSON. Gemini Flash — быстрый, дешёвый, должен справиться.
Не справился.
“Встреча с Олей завтра в 4” — бот создавал событие на неправильное время. Иногда startAt: null. “Перенеси на 4 часа” — AI не мог надёжно понять: это в 16:00 или сдвинуть на 4 часа вперёд? Оба варианта семантически валидны. Модель угадывала — в моих тестах ошибалась примерно в половине случаев.
Голосовые сообщения добавили ещё слой. Whisper транскрибирует аудио, потом результат идёт в тот же парсер. Ошибки транскрипции накладываются на ошибки парсинга. “В четыре” превращается в “вчетыре” — и дата не извлекается вообще.
Таймзоны. Пользователь в Алматы говорит “в 10 утра”. Сервер в UTC. Google Calendar ждёт ISO 8601 с оффсетом. AI возвращал строку без зоны, код падал тихо, событие создавалось на 5 часов раньше. Никакой ошибки в интерфейсе — просто неправильное время в календаре.
OAuth. Google требует HTTPS на callback URL даже в разработке. Zoom OAuth — отдельный поток авторизации, отдельные scope, отдельные токены с рефрешем. Когда токен истекал ночью, крон-джоб напоминания падал без внятного лога.
Самый тупой баг: инлайн-бот показывал слоты из чужого аккаунта, если два человека одновременно делали запрос. Race condition в state, который не должен был быть shared. Нашёл через неделю после деплоя.
По данным Composio, 95% AI-пилотов не доходят до реального результата. Я теперь понимаю почему. Не потому что AI плохой. Потому что “работает в демо” и “работает у пользователей” — разные продукты.
Поворот: от парсинга строк к tool calling
В марте переехал на Vercel AI SDK с tool calling. Это изменило архитектуру мышления, а не только код.
Старый подход: дай AI всю задачу, получи JSON, распарси, обработай ошибки парсинга, попробуй снова.
Новый подход: AI решает только одно — какой инструмент вызвать и с какими аргументами. Каждый инструмент — отдельная функция с валидированной схемой. createEvent принимает title, startAt (обязательный ISO 8601 с таймзоной), durationMinutes. Схема проверяется до того как что-то запишется в базу.
“Встреча с Олей завтра в 4” теперь превращается в вызов createEvent с конкретными параметрами. Если AI передаёт невалидный startAt — ошибка валидации, не тихое падение. AI получает её обратно и может попросить уточнение.
Это не серебряная пуля. Но разделение “что делать” и “как делать” убрало целый класс багов. Вместо хрупкого JSON-парсинга — типизированный контракт.
Миграция заняла дольше, чем ожидал. Не потому что SDK сложный — а потому что пришлось переписать промпты. Старые были написаны под “верни мне JSON вот такой структуры”. Новые — под “вот что ты умеешь делать, реши что нужно пользователю”.
Что на самом деле делает продукт
AI-букинг — это примерно 20% продукта по объёму работы. Остальные 80% — “операционная система” вокруг ядра.
Платёжная система заняла две недели. Telegram Stars — 350 звёзд в месяц за подписку. Нужно обработать покупку, активацию, истечение, отмену, восстановление. Что если webhook не дошёл? Что если Stars вернули после рефанда?
Zoom интеграция — ещё неделя. OAuth-авторизация, хранение токенов, создание конференций через API, привязка ссылки к инвайту, рефреш токенов когда они протухают в три ночи.
Настройки нотификаций. Когда напоминать — за час, за день? Как — ботом в ТГ? Это кажется мелочью пока не реализуешь и не поймёшь что у каждого пользователя разные ожидания. Крон-джобы для напоминаний — надёжная очередь задач, которая не задублирует сообщение если крон запустится дважды, не потеряет задачу если сервер перезапустился, не отправит напоминание о встрече которую отменили.
Перенос встреч. Не просто создать новое событие, а отменить старое в Google Calendar, уведомить обе стороны, создать новое, обновить Zoom-ссылку. Четыре операции которые должны пройти вместе или откатиться.
OG-картинки для букинг-ссылок — динамический SVG рендерится в PNG на сервере, чтобы при вставке ссылки в чат была красивая превьюшка с именем и расписанием.
Инлайн-бот — @meetcal_bot в любом чате мгновенно показывает ссылку для букинга. Собрал за день, а это именно та фича, которая лучше всего передаёт идею “назначить встречу без переключения контекста”.
Лендинг с GSAP-анимациями собрал за день. А потом ещё неделю фиксил баги в логике отмены встреч. Соотношение характерное.
Где сейчас
MeetCal работает. @meetcal_bot в Телеграме — Mini App открывается, Google Calendar подключается, AI-букинг понимает контекст, Zoom-ссылки генерируются, напоминания приходят, перенос работает. Подписка через Stars.
Что бы сделал иначе: не стал бы строить AI-парсинг на сыром fetch с первого дня. Tool calling с нормальными схемами — это то с чего надо начинать, а не то к чему приходишь после месяца отладки.
Что удивило: Mini App экосистема в Телеграме менее зрелая чем кажется. Около половины пользователей взаимодействуют с Mini Apps, но большинство — крипта и игры. Утилитарные инструменты — незанятая ниша. Это и возможность, и объяснение почему хорошей документации на реальные edge cases почти нет.
Ещё удивило: самая сложная работа не про AI. Таймзоны, рефреш токенов, крон-джобы — вот где ушло больше всего времени на отладку. AI-часть, когда архитектура встала на место, оказалась самой предсказуемой.
Вывод
Демо — это доказательство концепции. Продукт — это всё что нужно чтобы незнакомый человек смог воспользоваться этой концепцией в 2 часа ночи когда тебя нет рядом.
Разрыв между этими двумя вещами — это и есть настоящее обучение. AI-ассистент ломающийся на “на 4 часа” заставил разобраться в том, что такое tool calling архитектурно. Баг с рефрешем OAuth-токенов заставил думать о состоянии на длинных временных горизонтах. Таймзоны — о том как время вообще работает в распределённых системах.
В демо этому не научишься. Демо по определению чистое. Продакшен — это где система живёт на самом деле.
Собери демо быстро. Потом останься в пропасти достаточно долго, чтобы её понять.