diff --git a/src/App.tsx b/src/App.tsx index 2bf0a6f..592f2de 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import ToastContainer from "./components/toast/ToastContainer"; import { toast } from "./components/toast/toastStore"; import { flushPendingGenerationRecords } from "./api/generationRecordClient"; import { keyServerClient } from "./api/keyServerClient"; +import { preloadPublicConfig, getLogoUrl } from "./api/publicConfigClient"; import { setUserMaxConcurrency } from "./api/generationConcurrency"; import { SERVER_SESSION_EXPIRED_EVENT, @@ -242,6 +243,8 @@ function App() { let cancelled = false; const loadSession = async () => { + // 预加载公网配置(OSS base / logo URL),与 session 加载并行,不阻断启动。 + void preloadPublicConfig(); try { const nextSession = await keyServerClient.getCurrentSession(); if (cancelled) return; @@ -414,7 +417,7 @@ function App() {

{authMode === "login" ? "欢迎回来" : "创建账号"}

{authMode === "login" ? "登录后继续你的 AI 创作之旅" : "注册即可免费体验全部功能"}

diff --git a/src/api/publicConfigClient.ts b/src/api/publicConfigClient.ts new file mode 100644 index 0000000..5e80860 --- /dev/null +++ b/src/api/publicConfigClient.ts @@ -0,0 +1,65 @@ +// 前端公网配置客户端。 +// 从 GET /api/public/config/profile?name=web-public-config 拉取运行时配置, +// 包括 OSS 公网 base URL 与 logo URL。 +// 按 AGENTS.md 规则 1/4/5:这些环境权威数据不硬编码在前端源码,由 API 下发。 +// +// 设计:进程内单例缓存 + promise 去重,App 启动时预加载一次, +// 之后 getOssPublicBaseUrl() / getLogoUrl() 同步返回缓存值。 +// API 不可用时回退到 FALLBACK 值(当前生产 bucket),保证渐进可用。 + +import { serverRequest } from "./serverConnection"; + +interface PublicConfigPayload { + name: string; + config: { + ossPublicBaseUrl?: string; + logoUrl?: string; + }; + description?: string; + updatedAt?: string; +} + +// Fallback:API 不可用或未加载时的兜底值,保证首屏不白屏。 +// 这些值仅作为降级,正式来源是 API 返回的 config。 +const FALLBACK_OSS_PUBLIC_BASE_URL = "https://stringtest.oss-cn-hangzhou.aliyuncs.com"; +const FALLBACK_LOGO_URL = "https://stringtest.oss-cn-hangzhou.aliyuncs.com/muban/logo.png"; + +let cachedConfig: PublicConfigPayload["config"] | null = null; +let loadPromise: Promise | null = null; + +async function fetchPublicConfig(): Promise { + const payload = await serverRequest("public/config/profile?name=web-public-config", { + // 公开端点,无需 token。 + maxRetries: 2, + fallbackMessage: "加载公网配置失败", + }); + return payload?.config ?? {}; +} + +/** 预加载公网配置。App 启动时调用一次,后续同步读取缓存。可安全重复调用(promise 去重)。 */ +export async function preloadPublicConfig(): Promise { + if (loadPromise) return loadPromise.then(() => undefined); + loadPromise = fetchPublicConfig() + .then((config) => { + cachedConfig = config; + return config; + }) + .catch((error) => { + // 加载失败不阻断启动,用 fallback 值;记录后允许后续重试。 + console.warn("[publicConfig] 加载失败,使用 fallback 值", error); + cachedConfig = null; + loadPromise = null; + return {}; + }); + return loadPromise.then(() => undefined); +} + +/** 同步获取 OSS 公网 base URL。未加载时返回 fallback。 */ +export function getOssPublicBaseUrl(): string { + return cachedConfig?.ossPublicBaseUrl?.trim() || FALLBACK_OSS_PUBLIC_BASE_URL; +} + +/** 同步获取 logo URL。未加载时返回 fallback。 */ +export function getLogoUrl(): string { + return cachedConfig?.logoUrl?.trim() || FALLBACK_LOGO_URL; +} diff --git a/src/components/Topbar.tsx b/src/components/Topbar.tsx index 9bba4af..1eee5ad 100644 --- a/src/components/Topbar.tsx +++ b/src/components/Topbar.tsx @@ -10,6 +10,7 @@ import { WalletOutlined, } from "@ant-design/icons"; import { LocalAvatar } from "./LocalAvatar"; +import { getLogoUrl } from "../api/publicConfigClient"; import type { WebUserSession } from "../types"; interface TopbarProps { @@ -110,7 +111,7 @@ export function Topbar({ onClick={onOpenWorkspace} > OmniAI 电商智能体 diff --git a/src/data/ossAssets.ts b/src/data/ossAssets.ts index 5065378..75d2d0b 100644 --- a/src/data/ossAssets.ts +++ b/src/data/ossAssets.ts @@ -1,7 +1,10 @@ -const OSS_PUBLIC_BASE_URL = "https://stringtest.oss-cn-hangzhou.aliyuncs.com"; +// OSS 公网 base URL 由 API 下发(AGENTS.md 规则 1/5), +// 见 src/api/publicConfigClient.ts。ossAssets 在模块加载时同步取缓存值, +// App 启动时 preloadPublicConfig() 已预加载;未加载时 getOssPublicBaseUrl() 返回 fallback。 +import { getOssPublicBaseUrl } from "../api/publicConfigClient"; function oss(path: string): string { - return `${OSS_PUBLIC_BASE_URL}/${path.replace(/^\/+/, "")}`; + return `${getOssPublicBaseUrl()}/${path.replace(/^\/+/, "")}`; } function muban(path: string): string {