feat: locale-aware command templates, debounced auto-sync, entity pickers

- Locale-aware templates: CommandTemplateSlot now has a locale column,
  allowing each slot to have per-language variants (EN/RU). Templates
  are resolved at runtime from the Telegram user's language_code.

- Merged system configs: "Default Commands (EN)" and "(RU)" merged
  into a single "Default Commands" config with locale-aware slots.
  Migration handles existing data automatically.

- Configurable command descriptions: hardcoded COMMAND_DESCRIPTIONS
  replaced with desc_* template slots (desc_status, desc_help, etc.)
  that users can customize per locale. setMyCommands registers all
  locales explicitly.

- Removed locale from CommandConfig: no longer needed since locale
  is derived from the Telegram user's language at runtime.

- Debounced command auto-sync: after command config/tracker changes,
  affected bots are marked dirty and synced after a 30s debounce
  window. Manual "Sync with Telegram" button still works.

- Entity pickers in LinkedTargetsSection: replaced 6 plain <select>
  elements with EntitySelect components (search, icons, keyboard nav).
  Added onselect callback and size="sm" props to EntitySelect.
This commit is contained in:
2026-03-22 03:14:51 +03:00
parent 751097b347
commit 1167d138a3
47 changed files with 604 additions and 230 deletions
@@ -87,6 +87,11 @@ async def create_command_tracker(
session.add(tracker)
await session.commit()
await session.refresh(tracker)
# Mark affected bots dirty for debounced auto-sync
from ..services.command_sync import mark_dirty_for_tracker
await mark_dirty_for_tracker(tracker.id)
return await _tracker_response(session, tracker)
@@ -130,6 +135,11 @@ async def update_command_tracker(
session.add(tracker)
await session.commit()
await session.refresh(tracker)
# Mark affected bots dirty for debounced auto-sync
from ..services.command_sync import mark_dirty_for_tracker
await mark_dirty_for_tracker(tracker.id)
return await _tracker_response(session, tracker)
@@ -142,6 +152,10 @@ async def delete_command_tracker(
"""Delete a command tracker and cascade delete its listeners."""
tracker = await _get_user_tracker(session, tracker_id, user.id)
# Mark affected bots dirty before deleting (chain breaks after deletion)
from ..services.command_sync import mark_dirty_for_tracker
await mark_dirty_for_tracker(tracker.id)
# Delete associated listeners, collecting bot IDs for polling cleanup
result = await session.exec(
select(CommandTrackerListener).where(
@@ -177,6 +191,10 @@ async def enable_command_tracker(
await session.commit()
await session.refresh(tracker)
# Mark affected bots dirty for debounced auto-sync
from ..services.command_sync import mark_dirty_for_tracker
await mark_dirty_for_tracker(tracker.id)
# Start polling for any telegram bot listeners
lr = await session.exec(
select(CommandTrackerListener).where(
@@ -204,6 +222,10 @@ async def disable_command_tracker(
await session.commit()
await session.refresh(tracker)
# Mark affected bots dirty for debounced auto-sync
from ..services.command_sync import mark_dirty_for_tracker
await mark_dirty_for_tracker(tracker.id)
# Stop polling for any telegram bot listeners that are no longer needed
lr = await session.exec(
select(CommandTrackerListener).where(
@@ -286,6 +308,10 @@ async def add_listener(
from ..services.telegram_poller import start_bot_if_needed
await start_bot_if_needed(body.listener_id)
# Mark bot dirty for debounced auto-sync
from ..services.command_sync import mark_bot_dirty
mark_bot_dirty(body.listener_id)
return _listener_response(listener)
@@ -313,6 +339,10 @@ async def remove_listener(
from ..services.telegram_poller import stop_bot_if_unused
await stop_bot_if_unused(removed_id)
# Mark bot dirty for debounced auto-sync
from ..services.command_sync import mark_bot_dirty
mark_bot_dirty(removed_id)
# --- Helpers ---