Update app.py
Browse files
app.py
CHANGED
|
@@ -10,18 +10,20 @@
|
|
| 10 |
- /v1/reset-run
|
| 11 |
- /health
|
| 12 |
- 将原来的命令行/TTY 登录改为网页管理页:
|
| 13 |
-
- / 管理页(
|
| 14 |
-
- /admin/gate 管理页登录页
|
| 15 |
- /admin/login/start
|
| 16 |
- /admin/login/status
|
| 17 |
-
- /admin/logout
|
| 18 |
- 适配 Hugging Face Spaces:
|
| 19 |
- 读取 PORT 环境变量
|
| 20 |
- 凭据优先保存到 /data(若已开通持久化存储)
|
| 21 |
-
- 可通过 Secrets 传入 API_KEY / ADMIN_PASSWORD / ACCOUNTS_JSON
|
|
|
|
|
|
|
|
|
|
| 22 |
"""
|
| 23 |
|
| 24 |
import asyncio
|
|
|
|
| 25 |
import json
|
| 26 |
import os
|
| 27 |
import platform
|
|
@@ -46,15 +48,12 @@ PORT = int(os.environ.get("PORT", "7860"))
|
|
| 46 |
HOST = os.environ.get("HOST", "0.0.0.0")
|
| 47 |
POLL_INTERVAL_S = int(os.environ.get("POLL_INTERVAL_S", "5"))
|
| 48 |
TIMEOUT_S = int(os.environ.get("TIMEOUT_S", "300"))
|
| 49 |
-
COOKIE_SECURE = os.environ.get("COOKIE_SECURE", "1") != "0"
|
| 50 |
|
| 51 |
# /v1/* 访问鉴权(Bearer)
|
| 52 |
PROXY_API_KEY = os.environ.get("API_KEY", "")
|
| 53 |
-
# 管理后台鉴权(
|
|
|
|
| 54 |
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "")
|
| 55 |
-
ADMIN_COOKIE_NAME = "freebuff_admin_auth"
|
| 56 |
-
ADMIN_COOKIE_VALUE = "ok"
|
| 57 |
-
ADMIN_COOKIE_MAX_AGE = 86400 * 7
|
| 58 |
|
| 59 |
MODEL_TO_AGENT = {
|
| 60 |
"minimax/minimax-m2.7": "base2-free",
|
|
@@ -547,25 +546,30 @@ async def auth_middleware(request: web.Request, handler):
|
|
| 547 |
return await handler(request)
|
| 548 |
|
| 549 |
|
| 550 |
-
def is_admin_authed(request: web.Request) -> bool:
|
| 551 |
-
if not ADMIN_PASSWORD:
|
| 552 |
-
return True
|
| 553 |
-
return request.cookies.get(ADMIN_COOKIE_NAME) == ADMIN_COOKIE_VALUE
|
| 554 |
-
|
| 555 |
|
| 556 |
-
def
|
| 557 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
|
| 559 |
|
| 560 |
def require_admin(request: web.Request) -> Optional[web.Response]:
|
| 561 |
if not ADMIN_PASSWORD:
|
| 562 |
return None
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
if supplied == ADMIN_PASSWORD:
|
| 567 |
return None
|
| 568 |
-
|
|
|
|
|
|
|
|
|
|
| 569 |
|
| 570 |
|
| 571 |
# ============ Responses API 辅助函数 ============
|
|
@@ -931,6 +935,9 @@ async def handle_models(request: web.Request) -> web.Response:
|
|
| 931 |
|
| 932 |
|
| 933 |
async def handle_reset_run(request: web.Request) -> web.Response:
|
|
|
|
|
|
|
|
|
|
| 934 |
run_cache.clear()
|
| 935 |
log("Agent Run 缓存已清除")
|
| 936 |
return web.json_response({"status": "cleared"})
|
|
@@ -971,96 +978,11 @@ async def handle_health(request: web.Request) -> web.Response:
|
|
| 971 |
})
|
| 972 |
|
| 973 |
|
| 974 |
-
async def handle_admin_gate(request: web.Request) -> web.Response:
|
| 975 |
-
if is_admin_authed(request):
|
| 976 |
-
raise web.HTTPFound("/")
|
| 977 |
-
|
| 978 |
-
html = """<!doctype html>
|
| 979 |
-
<html lang="zh-CN">
|
| 980 |
-
<head>
|
| 981 |
-
<meta charset="utf-8" />
|
| 982 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 983 |
-
<title>Admin Login</title>
|
| 984 |
-
<style>
|
| 985 |
-
body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif; margin: 0; background: #0b1020; color: #e8eefc; display:flex; min-height:100vh; align-items:center; justify-content:center; }
|
| 986 |
-
.card { width: min(460px, 92vw); background: #141b34; border: 1px solid #2a355e; border-radius: 16px; padding: 22px; }
|
| 987 |
-
input, button { width:100%; box-sizing:border-box; border-radius:12px; border:1px solid #3a4b80; background:#0d1430; color:#fff; padding:12px; margin-top:12px; }
|
| 988 |
-
button { background:#3451ff; border:none; font-weight:700; cursor:pointer; }
|
| 989 |
-
.muted { color:#9fb0dd; }
|
| 990 |
-
.err { color:#ff9c9c; margin-top:10px; }
|
| 991 |
-
code { background:#091024; padding:2px 6px; border-radius:8px; }
|
| 992 |
-
</style>
|
| 993 |
-
</head>
|
| 994 |
-
<body>
|
| 995 |
-
<form class="card" method="post" action="/admin/gate">
|
| 996 |
-
<h1>管理页登录</h1>
|
| 997 |
-
<p class="muted">请输入 <code>ADMIN_PASSWORD</code>,验证通过后才会进入管理页面。</p>
|
| 998 |
-
<input type="password" name="password" placeholder="ADMIN_PASSWORD" autofocus />
|
| 999 |
-
<button type="submit">进入管理页</button>
|
| 1000 |
-
</form>
|
| 1001 |
-
</body>
|
| 1002 |
-
</html>"""
|
| 1003 |
-
return web.Response(text=html, content_type="text/html")
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
async def handle_admin_gate_post(request: web.Request) -> web.Response:
|
| 1007 |
-
if not ADMIN_PASSWORD:
|
| 1008 |
-
raise web.HTTPFound("/")
|
| 1009 |
-
|
| 1010 |
-
data = await request.post()
|
| 1011 |
-
supplied = str(data.get("password", ""))
|
| 1012 |
-
|
| 1013 |
-
if supplied != ADMIN_PASSWORD:
|
| 1014 |
-
html = """<!doctype html>
|
| 1015 |
-
<html lang="zh-CN">
|
| 1016 |
-
<head>
|
| 1017 |
-
<meta charset="utf-8" />
|
| 1018 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 1019 |
-
<title>Admin Login</title>
|
| 1020 |
-
<style>
|
| 1021 |
-
body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif; margin: 0; background: #0b1020; color: #e8eefc; display:flex; min-height:100vh; align-items:center; justify-content:center; }
|
| 1022 |
-
.card { width: min(460px, 92vw); background: #141b34; border: 1px solid #2a355e; border-radius: 16px; padding: 22px; }
|
| 1023 |
-
input, button { width:100%; box-sizing:border-box; border-radius:12px; border:1px solid #3a4b80; background:#0d1430; color:#fff; padding:12px; margin-top:12px; }
|
| 1024 |
-
button { background:#3451ff; border:none; font-weight:700; cursor:pointer; }
|
| 1025 |
-
.muted { color:#9fb0dd; }
|
| 1026 |
-
.err { color:#ff9c9c; margin-top:10px; }
|
| 1027 |
-
code { background:#091024; padding:2px 6px; border-radius:8px; }
|
| 1028 |
-
</style>
|
| 1029 |
-
</head>
|
| 1030 |
-
<body>
|
| 1031 |
-
<form class="card" method="post" action="/admin/gate">
|
| 1032 |
-
<h1>管理页登录</h1>
|
| 1033 |
-
<p class="muted">请输入 <code>ADMIN_PASSWORD</code>,验证通过后才会进入管理页面。</p>
|
| 1034 |
-
<input type="password" name="password" placeholder="ADMIN_PASSWORD" autofocus />
|
| 1035 |
-
<button type="submit">进入管理页</button>
|
| 1036 |
-
<div class="err">密码错误</div>
|
| 1037 |
-
</form>
|
| 1038 |
-
</body>
|
| 1039 |
-
</html>"""
|
| 1040 |
-
return web.Response(text=html, content_type="text/html", status=401)
|
| 1041 |
-
|
| 1042 |
-
resp = web.HTTPFound("/")
|
| 1043 |
-
resp.set_cookie(
|
| 1044 |
-
ADMIN_COOKIE_NAME,
|
| 1045 |
-
ADMIN_COOKIE_VALUE,
|
| 1046 |
-
max_age=ADMIN_COOKIE_MAX_AGE,
|
| 1047 |
-
httponly=True,
|
| 1048 |
-
samesite="Lax",
|
| 1049 |
-
secure=COOKIE_SECURE,
|
| 1050 |
-
path="/",
|
| 1051 |
-
)
|
| 1052 |
-
raise resp
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
async def handle_admin_logout(request: web.Request) -> web.Response:
|
| 1056 |
-
resp = web.HTTPFound("/admin/gate")
|
| 1057 |
-
resp.del_cookie(ADMIN_COOKIE_NAME, path="/")
|
| 1058 |
-
raise resp
|
| 1059 |
-
|
| 1060 |
|
| 1061 |
async def handle_root(request: web.Request) -> web.Response:
|
| 1062 |
-
|
| 1063 |
-
|
|
|
|
| 1064 |
|
| 1065 |
html = f"""<!doctype html>
|
| 1066 |
<html lang="zh-CN">
|
|
@@ -1073,7 +995,7 @@ async def handle_root(request: web.Request) -> web.Response:
|
|
| 1073 |
.wrap {{ max-width: 1100px; margin: 0 auto; padding: 28px; }}
|
| 1074 |
.card {{ background: #141b34; border: 1px solid #2a355e; border-radius: 16px; padding: 18px; margin-bottom: 18px; }}
|
| 1075 |
.row {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }}
|
| 1076 |
-
|
| 1077 |
button {{ cursor: pointer; background: #3451ff; border: none; font-weight: 700; }}
|
| 1078 |
button.secondary {{ background: #273053; }}
|
| 1079 |
a {{ color: #94c7ff; }}
|
|
@@ -1086,7 +1008,7 @@ async def handle_root(request: web.Request) -> web.Response:
|
|
| 1086 |
<div class="wrap">
|
| 1087 |
<div class="card">
|
| 1088 |
<h1>Freebuff OpenAI Proxy · Hugging Face Space</h1>
|
| 1089 |
-
<p class="muted">
|
| 1090 |
<div class="row">
|
| 1091 |
<div>
|
| 1092 |
<strong>聊天接口</strong>
|
|
@@ -1109,9 +1031,9 @@ async def handle_root(request: web.Request) -> web.Response:
|
|
| 1109 |
<div><button onclick="startLogin()">1. 发起网页登录</button></div>
|
| 1110 |
<div><button class="secondary" onclick="checkLogin()">2. 检查登录状态</button></div>
|
| 1111 |
<div><button class="secondary" onclick="refreshHealth()">刷新状态</button></div>
|
| 1112 |
-
<div><button class="secondary" onclick="logout()">退出管理页</button></div>
|
| 1113 |
</div>
|
| 1114 |
<p class="muted">登录后账号会写入凭据文件。若 Space 挂载了持久化存储,会写入 <code>/data/manicode/credentials.json</code>。</p>
|
|
|
|
| 1115 |
<pre id="loginBox">尚未发起登录。</pre>
|
| 1116 |
</div>
|
| 1117 |
|
|
@@ -1126,31 +1048,29 @@ let currentLoginId = localStorage.getItem('loginId') || '';
|
|
| 1126 |
const loginBox = document.getElementById('loginBox');
|
| 1127 |
const healthBox = document.getElementById('healthBox');
|
| 1128 |
|
| 1129 |
-
function adminHeaders() {{
|
| 1130 |
-
return {{ 'Content-Type': 'application/json' }};
|
| 1131 |
-
}}
|
| 1132 |
-
|
| 1133 |
async function refreshHealth() {{
|
| 1134 |
-
const res = await fetch('/health'
|
| 1135 |
-
|
| 1136 |
-
|
| 1137 |
-
|
|
|
|
|
|
|
|
|
|
| 1138 |
}}
|
| 1139 |
-
const data = await res.json();
|
| 1140 |
-
healthBox.textContent = JSON.stringify(data, null, 2);
|
| 1141 |
}}
|
| 1142 |
|
| 1143 |
async function startLogin() {{
|
| 1144 |
-
const res = await fetch('/admin/login/start', {{ method: 'POST'
|
| 1145 |
-
const
|
|
|
|
|
|
|
| 1146 |
if (!res.ok) {{
|
| 1147 |
-
loginBox.textContent = JSON.stringify(data, null, 2);
|
| 1148 |
-
if (res.status === 401) window.location.href = '/admin/gate';
|
| 1149 |
return;
|
| 1150 |
}}
|
| 1151 |
currentLoginId = data.loginId;
|
| 1152 |
localStorage.setItem('loginId', currentLoginId);
|
| 1153 |
-
loginBox.
|
| 1154 |
window.open(data.loginUrl, '_blank');
|
| 1155 |
}}
|
| 1156 |
|
|
@@ -1160,20 +1080,17 @@ async function checkLogin() {{
|
|
| 1160 |
return;
|
| 1161 |
}}
|
| 1162 |
const url = '/admin/login/status?loginId=' + encodeURIComponent(currentLoginId);
|
| 1163 |
-
const res = await fetch(url
|
| 1164 |
-
const
|
| 1165 |
-
|
| 1166 |
-
|
| 1167 |
-
|
| 1168 |
-
|
|
|
|
| 1169 |
}}
|
| 1170 |
await refreshHealth();
|
| 1171 |
}}
|
| 1172 |
|
| 1173 |
-
function logout() {{
|
| 1174 |
-
window.location.href = '/admin/logout';
|
| 1175 |
-
}}
|
| 1176 |
-
|
| 1177 |
refreshHealth();
|
| 1178 |
</script>
|
| 1179 |
</body>
|
|
@@ -1257,9 +1174,6 @@ def create_app() -> web.Application:
|
|
| 1257 |
app.on_cleanup.append(on_cleanup)
|
| 1258 |
|
| 1259 |
app.router.add_get("/", handle_root)
|
| 1260 |
-
app.router.add_get("/admin/gate", handle_admin_gate)
|
| 1261 |
-
app.router.add_post("/admin/gate", handle_admin_gate_post)
|
| 1262 |
-
app.router.add_get("/admin/logout", handle_admin_logout)
|
| 1263 |
app.router.add_get("/health", handle_health)
|
| 1264 |
app.router.add_post("/v1/chat/completions", handle_chat_completion)
|
| 1265 |
app.router.add_post("/v1/responses", handle_responses)
|
|
|
|
| 10 |
- /v1/reset-run
|
| 11 |
- /health
|
| 12 |
- 将原来的命令行/TTY 登录改为网页管理页:
|
| 13 |
+
- / 管理页(使用浏览器 Basic Auth 门禁)
|
|
|
|
| 14 |
- /admin/login/start
|
| 15 |
- /admin/login/status
|
|
|
|
| 16 |
- 适配 Hugging Face Spaces:
|
| 17 |
- 读取 PORT 环境变量
|
| 18 |
- 凭据优先保存到 /data(若已开通持久化存储)
|
| 19 |
+
- 可通过 Secrets 传入 API_KEY / ADMIN_PASSWORD / ADMIN_USERNAME / ACCOUNTS_JSON
|
| 20 |
+
- 管理页门禁:
|
| 21 |
+
- 使用浏览器原生 HTTP Basic Auth
|
| 22 |
+
- 不使用 cookie,不需要单独登录页
|
| 23 |
"""
|
| 24 |
|
| 25 |
import asyncio
|
| 26 |
+
import base64
|
| 27 |
import json
|
| 28 |
import os
|
| 29 |
import platform
|
|
|
|
| 48 |
HOST = os.environ.get("HOST", "0.0.0.0")
|
| 49 |
POLL_INTERVAL_S = int(os.environ.get("POLL_INTERVAL_S", "5"))
|
| 50 |
TIMEOUT_S = int(os.environ.get("TIMEOUT_S", "300"))
|
|
|
|
| 51 |
|
| 52 |
# /v1/* 访问鉴权(Bearer)
|
| 53 |
PROXY_API_KEY = os.environ.get("API_KEY", "")
|
| 54 |
+
# 管理后台鉴权(浏览器 Basic Auth)
|
| 55 |
+
ADMIN_USERNAME = os.environ.get("ADMIN_USERNAME", "admin")
|
| 56 |
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "")
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
MODEL_TO_AGENT = {
|
| 59 |
"minimax/minimax-m2.7": "base2-free",
|
|
|
|
| 546 |
return await handler(request)
|
| 547 |
|
| 548 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
|
| 550 |
+
def parse_basic_auth(request: web.Request) -> Tuple[Optional[str], Optional[str]]:
|
| 551 |
+
auth = request.headers.get("Authorization", "")
|
| 552 |
+
if not auth.startswith("Basic "):
|
| 553 |
+
return None, None
|
| 554 |
+
try:
|
| 555 |
+
decoded = base64.b64decode(auth[6:]).decode("utf-8")
|
| 556 |
+
username, password = decoded.split(":", 1)
|
| 557 |
+
return username, password
|
| 558 |
+
except Exception:
|
| 559 |
+
return None, None
|
| 560 |
|
| 561 |
|
| 562 |
def require_admin(request: web.Request) -> Optional[web.Response]:
|
| 563 |
if not ADMIN_PASSWORD:
|
| 564 |
return None
|
| 565 |
+
|
| 566 |
+
username, password = parse_basic_auth(request)
|
| 567 |
+
if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
|
|
|
|
| 568 |
return None
|
| 569 |
+
|
| 570 |
+
resp = web.Response(text="Authentication required", status=401)
|
| 571 |
+
resp.headers["WWW-Authenticate"] = 'Basic realm="Freebuff Admin"'
|
| 572 |
+
return resp
|
| 573 |
|
| 574 |
|
| 575 |
# ============ Responses API 辅助函数 ============
|
|
|
|
| 935 |
|
| 936 |
|
| 937 |
async def handle_reset_run(request: web.Request) -> web.Response:
|
| 938 |
+
denied = require_admin(request)
|
| 939 |
+
if denied:
|
| 940 |
+
return denied
|
| 941 |
run_cache.clear()
|
| 942 |
log("Agent Run 缓存已清除")
|
| 943 |
return web.json_response({"status": "cleared"})
|
|
|
|
| 978 |
})
|
| 979 |
|
| 980 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 981 |
|
| 982 |
async def handle_root(request: web.Request) -> web.Response:
|
| 983 |
+
denied = require_admin(request)
|
| 984 |
+
if denied:
|
| 985 |
+
return denied
|
| 986 |
|
| 987 |
html = f"""<!doctype html>
|
| 988 |
<html lang="zh-CN">
|
|
|
|
| 995 |
.wrap {{ max-width: 1100px; margin: 0 auto; padding: 28px; }}
|
| 996 |
.card {{ background: #141b34; border: 1px solid #2a355e; border-radius: 16px; padding: 18px; margin-bottom: 18px; }}
|
| 997 |
.row {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }}
|
| 998 |
+
button, textarea {{ width: 100%; box-sizing: border-box; border-radius: 12px; border: 1px solid #3a4b80; background: #0d1430; color: #fff; padding: 12px; }}
|
| 999 |
button {{ cursor: pointer; background: #3451ff; border: none; font-weight: 700; }}
|
| 1000 |
button.secondary {{ background: #273053; }}
|
| 1001 |
a {{ color: #94c7ff; }}
|
|
|
|
| 1008 |
<div class="wrap">
|
| 1009 |
<div class="card">
|
| 1010 |
<h1>Freebuff OpenAI Proxy · Hugging Face Space</h1>
|
| 1011 |
+
<p class="muted">当前管理页使用浏览器原生 <code>HTTP Basic Auth</code> 门禁。设置了 <code>ADMIN_PASSWORD</code> 后,访问页面或管理接口时会先弹出浏览器用户名/密码框,不再使用 cookie。</p>
|
| 1012 |
<div class="row">
|
| 1013 |
<div>
|
| 1014 |
<strong>聊天接口</strong>
|
|
|
|
| 1031 |
<div><button onclick="startLogin()">1. 发起网页登录</button></div>
|
| 1032 |
<div><button class="secondary" onclick="checkLogin()">2. 检查登录状态</button></div>
|
| 1033 |
<div><button class="secondary" onclick="refreshHealth()">刷新状态</button></div>
|
|
|
|
| 1034 |
</div>
|
| 1035 |
<p class="muted">登录后账号会写入凭据文件。若 Space 挂载了持久化存储,会写入 <code>/data/manicode/credentials.json</code>。</p>
|
| 1036 |
+
<p class="muted">管理页账号默认用户名是 <code>{ADMIN_USERNAME}</code>,密码来自 <code>ADMIN_PASSWORD</code>。</p>
|
| 1037 |
<pre id="loginBox">尚未发起登录。</pre>
|
| 1038 |
</div>
|
| 1039 |
|
|
|
|
| 1048 |
const loginBox = document.getElementById('loginBox');
|
| 1049 |
const healthBox = document.getElementById('healthBox');
|
| 1050 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1051 |
async function refreshHealth() {{
|
| 1052 |
+
const res = await fetch('/health');
|
| 1053 |
+
const raw = await res.text();
|
| 1054 |
+
try {{
|
| 1055 |
+
const data = JSON.parse(raw);
|
| 1056 |
+
healthBox.textContent = JSON.stringify(data, null, 2);
|
| 1057 |
+
}} catch (e) {{
|
| 1058 |
+
healthBox.textContent = raw;
|
| 1059 |
}}
|
|
|
|
|
|
|
| 1060 |
}}
|
| 1061 |
|
| 1062 |
async function startLogin() {{
|
| 1063 |
+
const res = await fetch('/admin/login/start', {{ method: 'POST' }});
|
| 1064 |
+
const raw = await res.text();
|
| 1065 |
+
let data = null;
|
| 1066 |
+
try {{ data = JSON.parse(raw); }} catch (e) {{}}
|
| 1067 |
if (!res.ok) {{
|
| 1068 |
+
loginBox.textContent = data ? JSON.stringify(data, null, 2) : raw;
|
|
|
|
| 1069 |
return;
|
| 1070 |
}}
|
| 1071 |
currentLoginId = data.loginId;
|
| 1072 |
localStorage.setItem('loginId', currentLoginId);
|
| 1073 |
+
loginBox.textContent = JSON.stringify(data, null, 2) + "\n\n打开登录链接:\n" + data.loginUrl;
|
| 1074 |
window.open(data.loginUrl, '_blank');
|
| 1075 |
}}
|
| 1076 |
|
|
|
|
| 1080 |
return;
|
| 1081 |
}}
|
| 1082 |
const url = '/admin/login/status?loginId=' + encodeURIComponent(currentLoginId);
|
| 1083 |
+
const res = await fetch(url);
|
| 1084 |
+
const raw = await res.text();
|
| 1085 |
+
try {{
|
| 1086 |
+
const data = JSON.parse(raw);
|
| 1087 |
+
loginBox.textContent = JSON.stringify(data, null, 2);
|
| 1088 |
+
}} catch (e) {{
|
| 1089 |
+
loginBox.textContent = raw;
|
| 1090 |
}}
|
| 1091 |
await refreshHealth();
|
| 1092 |
}}
|
| 1093 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
refreshHealth();
|
| 1095 |
</script>
|
| 1096 |
</body>
|
|
|
|
| 1174 |
app.on_cleanup.append(on_cleanup)
|
| 1175 |
|
| 1176 |
app.router.add_get("/", handle_root)
|
|
|
|
|
|
|
|
|
|
| 1177 |
app.router.add_get("/health", handle_health)
|
| 1178 |
app.router.add_post("/v1/chat/completions", handle_chat_completion)
|
| 1179 |
app.router.add_post("/v1/responses", handle_responses)
|