feat: extend platform mock + add research docs

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
This commit is contained in:
Mikhail Putilovskij 2026-03-30 14:04:34 +03:00
parent 6f0e9a53a6
commit 67499daa61
7 changed files with 1515 additions and 29 deletions

View file

@ -0,0 +1,251 @@
# 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/` | Не знает разницы | Не знает разницы | Не знает разницы |