fix: make login connection state observable

This commit is contained in:
2026-08-03 11:34:46 +08:00
parent 6e37cb9076
commit cee73736d8
+23 -5
View File
@@ -77,12 +77,32 @@ function normalizeAPIAddress(value: string): string {
}
function loginFailureMessage(reason: unknown): string {
if (reason instanceof Error && reason.message === "LINEUP_LOGIN_TIMEOUT") return "连接 AppServer 超时(10 秒)。请确认 Tailscale 已连接,并核对上方显示的服务地址。";
if (reason instanceof DOMException && reason.name === "AbortError") return "连接 AppServer 超时(10 秒)。请确认 Tailscale 已连接,且服务地址和端口可访问。";
const message = reason instanceof Error ? reason.message : "";
if (message === "Load failed" || message === "Failed to fetch") return "无法连接 AppServer。请确认 Tailscale 已连接,并检查地址是否为 http://100.121.118.116:8090。";
return message || "登录失败,请稍后重试。";
}
async function fetchLogin(endpoint: string, phone: string, code: string): Promise<Response> {
const controller = new AbortController();
let timeoutID = 0;
const timeout = new Promise<never>((_, reject) => {
timeoutID = window.setTimeout(() => {
controller.abort();
reject(new Error("LINEUP_LOGIN_TIMEOUT"));
}, 10_000);
});
try {
return await Promise.race([
fetch(`${endpoint}/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone, code }), signal: controller.signal }),
timeout,
]);
} finally {
window.clearTimeout(timeoutID);
}
}
function appendBubble(role: "human" | "agent", content: string, markdown = false): HTMLElement {
const row = document.createElement("article"); row.className = `message-row ${role}`;
if (role === "agent") { const avatar = document.createElement("span"); avatar.className = "message-avatar"; avatar.textContent = "✦"; row.append(avatar); }
@@ -162,19 +182,17 @@ $<HTMLFormElement>("#login-form").addEventListener("submit", async event => {
session.api = endpoint;
localStorage.setItem("lineup.api", session.api);
error.textContent = "正在连接 AppServer…";
error.textContent = `正在连接 ${session.api}`;
submit.disabled = true;
submit.textContent = "正在进入…";
const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), 10_000);
try {
const response = await fetch(api("/login"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone, code }), signal: controller.signal });
const response = await fetchLogin(session.api, phone, code);
const body = await response.json().catch(() => ({})) as LoginResponse & { error?: string };
if (!response.ok) throw new Error(body.error || "登录失败");
session = { ...session, uid: body.uid, agentUID: body.agent_uid || session.agentUID, channelID: body.channel_id || session.channelID, channelType: body.channel_type || session.channelType, lastSeq: 0, active: true };
loginView.hidden = true; chatView.hidden = false; addSystem("已进入与 AI Agent 的对话"); setPresence("正在同步消息…"); void syncLoop();
} catch (reason) { error.textContent = loginFailureMessage(reason); }
finally { window.clearTimeout(timeout); submit.disabled = false; submit.textContent = "进入 LineUp"; }
finally { submit.disabled = false; submit.textContent = "进入 LineUp"; }
});
$<HTMLFormElement>("#message-form").addEventListener("submit", async event => {