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
251 lines
10 KiB
Markdown
251 lines
10 KiB
Markdown
# 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 состояния
|
||
|
||
```python
|
||
from aiogram.fsm.state import State, StatesGroup
|
||
|
||
class UserStates(StatesGroup):
|
||
main_menu = State()
|
||
in_chat = State()
|
||
selecting_chat = State()
|
||
```
|
||
|
||
#### Хранение активного чата в StateData
|
||
|
||
```python
|
||
# При создании чата
|
||
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"]
|
||
```
|
||
|
||
#### Список чатов с инлайн-кнопками
|
||
|
||
```python
|
||
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"✅ Переключился в чат. Пишите...")
|
||
```
|
||
|
||
#### Схема БД
|
||
|
||
```sql
|
||
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 — делает хуже
|
||
|
||
```python
|
||
# Читать можно
|
||
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, не масштабируется.
|
||
|
||
```python
|
||
@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
|
||
|
||
### Суть
|
||
|
||
Кнопка в боте открывает мини-приложение (веб). Там красивый интерфейс со списком чатов, историей, поиском.
|
||
|
||
### Технические детали
|
||
|
||
```python
|
||
# В боте
|
||
from aiogram.types import WebAppInfo, InlineKeyboardButton
|
||
|
||
web_app_btn = InlineKeyboardButton(
|
||
text="📱 Открыть приложение",
|
||
web_app=WebAppInfo(url="https://lambda.example.com/app")
|
||
)
|
||
```
|
||
|
||
```javascript
|
||
// В 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/` | Не знает разницы | Не знает разницы | Не знает разницы |
|