Phase 10: Telegram bot commands + Phase 11: Snackbar notifications
All checks were successful
Validate / Hassfest (push) Successful in 3s

Phase 10 — Telegram Bot Commands:
- Add commands_config JSON field to TelegramBot model (enabled cmds,
  default count, response mode, rate limits, locale)
- Create command handler with 14 commands: /status, /albums, /events,
  /summary, /latest, /memory, /random, /search, /find, /person,
  /place, /favorites, /people, /help
- Add search_smart, search_metadata, search_by_person, get_random,
  download_asset, get_asset_thumbnail to ImmichClient
- Auto-register commands with Telegram setMyCommands API (EN+RU)
- Rate limiting per chat per command category
- Media mode: download thumbnails and send as photos to Telegram
- Webhook handler routes /commands before falling through to AI chat
- Frontend: expandable Commands section per bot with checkboxes,
  count/mode/locale settings, rate limit inputs, sync button

Phase 11 — Snackbar Notifications:
- Create snackbar store (snackbar.svelte.ts) with $state rune
- Create Snackbar component with fly/fade transitions, typed colors
- Mount globally in +layout.svelte
- Replace all alert() calls with typed snackbar notifications
- Add success snacks to all CRUD operations across all pages
- 4 types: success (3s), error (5s), info (3s), warning (4s)
- Max 3 visible, auto-dismiss, manual dismiss via X button

Both: Add ~30 i18n keys (EN+RU) for commands UI and snack messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 21:39:05 +03:00
parent ffce3ee337
commit e6ff0a423a
20 changed files with 1384 additions and 70 deletions

View File

@@ -9,6 +9,7 @@ import aiohttp
from immich_watcher_core.telegram.media import TELEGRAM_API_BASE_URL
from ..ai.commands import register_commands_with_telegram
from ..auth.dependencies import get_current_user
from ..database.engine import get_session
from ..database.models import TelegramBot, User
@@ -23,6 +24,7 @@ class BotCreate(BaseModel):
class BotUpdate(BaseModel):
name: str | None = None
commands_config: dict | None = None
@router.get("")
@@ -69,10 +71,12 @@ async def update_bot(
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
"""Update a bot's display name."""
"""Update a bot's display name and/or commands config."""
bot = await _get_user_bot(session, bot_id, user.id)
if body.name is not None:
bot.name = body.name
if body.commands_config is not None:
bot.commands_config = body.commands_config
session.add(bot)
await session.commit()
await session.refresh(bot)
@@ -121,6 +125,20 @@ async def list_bot_chats(
return chats
@router.post("/{bot_id}/sync-commands")
async def sync_commands(
bot_id: int,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
"""Register bot commands with Telegram BotFather API."""
bot = await _get_user_bot(session, bot_id, user.id)
success = await register_commands_with_telegram(bot)
if not success:
raise HTTPException(status_code=500, detail="Failed to register commands with Telegram")
return {"success": True}
# --- Helpers ---
async def _get_me(token: str) -> dict | None:
@@ -171,6 +189,7 @@ def _bot_response(b: TelegramBot) -> dict:
"bot_username": b.bot_username,
"bot_id": b.bot_id,
"token_preview": f"{b.token[:8]}...{b.token[-4:]}" if len(b.token) > 12 else "***",
"commands_config": b.commands_config,
"created_at": b.created_at.isoformat(),
}