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:
parent
6f0e9a53a6
commit
67499daa61
7 changed files with 1515 additions and 29 deletions
251
docs/research/telegram-chat-alternatives.md
Normal file
251
docs/research/telegram-chat-alternatives.md
Normal 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/` | Не знает разницы | Не знает разницы | Не знает разницы |
|
||||
Loading…
Add table
Add a link
Reference in a new issue