Hermes Agent commited on
Commit
fca969e
Β·
1 Parent(s): f4a0e33

fix: resolve Open WebUI and OmniRoute subpath routing by injecting JS fetch interceptors and removing conflicting API endpoint overrides

Browse files
Files changed (1) hide show
  1. proxy.py +78 -6
proxy.py CHANGED
@@ -320,12 +320,43 @@ async def health_check():
320
 
321
 
322
  # ── 1. Open WebUI Route (/openwebui/) ────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  def _fixup_webui_html(html: str) -> str:
324
  html = html.replace('href="/', 'href="/openwebui/')
325
  html = html.replace("href='/", "href='/openwebui/")
326
  html = html.replace('src="/', 'src="/openwebui/')
327
  html = html.replace("src='/", "src='/openwebui/")
328
  html = html.replace('action="/', 'action="/openwebui/')
 
 
 
 
329
  return html
330
 
331
  @app.api_route("/openwebui", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
@@ -340,8 +371,41 @@ async def route_openwebui(request: Request, path: str = ""):
340
  async def route_openwebui_ws(websocket: WebSocket):
341
  await proxy_websocket_stream(websocket, f"ws://127.0.0.1:{WEBUI_PORT}/ws")
342
 
 
 
 
 
 
 
343
 
344
  # ── 2. OmniRoute AI Gateway Route (/omniroute/) ──────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  def _fixup_omniroute_html(html: str) -> str:
346
  html = html.replace('href="/', 'href="/omniroute/')
347
  html = html.replace("href='/", "href='/omniroute/")
@@ -352,6 +416,10 @@ def _fixup_omniroute_html(html: str) -> str:
352
  html = html.replace("'/_next/", "'/omniroute/_next/")
353
  html = html.replace('"basePath":""', '"basePath":"/omniroute"')
354
  html = html.replace('"basePath": ""', '"basePath": "/omniroute"')
 
 
 
 
355
  return html
356
 
357
  @app.api_route("/omniroute", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
@@ -360,6 +428,12 @@ async def route_omniroute(request: Request, path: str = ""):
360
  target = f"http://127.0.0.1:{OMNIROUTE_PORT}/{path}"
361
  return await proxy_http_request(target, request, html_fixup=_fixup_omniroute_html)
362
 
 
 
 
 
 
 
363
 
364
  # ── 3. OpenCode CLI Server Route (/server/) ──────────────────────────────────
365
  def _fixup_opencode_html(html: str) -> str:
@@ -383,8 +457,8 @@ async def route_opencode_ws(websocket: WebSocket):
383
  await proxy_websocket_stream(websocket, f"ws://127.0.0.1:{OPENCODE_PORT}/ws")
384
 
385
 
386
- # ── Direct OpenCode Endpoints (session, project, config, etc.) ───────────────
387
- OPENCODE_DIRECT_PATHS = ["session", "project", "config", "permission", "question", "file", "find", "events", "event", "command", "provider", "model", "mcp", "api"]
388
 
389
  def _create_direct_route(prefix: str):
390
  @app.api_route(f"/{prefix}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
@@ -421,14 +495,12 @@ async def route_tg_stream(request: Request, path: str = ""):
421
  async def route_catch_all(path: str, request: Request):
422
  referer = request.headers.get("referer", "").lower()
423
 
424
- if "/openwebui" in referer:
425
- return await proxy_http_request(f"http://127.0.0.1:{WEBUI_PORT}/{path}", request, html_fixup=_fixup_webui_html)
426
- elif "/omniroute" in referer or "/dashboard" in referer or "/login" in referer:
427
  return await proxy_http_request(f"http://127.0.0.1:{OMNIROUTE_PORT}/{path}", request, html_fixup=_fixup_omniroute_html)
428
  elif "/server" in referer:
429
  return await proxy_http_request(f"http://127.0.0.1:{OPENCODE_PORT}/{path}", request, html_fixup=_fixup_opencode_html)
430
  elif "/jellyfin" in referer:
431
  return await proxy_http_request(f"http://127.0.0.1:{JELLYFIN_PORT}/{path}", request, extra_headers={"X-Forwarded-Prefix": "/jellyfin"})
432
 
433
- # Fallback default: Open WebUI
434
  return await proxy_http_request(f"http://127.0.0.1:{WEBUI_PORT}/{path}", request, html_fixup=_fixup_webui_html)
 
320
 
321
 
322
  # ── 1. Open WebUI Route (/openwebui/) ────────────────────────────────────────
323
+ WEBUI_JS_PATCH = """<script>
324
+ (function() {
325
+ if (window.__WEBUI_PATCHED__) return;
326
+ window.__WEBUI_PATCHED__ = true;
327
+ var origFetch = window.fetch;
328
+ window.fetch = function(resource, init) {
329
+ if (typeof resource === 'string') {
330
+ if (resource.startsWith('/') && !resource.startsWith('/openwebui')) {
331
+ resource = '/openwebui' + resource;
332
+ }
333
+ } else if (resource && resource.url && typeof resource.url === 'string') {
334
+ if (resource.url.startsWith('/') && !resource.url.startsWith('/openwebui')) {
335
+ resource = new Request('/openwebui' + resource.url, resource);
336
+ }
337
+ }
338
+ return origFetch.call(this, resource, init);
339
+ };
340
+ var origOpen = XMLHttpRequest.prototype.open;
341
+ XMLHttpRequest.prototype.open = function(method, url) {
342
+ if (typeof url === 'string' && url.startsWith('/') && !url.startsWith('/openwebui')) {
343
+ url = '/openwebui' + url;
344
+ }
345
+ return origOpen.apply(this, arguments);
346
+ };
347
+ })();
348
+ </script>"""
349
+
350
  def _fixup_webui_html(html: str) -> str:
351
  html = html.replace('href="/', 'href="/openwebui/')
352
  html = html.replace("href='/", "href='/openwebui/")
353
  html = html.replace('src="/', 'src="/openwebui/')
354
  html = html.replace("src='/", "src='/openwebui/")
355
  html = html.replace('action="/', 'action="/openwebui/')
356
+ if "<head>" in html:
357
+ html = html.replace("<head>", f"<head>{WEBUI_JS_PATCH}", 1)
358
+ elif "<head " in html:
359
+ html = re.sub(r"(<head[^>]*>)", r"\1" + WEBUI_JS_PATCH, html, count=1)
360
  return html
361
 
362
  @app.api_route("/openwebui", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
 
371
  async def route_openwebui_ws(websocket: WebSocket):
372
  await proxy_websocket_stream(websocket, f"ws://127.0.0.1:{WEBUI_PORT}/ws")
373
 
374
+ # Direct SvelteKit asset routing (Open WebUI)
375
+ @app.api_route("/_app/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
376
+ async def openwebui_assets(request: Request, path: str = ""):
377
+ target = f"http://127.0.0.1:{WEBUI_PORT}/_app/{path}"
378
+ return await proxy_http_request(target, request)
379
+
380
 
381
  # ── 2. OmniRoute AI Gateway Route (/omniroute/) ──────────────────────────────
382
+ OMNIROUTE_JS_PATCH = """<script>
383
+ (function() {
384
+ if (window.__OMNIROUTE_PATCHED__) return;
385
+ window.__OMNIROUTE_PATCHED__ = true;
386
+ var origFetch = window.fetch;
387
+ window.fetch = function(resource, init) {
388
+ if (typeof resource === 'string') {
389
+ if (resource.startsWith('/') && !resource.startsWith('/omniroute')) {
390
+ resource = '/omniroute' + resource;
391
+ }
392
+ } else if (resource && resource.url && typeof resource.url === 'string') {
393
+ if (resource.url.startsWith('/') && !resource.url.startsWith('/omniroute')) {
394
+ resource = new Request('/omniroute' + resource.url, resource);
395
+ }
396
+ }
397
+ return origFetch.call(this, resource, init);
398
+ };
399
+ var origOpen = XMLHttpRequest.prototype.open;
400
+ XMLHttpRequest.prototype.open = function(method, url) {
401
+ if (typeof url === 'string' && url.startsWith('/') && !url.startsWith('/omniroute')) {
402
+ url = '/omniroute' + url;
403
+ }
404
+ return origOpen.apply(this, arguments);
405
+ };
406
+ })();
407
+ </script>"""
408
+
409
  def _fixup_omniroute_html(html: str) -> str:
410
  html = html.replace('href="/', 'href="/omniroute/')
411
  html = html.replace("href='/", "href='/omniroute/")
 
416
  html = html.replace("'/_next/", "'/omniroute/_next/")
417
  html = html.replace('"basePath":""', '"basePath":"/omniroute"')
418
  html = html.replace('"basePath": ""', '"basePath": "/omniroute"')
419
+ if "<head>" in html:
420
+ html = html.replace("<head>", f"<head>{OMNIROUTE_JS_PATCH}", 1)
421
+ elif "<head " in html:
422
+ html = re.sub(r"(<head[^>]*>)", r"\1" + OMNIROUTE_JS_PATCH, html, count=1)
423
  return html
424
 
425
  @app.api_route("/omniroute", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
 
428
  target = f"http://127.0.0.1:{OMNIROUTE_PORT}/{path}"
429
  return await proxy_http_request(target, request, html_fixup=_fixup_omniroute_html)
430
 
431
+ # Direct Next.js asset routing (OmniRoute)
432
+ @app.api_route("/_next/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
433
+ async def omniroute_assets(request: Request, path: str = ""):
434
+ target = f"http://127.0.0.1:{OMNIROUTE_PORT}/_next/{path}"
435
+ return await proxy_http_request(target, request)
436
+
437
 
438
  # ── 3. OpenCode CLI Server Route (/server/) ──────────────────────────────────
439
  def _fixup_opencode_html(html: str) -> str:
 
457
  await proxy_websocket_stream(websocket, f"ws://127.0.0.1:{OPENCODE_PORT}/ws")
458
 
459
 
460
+ # ── Direct OpenCode Endpoints (Unique endpoints only - no conflicts with Open WebUI/OmniRoute) ───
461
+ OPENCODE_DIRECT_PATHS = ["session", "project", "permission", "question", "file", "find", "events", "event", "command", "provider", "mcp"]
462
 
463
  def _create_direct_route(prefix: str):
464
  @app.api_route(f"/{prefix}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
 
495
  async def route_catch_all(path: str, request: Request):
496
  referer = request.headers.get("referer", "").lower()
497
 
498
+ if "/omniroute" in referer or "/dashboard" in referer or "/login" in referer:
 
 
499
  return await proxy_http_request(f"http://127.0.0.1:{OMNIROUTE_PORT}/{path}", request, html_fixup=_fixup_omniroute_html)
500
  elif "/server" in referer:
501
  return await proxy_http_request(f"http://127.0.0.1:{OPENCODE_PORT}/{path}", request, html_fixup=_fixup_opencode_html)
502
  elif "/jellyfin" in referer:
503
  return await proxy_http_request(f"http://127.0.0.1:{JELLYFIN_PORT}/{path}", request, extra_headers={"X-Forwarded-Prefix": "/jellyfin"})
504
 
505
+ # Fallback default: Open WebUI (Handles /api/v1/..., /static/..., etc.)
506
  return await proxy_http_request(f"http://127.0.0.1:{WEBUI_PORT}/{path}", request, html_fixup=_fixup_webui_html)