Merge origin/master into feat/dialog-generator-cancel-generation

This commit is contained in:
OmniAI Developer
2026-06-08 14:46:34 +08:00
76 changed files with 2510 additions and 928 deletions
+65 -55
View File
@@ -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>
);
}