Files
LM-Gambit/app.py
T
Christopher Clendening adf61ae1a0 feat: add Settings and Suite pages with comprehensive settings management and question suite builder
- Implemented SettingsPage for configuring default provider, temperature, and model paths.
- Added SuitePage for managing a suite of questions with features to add, edit, duplicate, and delete questions.
- Introduced TypeScript configuration files for app and node environments.
- Set up Vite configuration for development server with API proxying to backend.
2026-07-27 17:41:08 -04:00

115 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""LM-Gambit desktop entrypoint.
Starts the FastAPI backend, serves the compiled React interface from the same
origin and opens a browser at it:
python app.py
For frontend development run the Vite dev server alongside it instead:
python app.py --no-browser # terminal 1
cd web && npm run dev # terminal 2 -> http://localhost:5173
"""
from __future__ import annotations
import argparse
import socket
import sys
import threading
import webbrowser
from pathlib import Path
ROOT_DIR = Path(__file__).resolve().parent
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
DEFAULT_PORT = 8765
WEB_DIST = ROOT_DIR / "web" / "dist"
def port_is_free(host: str, port: int) -> bool:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
probe.bind((host, port))
except OSError:
return False
return True
def find_port(host: str, preferred: int) -> int:
for candidate in range(preferred, preferred + 20):
if port_is_free(host, candidate):
return candidate
raise SystemExit(
f"No free port found between {preferred} and {preferred + 19}. "
"Pass --port to choose one explicitly."
)
def main() -> int:
parser = argparse.ArgumentParser(
prog="app.py",
description="Run the LM-Gambit web interface.",
)
parser.add_argument("--host", default="127.0.0.1", help="Interface to bind (default: 127.0.0.1)")
parser.add_argument(
"--port",
type=int,
default=DEFAULT_PORT,
help=f"Port to serve on (default: {DEFAULT_PORT}; the next free one is used if taken)",
)
parser.add_argument("--no-browser", action="store_true", help="Do not open a browser window")
parser.add_argument("--reload", action="store_true", help="Auto-reload on Python changes (dev)")
args = parser.parse_args()
try:
import uvicorn
except ImportError:
print(
"FastAPI/uvicorn are not installed.\n"
" python -m pip install -r requirements.txt",
file=sys.stderr,
)
return 1
port = args.port if port_is_free(args.host, args.port) else find_port(args.host, args.port)
url = f"http://{'localhost' if args.host in {'127.0.0.1', '0.0.0.0'} else args.host}:{port}"
if not (WEB_DIST / "index.html").is_file():
print(
"The web interface has not been built yet.\n"
" cd web && npm install && npm run build\n"
f"Starting the API anyway — docs at {url}/api/docs\n",
file=sys.stderr,
)
elif not args.no_browser:
threading.Timer(1.0, lambda: webbrowser.open(url)).start()
print(f"\n LM-Gambit -> {url}\n Press Ctrl+C to stop.\n")
uvicorn.run(
"server.main:app" if args.reload else _build_app(),
host=args.host,
port=port,
reload=args.reload,
log_level="info",
access_log=False,
)
return 0
def _build_app():
from server.main import create_app
return create_app()
if __name__ == "__main__":
try:
raise SystemExit(main())
except KeyboardInterrupt:
print("\nStopped.")