feat: add web gateway — browser-based chat UI over WebSocket

New platform adapter that serves a full-featured chat interface via HTTP.
Enables access from any device on the network (phone, tablet, desktop).

Features:
- aiohttp server with WebSocket real-time messaging
- Token-based authentication
- Markdown rendering (marked.js) + code highlighting (highlight.js)
- Voice recording via MediaRecorder API + STT transcription
- Image, voice, and document display
- Typing indicator + message editing (streaming support)
- Mobile responsive dark theme
- Auto-reconnect on disconnect
- Media file cleanup (24h TTL)

Config: WEB_UI_ENABLED=true, WEB_UI_PORT=8765, WEB_UI_TOKEN=<token>
No new dependencies — uses aiohttp already in [messaging] extra.
This commit is contained in:
0xbyt4
2026-03-11 18:53:23 +03:00
parent e50323f730
commit a3905ef289
3 changed files with 1219 additions and 0 deletions

View File

@@ -31,6 +31,7 @@ class Platform(Enum):
SIGNAL = "signal"
HOMEASSISTANT = "homeassistant"
EMAIL = "email"
WEB = "web"
@dataclass
@@ -176,6 +177,9 @@ class GatewayConfig:
# Email uses extra dict for config (address + imap_host + smtp_host)
elif platform == Platform.EMAIL and config.extra.get("address"):
connected.append(platform)
# Web UI uses enabled flag only
elif platform == Platform.WEB:
connected.append(platform)
return connected
def get_home_channel(self, platform: Platform) -> Optional[HomeChannel]:
@@ -466,6 +470,18 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
name=os.getenv("EMAIL_HOME_ADDRESS_NAME", "Home"),
)
# Web UI
web_enabled = os.getenv("WEB_UI_ENABLED", "").lower() in ("true", "1", "yes")
if web_enabled:
if Platform.WEB not in config.platforms:
config.platforms[Platform.WEB] = PlatformConfig()
config.platforms[Platform.WEB].enabled = True
config.platforms[Platform.WEB].extra.update({
"port": int(os.getenv("WEB_UI_PORT", "8765")),
"host": os.getenv("WEB_UI_HOST", "0.0.0.0"),
"token": os.getenv("WEB_UI_TOKEN", ""),
})
# Session settings
idle_minutes = os.getenv("SESSION_IDLE_MINUTES")
if idle_minutes:

1191
gateway/platforms/web.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -829,6 +829,13 @@ class GatewayRunner:
return None
return EmailAdapter(config)
elif platform == Platform.WEB:
from gateway.platforms.web import WebAdapter, check_web_requirements
if not check_web_requirements():
logger.warning("Web: aiohttp not installed. Run: pip install aiohttp")
return None
return WebAdapter(config)
return None
def _is_user_authorized(self, source: SessionSource) -> bool:
@@ -848,6 +855,11 @@ class GatewayRunner:
if source.platform == Platform.HOMEASSISTANT:
return True
# Web UI users are authenticated via token at the WebSocket level.
# No additional allowlist check needed.
if source.platform == Platform.WEB:
return True
user_id = source.user_id
if not user_id:
return False