Merge origin/master into feat/dialog-generator-cancel-generation
This commit is contained in:
+65
-55
@@ -1,15 +1,3 @@
|
||||
import {
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
CheckCircleOutlined,
|
||||
FlagOutlined,
|
||||
InfoCircleOutlined,
|
||||
LoginOutlined,
|
||||
LogoutOutlined,
|
||||
PlusCircleOutlined,
|
||||
UserOutlined,
|
||||
WalletOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { publicConfigClient, type WebPublicConfig } from "../api/publicConfigClient";
|
||||
@@ -20,10 +8,12 @@ import { canManageCommunityCases, canReviewCommunity } from "../features/communi
|
||||
import type { WebNavItem, WebNotification, WebUsageSummary, WebUserSession, WebViewKey } from "../types";
|
||||
import NotificationCenter from "./NotificationCenter";
|
||||
import BetaApplicationModal from "./BetaApplicationModal";
|
||||
import { RechargeModal } from "./RechargeModal/RechargeModal";
|
||||
import { AnimatedPanel } from "./AnimatedPanel";
|
||||
import AdminMonitor from "./AdminMonitor";
|
||||
import CookieConsentBanner from "./CookieConsentBanner";
|
||||
import { loadRechargeModal, type RechargeModalComponent } from "./RechargeModal/loadRechargeModal";
|
||||
import { ShellIcon } from "./ShellIcon";
|
||||
import { loadDarkGreenTheme } from "../styles/loadDarkGreenTheme";
|
||||
|
||||
interface AppShellProps {
|
||||
activeView: WebViewKey;
|
||||
@@ -42,6 +32,32 @@ interface AppShellProps {
|
||||
}
|
||||
|
||||
const BRAND_LOGO_URL = ossAssets.brand.logo;
|
||||
const TOOL_SURFACE_VIEW_SET = new Set<WebViewKey>([
|
||||
"workbench",
|
||||
"canvas",
|
||||
"more",
|
||||
"scriptTokens",
|
||||
"tokenUsage",
|
||||
"ecommerceTemplates",
|
||||
"sizeTemplate",
|
||||
"imageWorkbench",
|
||||
"resolutionUpscale",
|
||||
"digitalHuman",
|
||||
"dialogGenerator",
|
||||
"avatarConsole",
|
||||
"characterMix",
|
||||
] as WebViewKey[]);
|
||||
const PRIMARY_NAV_ORDER: WebViewKey[] = [
|
||||
"workbench",
|
||||
"ecommerce",
|
||||
"sizeTemplate",
|
||||
"canvas",
|
||||
"scriptTokens",
|
||||
"tokenUsage",
|
||||
"more",
|
||||
"assets",
|
||||
"community",
|
||||
];
|
||||
|
||||
function formatBalance(cents: number): string {
|
||||
const value = Math.max(0, cents) / 100;
|
||||
@@ -68,6 +84,7 @@ function AppShell({
|
||||
const submenuHideTimerRef = useRef<number | null>(null);
|
||||
const [profileOpen, setProfileOpen] = useState(false);
|
||||
const [rechargeOpen, setRechargeOpen] = useState(false);
|
||||
const [RechargeModal, setRechargeModal] = useState<RechargeModalComponent | null>(null);
|
||||
const [infoOpen, setInfoOpen] = useState(false);
|
||||
const [betaOpen, setBetaOpen] = useState(false);
|
||||
const infoRef = useRef<HTMLDivElement>(null);
|
||||
@@ -78,38 +95,13 @@ function AppShell({
|
||||
const isAuthView = activeView === "login";
|
||||
const isImmersiveView = activeView === "agent" || activeView === "avatarConsole";
|
||||
const showFloatingNav = !isAuthView && !isImmersiveView && activeView !== "home";
|
||||
const toolSurfaceViews = [
|
||||
"workbench",
|
||||
"canvas",
|
||||
"more",
|
||||
"scriptTokens",
|
||||
"tokenUsage",
|
||||
"ecommerceTemplates",
|
||||
"sizeTemplate",
|
||||
"imageWorkbench",
|
||||
"resolutionUpscale",
|
||||
"digitalHuman",
|
||||
"dialogGenerator",
|
||||
"avatarConsole",
|
||||
"characterMix",
|
||||
] as WebViewKey[];
|
||||
const showPageScrollActions = showFloatingNav && !toolSurfaceViews.includes(activeView);
|
||||
const showPageScrollActions = showFloatingNav && !TOOL_SURFACE_VIEW_SET.has(activeView);
|
||||
|
||||
const visibleNavItems = useMemo(
|
||||
() => {
|
||||
const orderedKeys: WebViewKey[] = [
|
||||
"workbench",
|
||||
"ecommerce",
|
||||
"sizeTemplate",
|
||||
"canvas",
|
||||
"scriptTokens",
|
||||
"tokenUsage",
|
||||
"more",
|
||||
"assets",
|
||||
"community",
|
||||
];
|
||||
return orderedKeys
|
||||
.map((key) => navItems.find((item) => item.key === key))
|
||||
const navItemByKey = new Map(navItems.map((item) => [item.key, item]));
|
||||
return PRIMARY_NAV_ORDER
|
||||
.map((key) => navItemByKey.get(key))
|
||||
.filter((item): item is WebNavItem => Boolean(item));
|
||||
},
|
||||
[navItems],
|
||||
@@ -129,6 +121,7 @@ function AppShell({
|
||||
return;
|
||||
}
|
||||
|
||||
void loadDarkGreenTheme();
|
||||
document.documentElement.dataset.theme = "dark";
|
||||
document.documentElement.dataset.uiTheme = "dark-green";
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
@@ -193,6 +186,21 @@ function AppShell({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rechargeOpen || RechargeModal) return;
|
||||
|
||||
let cancelled = false;
|
||||
void loadRechargeModal().then((component) => {
|
||||
if (!cancelled) {
|
||||
setRechargeModal(() => component);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [RechargeModal, rechargeOpen]);
|
||||
|
||||
const showSubmenu = (key: WebViewKey) => {
|
||||
if (submenuHideTimerRef.current) {
|
||||
window.clearTimeout(submenuHideTimerRef.current);
|
||||
@@ -313,7 +321,7 @@ function AppShell({
|
||||
aria-label="返回页面顶部"
|
||||
onClick={() => scrollActivePage("top")}
|
||||
>
|
||||
<ArrowUpOutlined />
|
||||
<ShellIcon name="arrow-up" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -322,7 +330,7 @@ function AppShell({
|
||||
aria-label="到达页面底部"
|
||||
onClick={() => scrollActivePage("bottom")}
|
||||
>
|
||||
<ArrowDownOutlined />
|
||||
<ShellIcon name="arrow-down" />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -361,7 +369,7 @@ function AppShell({
|
||||
aria-label="网站信息"
|
||||
onClick={() => setInfoOpen((c) => !c)}
|
||||
>
|
||||
<InfoCircleOutlined />
|
||||
<ShellIcon name="info-circle" />
|
||||
</button>
|
||||
<AnimatedPanel open={infoOpen} className="info-popover panel-surface">
|
||||
<dl>
|
||||
@@ -373,6 +381,7 @@ function AppShell({
|
||||
<dd>{publicConfig.contactPhone || "由服务器配置"}</dd>
|
||||
</dl>
|
||||
<div className="info-popover__links">
|
||||
<a href="#/bug-feedback" onClick={() => setInfoOpen(false)}>Bug 反馈</a>
|
||||
<a href="#/userAgreement" onClick={() => setInfoOpen(false)}>用户协议</a>
|
||||
<a href="#/privacyPolicy" onClick={() => setInfoOpen(false)}>隐私政策</a>
|
||||
</div>
|
||||
@@ -384,7 +393,7 @@ function AppShell({
|
||||
aria-label={`积分余额 ${displayedBalanceLabel}`}
|
||||
onClick={() => toast.info("充值功能即将开放,敬请期待")}
|
||||
>
|
||||
<WalletOutlined />
|
||||
<ShellIcon name="wallet" />
|
||||
<span className="member-button__label">{displayedBalanceLabel}</span>
|
||||
</button>
|
||||
<div className="profile-popover-anchor" ref={profileRef}>
|
||||
@@ -408,7 +417,7 @@ function AppShell({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<LoginOutlined />
|
||||
<ShellIcon name="login" />
|
||||
<span>登录 / 注册</span>
|
||||
</>
|
||||
)}
|
||||
@@ -436,7 +445,7 @@ function AppShell({
|
||||
<div className="profile-popover__footer">
|
||||
<span>{session?.source === "server" ? "服务器会话" : "预览会话"}</span>
|
||||
<button type="button" onClick={onLogout}>
|
||||
<LogoutOutlined />
|
||||
<ShellIcon name="logout" />
|
||||
退出
|
||||
</button>
|
||||
</div>
|
||||
@@ -448,7 +457,7 @@ function AppShell({
|
||||
onSelectView("login");
|
||||
}}
|
||||
>
|
||||
<UserOutlined />
|
||||
<ShellIcon name="user" />
|
||||
个人中心
|
||||
</button>
|
||||
<button
|
||||
@@ -459,8 +468,8 @@ function AppShell({
|
||||
onSelectView("report");
|
||||
}}
|
||||
>
|
||||
<FlagOutlined />
|
||||
投诉举报
|
||||
<ShellIcon name="flag" />
|
||||
Bug 反馈
|
||||
</button>
|
||||
{showCommunityReview ? (
|
||||
<>
|
||||
@@ -472,7 +481,7 @@ function AppShell({
|
||||
onSelectView("communityReview");
|
||||
}}
|
||||
>
|
||||
<CheckCircleOutlined />
|
||||
<ShellIcon name="check-circle" />
|
||||
社区审核
|
||||
</button>
|
||||
</>
|
||||
@@ -487,7 +496,7 @@ function AppShell({
|
||||
onSelectView("communityCaseAdd");
|
||||
}}
|
||||
>
|
||||
<PlusCircleOutlined />
|
||||
<ShellIcon name="plus-circle" />
|
||||
添加案例
|
||||
</button>
|
||||
</>
|
||||
@@ -501,9 +510,10 @@ function AppShell({
|
||||
</main>
|
||||
</div>
|
||||
{session?.user.role === "admin" ? <AdminMonitor /> : null}
|
||||
<RechargeModal open={rechargeOpen} onClose={() => setRechargeOpen(false)} currentBalance={displayedBalanceCents} />
|
||||
{rechargeOpen && RechargeModal ? (
|
||||
<RechargeModal open={rechargeOpen} onClose={() => setRechargeOpen(false)} currentBalance={displayedBalanceCents} />
|
||||
) : null}
|
||||
<BetaApplicationModal open={betaOpen} onClose={() => setBetaOpen(false)} />
|
||||
<CookieConsentBanner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user