Skip to content

Commit 68b8d4d

Browse files
committed
Add UI startup option
1 parent 6ec895e commit 68b8d4d

File tree

1 file changed

+76
-57
lines changed

1 file changed

+76
-57
lines changed

mlx_audio/server.py

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
import asyncio
1010
import io
1111
import os
12+
import subprocess
1213
import time
14+
import webbrowser
15+
from pathlib import Path
1316
from typing import Any, Dict, List, Optional
1417
from urllib.parse import unquote
1518

@@ -125,6 +128,13 @@ class SpeechRequest(BaseModel):
125128
model_provider = ModelProvider()
126129

127130

131+
@app.get("/")
132+
async def root():
133+
return {
134+
"message": "Welcome to the MLX Audio API server! Visit https://localhost:3000 for the UI."
135+
}
136+
137+
128138
@app.get("/v1/models")
129139
async def list_models():
130140
"""
@@ -235,63 +245,62 @@ async def stt_transcriptions(
235245
return result
236246

237247

238-
def main():
239-
parser = argparse.ArgumentParser(description="MLX Audio API server")
240-
parser.add_argument(
241-
"--allowed-origins",
242-
nargs="+",
243-
default=["*"],
244-
help="List of allowed origins for CORS",
245-
)
246-
parser.add_argument(
247-
"--host", type=str, default="0.0.0.0", help="Host to run the server on"
248-
)
249-
parser.add_argument(
250-
"--port", type=int, default=8000, help="Port to run the server on"
251-
)
252-
parser.add_argument(
253-
"--reload",
254-
type=bool,
255-
default=False,
256-
help="Enable auto-reload of the server. Only works when 'workers' is set to None.",
257-
)
258-
259-
parser.add_argument(
260-
"--workers",
261-
type=int_or_float,
262-
default=calculate_default_workers(),
263-
help="""Number of workers. Overrides the `MLX_AUDIO_NUM_WORKERS` env variable.
264-
Can be either an int or a float.
265-
If an int, it will be the number of workers to use.
266-
If a float, number of workers will be this fraction of the number of CPU cores available, with a minimum of 1.
267-
Defaults to the `MLX_AUDIO_NUM_WORKERS` env variable if set and to 2 if not.
268-
To use all available CPU cores, set it to 1.0.
269-
270-
Examples:
271-
--workers 1 (will use 1 worker)
272-
--workers 1.0 (will use all available CPU cores)
273-
--workers 0.5 (will use half the number of CPU cores available)
274-
--workers 0.0 (will use 1 worker)""",
275-
)
248+
class MLXAudioStudioServer:
249+
def __init__(self, start_ui=False, log_dir="logs"):
250+
self.start_ui = start_ui
251+
self.ui_process = None
252+
self.log_dir = Path(log_dir)
253+
self.log_dir.mkdir(exist_ok=True)
276254

277-
args = parser.parse_args()
278-
if isinstance(args.workers, float):
279-
args.workers = max(1, int(os.cpu_count() * args.workers))
255+
def start_ui_background(self):
256+
"""Start UI with logs redirected to file"""
257+
ui_path = Path(__file__).parent / "ui"
280258

281-
setup_cors(app, args.allowed_origins)
259+
# Install deps silently
260+
subprocess.run(
261+
["npm", "install"],
262+
cwd=str(ui_path),
263+
stdout=subprocess.DEVNULL,
264+
stderr=subprocess.DEVNULL,
265+
)
282266

283-
uvicorn.run(
284-
"mlx_audio.server:app",
285-
host=args.host,
286-
port=args.port,
287-
reload=args.reload,
288-
workers=args.workers,
289-
loop="asyncio",
290-
)
267+
# Start UI with logs to file
268+
ui_log = open(self.log_dir / "ui.log", "w")
269+
self.ui_process = subprocess.Popen(
270+
["npm", "run", "dev"],
271+
cwd=str(ui_path),
272+
stdout=ui_log,
273+
stderr=subprocess.STDOUT,
274+
)
275+
print(f"✓ UI started (logs: {self.log_dir}/ui.log)")
291276

277+
def start_server(self, host="localhost", port=8000, reload=False, workers=2):
278+
if self.start_ui:
279+
self.start_ui_background()
280+
time.sleep(2)
281+
webbrowser.open("http://localhost:3000")
282+
print(f"✓ API server starting on http://{host}:{port}")
283+
print(f"✓ Studio UI available at http://localhost:3000")
284+
print("\nPress Ctrl+C to stop both servers")
292285

293-
if __name__ == "__main__":
294-
main()
286+
try:
287+
uvicorn.run(
288+
"mlx_audio.server:app",
289+
host=host,
290+
port=port,
291+
reload=reload,
292+
workers=workers,
293+
loop="asyncio",
294+
)
295+
finally:
296+
if self.ui_process:
297+
self.ui_process.terminate()
298+
print("✓ UI server stopped")
299+
300+
ui_log_path = self.log_dir / "ui.log"
301+
if ui_log_path.exists():
302+
ui_log_path.unlink()
303+
print(f"✓ UI logs deleted from {ui_log_path}")
295304

296305

297306
def main():
@@ -303,7 +312,7 @@ def main():
303312
help="List of allowed origins for CORS",
304313
)
305314
parser.add_argument(
306-
"--host", type=str, default="0.0.0.0", help="Host to run the server on"
315+
"--host", type=str, default="localhost", help="Host to run the server on"
307316
)
308317
parser.add_argument(
309318
"--port", type=int, default=8000, help="Port to run the server on"
@@ -332,20 +341,30 @@ def main():
332341
--workers 0.5 (will use half the number of CPU cores available)
333342
--workers 0.0 (will use 1 worker)""",
334343
)
344+
parser.add_argument(
345+
"--start-ui",
346+
action="store_true",
347+
help="Start the Studio UI alongside the API server",
348+
)
349+
parser.add_argument(
350+
"--log-dir",
351+
type=str,
352+
default="logs",
353+
help="Directory to save server logs",
354+
)
335355

336356
args = parser.parse_args()
337357
if isinstance(args.workers, float):
338358
args.workers = max(1, int(os.cpu_count() * args.workers))
339359

340360
setup_cors(app, args.allowed_origins)
341361

342-
uvicorn.run(
343-
"mlx_audio.server:app",
362+
client = MLXAudioStudioServer(start_ui=args.start_ui, log_dir=args.log_dir)
363+
client.start_server(
344364
host=args.host,
345365
port=args.port,
346-
reload=args.reload,
366+
reload=args.reload if args.workers is None else False,
347367
workers=args.workers,
348-
loop="asyncio",
349368
)
350369

351370

0 commit comments

Comments
 (0)