From 1ba171a58d22962a8060088ef54847343c8e613d Mon Sep 17 00:00:00 2001 From: Quentin Fuxa Date: Fri, 29 Aug 2025 21:58:51 +0200 Subject: [PATCH] add embedded web interface HTML (single-file version with inline CSS/JS/SVG) ### Added - `get_inline_ui_html()`: generates a self-contained version of the web interface, with CSS, JS, and SVG assets inlined directly into the HTML. useful for environments where serving static files is inconvenient or when a single-call UI delivery is preferred. (cherry picked from commit aa44a92a6775bcc42b3518e78acd16eda275935a) --- whisperlivekit/__init__.py | 3 +- whisperlivekit/basic_server.py | 4 +- whisperlivekit/web/web_interface.py | 66 ++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/whisperlivekit/__init__.py b/whisperlivekit/__init__.py index 4547c3d..50fb50c 100644 --- a/whisperlivekit/__init__.py +++ b/whisperlivekit/__init__.py @@ -1,12 +1,13 @@ from .audio_processor import AudioProcessor from .core import TranscriptionEngine from .parse_args import parse_args -from .web.web_interface import get_web_interface_html +from .web.web_interface import get_web_interface_html, get_inline_ui_html __all__ = [ "TranscriptionEngine", "AudioProcessor", "parse_args", "get_web_interface_html", + "get_inline_ui_html", "download_simulstreaming_backend", ] diff --git a/whisperlivekit/basic_server.py b/whisperlivekit/basic_server.py index ba6181e..b66eefd 100644 --- a/whisperlivekit/basic_server.py +++ b/whisperlivekit/basic_server.py @@ -2,7 +2,7 @@ from contextlib import asynccontextmanager from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.responses import HTMLResponse from fastapi.middleware.cors import CORSMiddleware -from whisperlivekit import TranscriptionEngine, AudioProcessor, get_web_interface_html, parse_args +from whisperlivekit import TranscriptionEngine, AudioProcessor, get_inline_ui_html, parse_args import asyncio import logging from starlette.staticfiles import StaticFiles @@ -38,7 +38,7 @@ app.mount("/web", StaticFiles(directory=str(web_dir)), name="web") @app.get("/") async def get(): - return HTMLResponse(get_web_interface_html()) + return HTMLResponse(get_inline_ui_html()) async def handle_websocket_results(websocket, results_generator): diff --git a/whisperlivekit/web/web_interface.py b/whisperlivekit/web/web_interface.py index 0b0886e..44cfd4f 100644 --- a/whisperlivekit/web/web_interface.py +++ b/whisperlivekit/web/web_interface.py @@ -1,5 +1,6 @@ import logging import importlib.resources as resources +import base64 logger = logging.getLogger(__name__) @@ -12,6 +13,67 @@ def get_web_interface_html(): logger.error(f"Error loading web interface HTML: {e}") return "

Error loading interface

" +def get_inline_ui_html(): + """Returns the complete web interface HTML with all assets embedded in a single call.""" + try: + # Load HTML template + with resources.files('whisperlivekit.web').joinpath('live_transcription.html').open('r', encoding='utf-8') as f: + html_content = f.read() + + # Load CSS and embed it + with resources.files('whisperlivekit.web').joinpath('live_transcription.css').open('r', encoding='utf-8') as f: + css_content = f.read() + + # Load JS and embed it + with resources.files('whisperlivekit.web').joinpath('live_transcription.js').open('r', encoding='utf-8') as f: + js_content = f.read() + + # Load SVG files and convert to data URIs + with resources.files('whisperlivekit.web').joinpath('src', 'system_mode.svg').open('r', encoding='utf-8') as f: + system_svg = f.read() + system_data_uri = f"data:image/svg+xml;base64,{base64.b64encode(system_svg.encode('utf-8')).decode('utf-8')}" + + with resources.files('whisperlivekit.web').joinpath('src', 'light_mode.svg').open('r', encoding='utf-8') as f: + light_svg = f.read() + light_data_uri = f"data:image/svg+xml;base64,{base64.b64encode(light_svg.encode('utf-8')).decode('utf-8')}" + + with resources.files('whisperlivekit.web').joinpath('src', 'dark_mode.svg').open('r', encoding='utf-8') as f: + dark_svg = f.read() + dark_data_uri = f"data:image/svg+xml;base64,{base64.b64encode(dark_svg.encode('utf-8')).decode('utf-8')}" + + # Replace external references with embedded content + html_content = html_content.replace( + '', + f'' + ) + + html_content = html_content.replace( + '', + f'' + ) + + # Replace SVG references with data URIs + html_content = html_content.replace( + '', + f'' + ) + + html_content = html_content.replace( + '', + f'' + ) + + html_content = html_content.replace( + '', + f'' + ) + + return html_content + + except Exception as e: + logger.error(f"Error creating embedded web interface: {e}") + return "

Error loading embedded interface

" + if __name__ == '__main__': @@ -28,6 +90,6 @@ if __name__ == '__main__': @app.get("/") async def get(): - return HTMLResponse(get_web_interface_html()) + return HTMLResponse(get_inline_ui_html()) - uvicorn.run(app=app) \ No newline at end of file + uvicorn.run(app=app)