mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 10:33:18 +08:00
feat(品牌): 支持 _local 外部优先 Logo 与图标覆盖
新增 /admin/brand-assets 静态挂载,并在前端与桌面端统一采用外部优先、内置兜底的品牌资源解析;同时补充 BRANDING 文档与 README 入口说明。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b15ff4c31c
commit
acf1266e90
7 changed files with 106 additions and 13 deletions
|
|
@ -1934,7 +1934,8 @@ function prependMessageTime(node, tsIso) {
|
|||
node.insertBefore(el("div", { class: "chat-msg__time", text: txt }), node.firstChild);
|
||||
}
|
||||
|
||||
const CHAT_BOT_LOGO_SRC = "/admin/assets/oliver.svg";
|
||||
const CHAT_BOT_LOGO_SRC = "/admin/brand-assets/logo.svg";
|
||||
const CHAT_BOT_LOGO_FALLBACK_SRC = "/admin/assets/oliver.svg";
|
||||
/** 内置默认用户头像(与助手头像同尺寸与底栏样式,SVG) */
|
||||
const DEFAULT_USER_AVATAR_SRC = "/admin/assets/default-user-avatar.svg";
|
||||
|
||||
|
|
@ -1950,12 +1951,22 @@ async function loadMeProfile() {
|
|||
}
|
||||
|
||||
function buildBotAvatarImg() {
|
||||
return el("img", {
|
||||
const img = el("img", {
|
||||
class: "chat-avatar chat-avatar--bot",
|
||||
src: CHAT_BOT_LOGO_SRC,
|
||||
alt: "",
|
||||
loading: "lazy",
|
||||
});
|
||||
img.addEventListener(
|
||||
"error",
|
||||
() => {
|
||||
if (img.dataset.logoFallbackApplied === "1") return;
|
||||
img.dataset.logoFallbackApplied = "1";
|
||||
img.src = CHAT_BOT_LOGO_FALLBACK_SRC;
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
return img;
|
||||
}
|
||||
|
||||
function buildUserAvatarSlot() {
|
||||
|
|
@ -2331,13 +2342,21 @@ function mount(node) {
|
|||
}
|
||||
|
||||
function buildChatBrandLogoNode() {
|
||||
return el("div", { class: "chat-nav__brandWrap" }, [
|
||||
el("img", {
|
||||
class: "chat-nav__brandLogo",
|
||||
src: "/admin/assets/oliver.svg",
|
||||
alt: "oliver logo",
|
||||
}),
|
||||
]);
|
||||
const img = el("img", {
|
||||
class: "chat-nav__brandLogo",
|
||||
src: CHAT_BOT_LOGO_SRC,
|
||||
alt: "site logo",
|
||||
});
|
||||
img.addEventListener(
|
||||
"error",
|
||||
() => {
|
||||
if (img.dataset.logoFallbackApplied === "1") return;
|
||||
img.dataset.logoFallbackApplied = "1";
|
||||
img.src = CHAT_BOT_LOGO_FALLBACK_SRC;
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
return el("div", { class: "chat-nav__brandWrap" }, [img]);
|
||||
}
|
||||
|
||||
function resolveAdminHashUrl(hashPath, sessionId) {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,12 @@
|
|||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand__logoWrap">
|
||||
<img class="brand__logo" src="/admin/assets/oliver.svg" alt="oliver logo" />
|
||||
<img
|
||||
class="brand__logo"
|
||||
src="/admin/brand-assets/logo.svg"
|
||||
alt="site logo"
|
||||
onerror="if(!this.dataset.fallback){this.dataset.fallback='1';this.src='/admin/assets/oliver.svg';}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="nav">
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import asyncio
|
|||
import os
|
||||
import shutil
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import threading
|
||||
|
||||
|
|
@ -230,6 +231,8 @@ async def _lifespan(app: FastAPI): # type: ignore[no-untyped-def]
|
|||
|
||||
def create_app() -> FastAPI:
|
||||
app = FastAPI(title="ops-gateway", version="0.1", lifespan=_lifespan)
|
||||
brand_assets_dir = (Path(PROJECT_ROOT) / "_local" / "branding").resolve()
|
||||
app.mount("/admin/brand-assets", StaticFiles(directory=str(brand_assets_dir), check_dir=False), name="admin-brand-assets")
|
||||
app.mount("/admin/assets", StaticFiles(directory=str(admin_static_dir())), name="admin-assets")
|
||||
app.include_router(build_admin_router())
|
||||
app.include_router(weixin_ilink_router)
|
||||
|
|
@ -239,7 +242,8 @@ def create_app() -> FastAPI:
|
|||
resp = await call_next(request)
|
||||
# Avoid stale JS/CSS after refactors (especially in Electron webview).
|
||||
# The admin SPA and /chat both load from /admin/assets/...
|
||||
if str(request.url.path or "").startswith("/admin/assets/"):
|
||||
req_path = str(request.url.path or "")
|
||||
if req_path.startswith("/admin/assets/") or req_path.startswith("/admin/brand-assets/"):
|
||||
resp.headers["Cache-Control"] = "no-store, max-age=0"
|
||||
resp.headers["Pragma"] = "no-cache"
|
||||
return resp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue