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:
@@ -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
1191
gateway/platforms/web.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user