diff --git a/sundynix-desktop/frontend/src/lib/api.ts b/sundynix-desktop/frontend/src/lib/api.ts index d8efab9..11d848a 100644 --- a/sundynix-desktop/frontend/src/lib/api.ts +++ b/sundynix-desktop/frontend/src/lib/api.ts @@ -1,9 +1,42 @@ // Gateway HTTP/SSE 客户端:提交 DSL 任务、订阅 Token 流、登记偏好记忆。 import type { TaskDsl } from "./dsl"; -// 开发期直连 Gateway;Wails 打包后可改为本地后端地址或经 Go 绑定。 -export const GATEWAY: string = - (import.meta.env.VITE_GATEWAY as string | undefined) ?? "http://localhost:8080"; +// 服务器地址:**运行时可配**(登录页可填),优先级 localStorage > 构建期 VITE_GATEWAY > 本地默认。 +// 做成运行时而非只在构建期写死,是因为同一个 App 要能在「本地全栈」和「线上服务器」之间随时切, +// 每换一次环境就重编一次客户端不现实。改完需重载窗口(模块级常量,40+ 处调用点靠它读同一份值)。 +const GATEWAY_KEY = "sdx_gateway"; + +function initialGateway(): string { + try { + const saved = localStorage.getItem(GATEWAY_KEY); + if (saved) return saved.replace(/\/+$/, ""); // 去掉尾斜杠,否则拼出 //api/v1 这种路径 + } catch { + /* 隐私模式忽略 */ + } + return (import.meta.env.VITE_GATEWAY as string | undefined) ?? "http://localhost:8080"; +} + +export const GATEWAY: string = initialGateway(); + +// setGatewayURL 保存服务器地址(空串=清除,回落构建期默认)。调用方保存后应重载窗口使其生效。 +export function setGatewayURL(url: string): void { + try { + const v = url.trim().replace(/\/+$/, ""); + if (v) localStorage.setItem(GATEWAY_KEY, v); + else localStorage.removeItem(GATEWAY_KEY); + } catch { + /* ignore */ + } +} + +// customGatewayURL 返回用户自定义的地址(没设过则空串),供设置界面回显。 +export function customGatewayURL(): string { + try { + return localStorage.getItem(GATEWAY_KEY) ?? ""; + } catch { + return ""; + } +} export interface Identity { userId: string; diff --git a/sundynix-desktop/frontend/src/views/Login.tsx b/sundynix-desktop/frontend/src/views/Login.tsx index bb77b5d..b123db2 100644 --- a/sundynix-desktop/frontend/src/views/Login.tsx +++ b/sundynix-desktop/frontend/src/views/Login.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { LogIn, UserPlus, Loader2 } from "lucide-react"; -import { authLogin, authRegister, type AuthUser } from "../lib/api"; +import { authLogin, authRegister, customGatewayURL, setGatewayURL, GATEWAY, type AuthUser } from "../lib/api"; import { Button, Input, Field } from "../ui"; // Login:未登录时的全屏鉴权门。登录/注册成功后回调 onAuthed 把用户交给 App。 @@ -66,7 +66,49 @@ export function Login({ onAuthed }: { onAuthed: (u: AuthUser) => void }) { {isRegister ? "去登录" : "去注册"} + + ); } + +// ServerField 服务器地址设置:默认折叠,展开后可指向线上后端或本地全栈。 +// 放在登录页是因为它必须在登录之前可改——地址不对就连不上、更谈不上登录。 +function ServerField() { + const [open, setOpen] = useState(false); + const [url, setUrl] = useState(customGatewayURL()); + const [saved, setSaved] = useState(false); + + const save = () => { + setGatewayURL(url); + setSaved(true); + // 模块级常量在加载时读定,改完必须重载窗口才生效——直接替用户重载,省得他困惑为何没变。 + setTimeout(() => window.location.reload(), 400); + }; + + return ( +
+ + {open && ( +
+ setUrl(e.target.value)} + placeholder="https://agent.sundynix.cn" + onKeyDown={(e) => e.key === "Enter" && save()} + /> +
+ + 留空 = 用默认 +
+
+ )} +
+ ); +}