mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
Dashboard UX polish pass — consolidates create forms into modals triggered from the page header, fixes layout inconsistencies, adds scroll-to navigation for the Keys page, and aligns the TokenBar with the design system. Changes: - App.tsx: add padding to sidebar header - resolve-page-title.ts: add missing routes, better fallback title - en.ts: fix nav labels (Profiles was 'profiles : multi agents') - ModelsPage: two-col layout, auxiliary tasks modal, TokenBar redesign - ProfilesPage: create button in header, form in modal, Checkbox component - CronPage: create button in header, form in modal - EnvPage: scroll-to sub-nav in header, fix text overflow Modal and dialog standardization: - Replace all native confirm()/window.confirm() with ConfirmDialog (OAuthProvidersCard, PluginsPage, ModelsPage, ConfigPage) - Add useModalBehavior hook (Escape-to-close, scroll lock, focus restore) - Apply hook to ProfilesPage, CronPage, AuxiliaryTasksModal Component fixes (from PR review): - Checkbox: fix controlled/uncontrolled mismatch, add focus-visible ring - TokenBar: add rounded-full to legend dots, remove dead code CI/test fixes: - Fix TS unused imports (noUnusedLocals), type-narrow PickerTarget union - Add windows-footgun suppression on platform-guarded os.killpg - Fix 19 stale unit tests + 9 e2e tests broken by recent main changes - Restore minimal example-dashboard plugin for plugin auth test
205 lines
7.4 KiB
Python
205 lines
7.4 KiB
Python
"""
|
|
Tests for cross-platform audio/voice media routing.
|
|
|
|
These tests pin the expected delivery path for audio media files across
|
|
Telegram (where Bot-API sendAudio only accepts MP3/M4A and .ogg/.opus
|
|
only renders as a voice bubble when explicitly flagged) and via
|
|
``GatewayRunner._deliver_media_from_response``.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from gateway.config import Platform, PlatformConfig
|
|
from gateway.platforms.base import BasePlatformAdapter, MessageEvent, MessageType, SendResult
|
|
from gateway.run import GatewayRunner
|
|
from gateway.session import SessionSource, build_session_key
|
|
|
|
|
|
class _MediaRoutingAdapter(BasePlatformAdapter):
|
|
def __init__(self):
|
|
super().__init__(PlatformConfig(enabled=True, token="test"), Platform.TELEGRAM)
|
|
|
|
async def connect(self):
|
|
return True
|
|
|
|
async def disconnect(self):
|
|
pass
|
|
|
|
async def send(self, chat_id, content=None, **kwargs):
|
|
return SendResult(success=True, message_id="text")
|
|
|
|
async def get_chat_info(self, chat_id):
|
|
return {"id": chat_id, "type": "dm"}
|
|
|
|
|
|
def _event(thread_id=None):
|
|
source = SessionSource(
|
|
platform=Platform.TELEGRAM,
|
|
chat_id="chat-1",
|
|
chat_type="dm",
|
|
thread_id=thread_id,
|
|
)
|
|
return MessageEvent(
|
|
text="make speech",
|
|
message_type=MessageType.TEXT,
|
|
source=source,
|
|
message_id="msg-1",
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_base_adapter_routes_telegram_flac_media_tag_to_document_sender():
|
|
adapter = _MediaRoutingAdapter()
|
|
event = _event()
|
|
adapter._message_handler = AsyncMock(return_value="MEDIA:/tmp/speech.flac")
|
|
adapter.send_voice = AsyncMock(return_value=SendResult(success=True, message_id="voice"))
|
|
adapter.send_document = AsyncMock(return_value=SendResult(success=True, message_id="doc"))
|
|
|
|
await adapter._process_message_background(event, build_session_key(event.source))
|
|
|
|
adapter.send_document.assert_awaited_once_with(
|
|
chat_id="chat-1",
|
|
file_path="/tmp/speech.flac",
|
|
metadata=None,
|
|
)
|
|
adapter.send_voice.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_base_adapter_routes_non_voice_telegram_ogg_media_tag_to_document_sender():
|
|
adapter = _MediaRoutingAdapter()
|
|
event = _event()
|
|
adapter._message_handler = AsyncMock(return_value="MEDIA:/tmp/speech.ogg")
|
|
adapter.send_voice = AsyncMock(return_value=SendResult(success=True, message_id="voice"))
|
|
adapter.send_document = AsyncMock(return_value=SendResult(success=True, message_id="doc"))
|
|
|
|
await adapter._process_message_background(event, build_session_key(event.source))
|
|
|
|
adapter.send_document.assert_awaited_once_with(
|
|
chat_id="chat-1",
|
|
file_path="/tmp/speech.ogg",
|
|
metadata=None,
|
|
)
|
|
adapter.send_voice.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_base_adapter_routes_voice_tagged_telegram_ogg_media_tag_to_voice_sender():
|
|
adapter = _MediaRoutingAdapter()
|
|
event = _event()
|
|
adapter._message_handler = AsyncMock(
|
|
return_value="[[audio_as_voice]]\nMEDIA:/tmp/speech.ogg"
|
|
)
|
|
adapter.send_voice = AsyncMock(return_value=SendResult(success=True, message_id="voice"))
|
|
adapter.send_document = AsyncMock(return_value=SendResult(success=True, message_id="doc"))
|
|
|
|
await adapter._process_message_background(event, build_session_key(event.source))
|
|
|
|
adapter.send_voice.assert_awaited_once_with(
|
|
chat_id="chat-1",
|
|
audio_path="/tmp/speech.ogg",
|
|
metadata=None,
|
|
)
|
|
adapter.send_document.assert_not_awaited()
|
|
|
|
|
|
def _fake_runner(thread_meta):
|
|
"""Build a fake GatewayRunner-like object with the helper methods needed by
|
|
_deliver_media_from_response."""
|
|
runner = SimpleNamespace(
|
|
_thread_metadata_for_source=lambda source, anchor=None: thread_meta,
|
|
_reply_anchor_for_event=lambda event: None,
|
|
)
|
|
return runner
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_streaming_delivery_routes_telegram_flac_media_tag_to_document_sender():
|
|
event = _event(thread_id="topic-1")
|
|
adapter = SimpleNamespace(
|
|
name="test",
|
|
extract_media=BasePlatformAdapter.extract_media,
|
|
extract_images=BasePlatformAdapter.extract_images,
|
|
extract_local_files=BasePlatformAdapter.extract_local_files,
|
|
send_voice=AsyncMock(return_value=SendResult(success=True, message_id="voice")),
|
|
send_document=AsyncMock(return_value=SendResult(success=True, message_id="doc")),
|
|
send_image_file=AsyncMock(return_value=SendResult(success=True, message_id="image")),
|
|
send_video=AsyncMock(return_value=SendResult(success=True, message_id="video")),
|
|
)
|
|
|
|
await GatewayRunner._deliver_media_from_response(
|
|
_fake_runner({"thread_id": "topic-1"}),
|
|
"MEDIA:/tmp/speech.flac",
|
|
event,
|
|
adapter,
|
|
)
|
|
|
|
adapter.send_document.assert_awaited_once_with(
|
|
chat_id="chat-1",
|
|
file_path="/tmp/speech.flac",
|
|
metadata={"thread_id": "topic-1"},
|
|
)
|
|
adapter.send_voice.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_streaming_delivery_routes_non_voice_telegram_ogg_media_tag_to_document_sender():
|
|
event = _event(thread_id="topic-1")
|
|
adapter = SimpleNamespace(
|
|
name="test",
|
|
extract_media=BasePlatformAdapter.extract_media,
|
|
extract_images=BasePlatformAdapter.extract_images,
|
|
extract_local_files=BasePlatformAdapter.extract_local_files,
|
|
send_voice=AsyncMock(return_value=SendResult(success=True, message_id="voice")),
|
|
send_document=AsyncMock(return_value=SendResult(success=True, message_id="doc")),
|
|
send_image_file=AsyncMock(return_value=SendResult(success=True, message_id="image")),
|
|
send_video=AsyncMock(return_value=SendResult(success=True, message_id="video")),
|
|
)
|
|
|
|
await GatewayRunner._deliver_media_from_response(
|
|
_fake_runner({"thread_id": "topic-1"}),
|
|
"MEDIA:/tmp/speech.ogg",
|
|
event,
|
|
adapter,
|
|
)
|
|
|
|
adapter.send_document.assert_awaited_once_with(
|
|
chat_id="chat-1",
|
|
file_path="/tmp/speech.ogg",
|
|
metadata={"thread_id": "topic-1"},
|
|
)
|
|
adapter.send_voice.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_streaming_delivery_routes_telegram_mp3_media_tag_to_voice_sender():
|
|
"""MP3 audio on Telegram must go through send_voice (which routes to
|
|
sendAudio internally); Telegram accepts MP3 for the audio player."""
|
|
event = _event(thread_id="topic-1")
|
|
adapter = SimpleNamespace(
|
|
name="test",
|
|
extract_media=BasePlatformAdapter.extract_media,
|
|
extract_images=BasePlatformAdapter.extract_images,
|
|
extract_local_files=BasePlatformAdapter.extract_local_files,
|
|
send_voice=AsyncMock(return_value=SendResult(success=True, message_id="voice")),
|
|
send_document=AsyncMock(return_value=SendResult(success=True, message_id="doc")),
|
|
send_image_file=AsyncMock(return_value=SendResult(success=True, message_id="image")),
|
|
send_video=AsyncMock(return_value=SendResult(success=True, message_id="video")),
|
|
)
|
|
|
|
await GatewayRunner._deliver_media_from_response(
|
|
_fake_runner({"thread_id": "topic-1"}),
|
|
"MEDIA:/tmp/speech.mp3",
|
|
event,
|
|
adapter,
|
|
)
|
|
|
|
adapter.send_voice.assert_awaited_once_with(
|
|
chat_id="chat-1",
|
|
audio_path="/tmp/speech.mp3",
|
|
metadata={"thread_id": "topic-1"},
|
|
)
|
|
adapter.send_document.assert_not_awaited()
|