edit_square igindin

MeetCal: Пропасть между демо и продуктом

Я собрал AI-бота для планирования встреч в Телеграме за выходные. Потом восемь недель заставлял его работать по-настоящему.

Ilya Gindin
translate en

Демо работало идеально. Ни один реальный пользователь так и не воспользовался им так, как оно было задумано.

Зачем 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-токенов заставил думать о состоянии на длинных временных горизонтах. Таймзоны — о том как время вообще работает в распределённых системах.

В демо этому не научишься. Демо по определению чистое. Продакшен — это где система живёт на самом деле.

Собери демо быстро. Потом останься в пропасти достаточно долго, чтобы её понять.

← стрелки или свайп →