platform/interface.py: - Add Attachment, MessageChunk, AgentEvent types - Add stream_message() to PlatformClient Protocol (door open for streaming) - Add WebhookReceiver Protocol platform/mock.py: - Add attachment_mode config (url/binary/s3) - Implement stream_message() — single chunk, ready for real streaming - Add register_webhook_receiver() + simulate_agent_event() for testing docs/research/: - telegram-forum-topics.md — aiogram 3.x Forum Topics API, FSM patterns, UX analysis - fsm-patterns.md — FSM storage options, StateData best practices - matrix-spaces.md — matrix-nio Space API, room ordering, invite flow - matrix-events.md — reactions, threads, typing, sync loop pitfalls - telegram-chat-alternatives.md — 7 alternatives for multi-chat UX, virtual chats in DM recommended
10 KiB
Research: Альтернативные варианты организации чатов в Telegram
Сравнительная таблица
| Вариант | Friction для юзера | Техническая сложность | Ограничения | Статус |
|---|---|---|---|---|
| 1. Виртуальные чаты в DM | Нет | Низкая | Нет | ✅ РЕКОМЕНДУЕТСЯ |
| 2. Threads / Reply Threads | Высокая | Средняя | Требует группу, бот не создаёт темы | ❌ |
| 3. Multiple Bot Instances | Высокая | Средняя | Нужно добавлять каждый | ❌ |
| 4. Inline Mode | Нет | Низкая | Stateless, не для диалогов | ❌ |
| 5. Бот создаёт приватные группы | Очень высокая | Очень высокая | Bot API не создаёт группы | ❌ |
| 6. Telegram Web App (TWA) | Нет | Высокая | Нужен web-сервер | ⚠️ Phase 2 |
| 7. Forum Topics (исходный) | Высокая | Средняя | Пользователь создаёт группу вручную | ⚠️ Оставить как опцию |
Вариант 1: Виртуальные чаты в DM (РЕКОМЕНДУЕТСЯ)
Как выглядит для пользователя
/start
→ [➕ Новый чат] [📋 Мои чаты] [⚙️ Настройки]
Нажимает "Новый чат"
→ "✅ Чат #1 создан! Начните писать..."
User: "Расскажи про Python"
→ "[Чат #1] Python — это язык программирования..."
Нажимает "Мои чаты"
→ 1️⃣ Чат #1 (Python)
2️⃣ Чат #2 (Математика) — 2 часа назад
3️⃣ Исследование рынка — вчера
Нажимает на "Чат #2"
→ "Вы в Чате #2. Последние сообщения: ..."
→ Продолжает разговор
Технические детали (aiogram 3.x)
FSM состояния
from aiogram.fsm.state import State, StatesGroup
class UserStates(StatesGroup):
main_menu = State()
in_chat = State()
selecting_chat = State()
Хранение активного чата в StateData
# При создании чата
await state.set_state(UserStates.in_chat)
await state.update_data(active_chat_id=chat_id)
# При получении сообщения
data = await state.get_data()
chat_id = data["active_chat_id"]
Список чатов с инлайн-кнопками
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
@router.message(Command("list"))
async def cmd_list(message: Message, state: FSMContext):
chats = db.get_user_chats(message.from_user.id)
buttons = [
[InlineKeyboardButton(
text=f"📄 {chat.title or f'Чат #{chat.id[:6]}'}",
callback_data=f"select:{chat.id}"
)]
for chat in chats
]
await message.answer("📋 Ваши чаты:", reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@router.callback_query(F.data.startswith("select:"))
async def switch_chat(callback: CallbackQuery, state: FSMContext):
chat_id = callback.data.split(":")[1]
await state.update_data(active_chat_id=chat_id)
await state.set_state(UserStates.in_chat)
await callback.message.edit_text(f"✅ Переключился в чат. Пишите...")
Схема БД
CREATE TABLE chats (
chat_id TEXT PRIMARY KEY, -- UUID
user_id INTEGER NOT NULL,
title TEXT,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
CREATE TABLE messages (
id INTEGER PRIMARY KEY,
chat_id TEXT NOT NULL,
user_id INTEGER NOT NULL,
direction TEXT, -- 'user' / 'bot'
content TEXT NOT NULL,
created_at TIMESTAMP,
FOREIGN KEY(chat_id) REFERENCES chats(chat_id)
);
Ограничения
- История только в нашей БД, не в Telegram нативно
- Нет шаринга чата с другими пользователями
Примеры в реальных ботах
- ChatGPT Telegram боты — именно этот паттерн,
/new→ новый разговор,/history→ список - StudyGPT — каждый урок = отдельный "чат" с контекстом
- Notion Bot — список "проектов" с переключением
Оценка
✅ Нулевое трение — ничего настраивать не нужно ✅ Работает в DM — приватно ✅ Стандартный паттерн — пользователи знакомы ✅ Полный контроль над UX ✅ Масштабируется без ограничений ❌ История только в нашей БД (но это нормально)
Вариант 2: Threads / Reply Topics — ОТКЛОНЕНО
Суть
Telegram поддерживает message_thread_id в апдейтах — можно читать из какого треда пришло сообщение и отвечать в него. Но:
- Бот не может создавать треды — Bot API этого не умеет
- Работает только в Group/Supergroup, не в DM
- Пользователь должен создавать каждый тред вручную
- Не решает проблему friction — делает хуже
# Читать можно
thread_id = message.message_thread_id # int или None
# Отвечать можно
await message.answer("ответ", message_thread_id=thread_id)
# Создать тред НЕЛЬЗЯ — метода нет в Bot API
Вариант 3: Deep Linking — ОТКЛОНЕНО
Каждый чат = отдельная ссылка вида https://t.me/bot?start=chatid_xyz. Пользователь переходит по ссылке, бот предвыбирает чат.
Проблема: пользователь не знает что такие ссылки существуют, нет discovery, не масштабируется.
@router.message(Command("start"))
async def cmd_start(message: Message):
args = message.text.split()
if len(args) > 1:
chat_id = args[1] # из deep link
# предвыбрать чат
Вариант 4: Inline Mode — ОТКЛОНЕНО
Inline mode (@botname query) — fundamentally stateless. Один запрос → несколько результатов → пользователь выбирает. Нет истории, нет контекста. Категорически не подходит для многооборотного диалога с AI.
Вариант 5: Бот создаёт приватные группы — ОТКЛОНЕНО
Telegram Bot API не умеет создавать группы. Единственный workaround — заранее создать 100+ пустых групп и назначать их пользователям. 1000 пользователей = 1000 лишних групп. Абсурд.
Вариант 6: Telegram Web App (TWA) — Phase 2
Суть
Кнопка в боте открывает мини-приложение (веб). Там красивый интерфейс со списком чатов, историей, поиском.
Технические детали
# В боте
from aiogram.types import WebAppInfo, InlineKeyboardButton
web_app_btn = InlineKeyboardButton(
text="📱 Открыть приложение",
web_app=WebAppInfo(url="https://lambda.example.com/app")
)
// В TWA (JavaScript)
const tg = window.Telegram.WebApp;
const user_id = tg.initDataUnsafe.user.id;
// Загрузить список чатов
const chats = await fetch('/api/chats', {
headers: { 'X-Telegram-Init-Data': tg.initData }
}).then(r => r.json());
Оценка
✅ Красивый интерфейс (история, поиск, форматирование) ✅ Мобильный-friendly ⚠️ Нужен HTTPS web-сервер ⚠️ Сложнее разрабатывать ⚠️ Усложняет деплой
Вывод: хороший вариант для v2, не для MVP.
Финальные рекомендации
Сейчас (MVP)
Вариант 1 — Виртуальные чаты в DM. Реализуется за 2-3 дня, нулевое friction, стандартный паттерн.
Phase 2
Вариант 6 — TWA как дополнительный UI поверх той же логики. Бэкенд не меняется — просто добавляется web-интерфейс.
Опционально (для пользователей которые хотят)
Forum Topics — оставить как opt-in возможность. Пользователь сам создаёт группу, добавляет бота — получает "продвинутый режим" с нативными Telegram темами.
Что это означает для архитектуры
ChatManager в core/chat.py должен работать одинаково для обоих режимов — и виртуальных чатов в DM, и Forum Topics. Разница только в адаптере:
| Telegram DM (virtual) | Telegram Forum | Matrix | |
|---|---|---|---|
chat_id |
UUID | message_thread_id |
room_id |
| Создание | В БД | create_forum_topic |
room_create |
| Отправка | send_message(chat_id=user_id) + tag |
send_message(thread_id=...) |
room_send |
core/ |
Не знает разницы | Не знает разницы | Не знает разницы |