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
+187 -80
View File
@@ -1,20 +1,5 @@
import {
BarChartOutlined,
BranchesOutlined,
CustomerServiceOutlined,
DeleteOutlined,
FolderOpenOutlined,
GlobalOutlined,
HeartOutlined,
HomeOutlined,
LayoutOutlined,
RobotOutlined,
ShoppingOutlined,
SwapOutlined,
ToolOutlined,
WalletOutlined,
} from "@ant-design/icons";
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useShallow } from "zustand/react/shallow";
import ErrorBoundary from "./components/ErrorBoundary";
import { reportError } from "./utils/errorReporting";
import { initNotificationPermission } from "./utils/generationNotifier";
@@ -36,6 +21,7 @@ import { webGenerationGateway, type CreatePreviewTaskInput } from "./api/webGene
import { translateTaskError } from "./utils/translateTaskError";
import { recoverAndResumeTasks } from "./services/backgroundTaskRunner";
import AppShell from "./components/AppShell";
import { ShellIcon } from "./components/ShellIcon";
const NotFoundPage = lazy(() => import("./components/NotFoundPage"));
const CompliancePage = lazy(() => import("./features/compliance/CompliancePage"));
import { cloneWorkflow, createBlankWorkflow } from "./data/workflows";
@@ -50,6 +36,7 @@ const AvatarConsolePage = lazy(() => import("./features/digital-human/AvatarCons
const DigitalHumanPage = lazy(() => import("./features/digital-human/DigitalHumanPage"));
const DialogGeneratorPage = lazy(() => import("./features/dialog-generator/DialogGeneratorPage"));
const EcommercePage = lazy(() => import("./features/ecommerce/EcommercePage"));
const EcommerceTemplatesPage = lazy(() => import("./features/ecommerce/EcommerceTemplatesPage"));
const HomePage = lazy(() => import("./features/home/HomePage"));
const ImageWorkbenchPage = lazy(() => import("./features/image-workbench/ImageWorkbenchPage"));
const MorePage = lazy(() => import("./features/more/MorePage"));
@@ -60,6 +47,7 @@ const ResolutionUpscalePage = lazy(() => import("./features/resolution-upscale/R
const WatermarkRemovalPage = lazy(() => import("./features/watermark-removal/WatermarkRemovalPage"));
const SubtitleRemovalPage = lazy(() => import("./features/subtitle-removal/SubtitleRemovalPage"));
const ScriptTokensPage = lazy(() => import("./features/script-tokens/ScriptTokensPage"));
const SizeTemplatePage = lazy(() => import("./features/size-template/SizeTemplatePage"));
const TokenUsagePage = lazy(() => import("./features/script-tokens/TokenUsagePage"));
const WorkbenchPage = lazy(() => import("./features/workbench/WorkbenchPage"));
import type { WorkbenchResultActionPayload } from "./features/workbench/WorkbenchPage";
@@ -105,6 +93,8 @@ const VIEW_KEYS = new Set<WebViewKey>([
"assets",
"ecommerceHub",
"ecommerce",
"ecommerceTemplates",
"sizeTemplate",
"scriptTokens",
"tokenUsage",
"imageWorkbench",
@@ -126,6 +116,29 @@ const VIEW_KEYS = new Set<WebViewKey>([
]);
const PUBLIC_VIEW_SET = new Set<WebViewKey>(["home", "login", "community", "more", "dialogGenerator", "userAgreement", "privacyPolicy", "not-found"]);
const LEGACY_PAGE_STYLE_VIEWS = new Set<WebViewKey>([
"login",
"workbench",
"canvas",
"community",
"communityReview",
"communityCaseAdd",
"assets",
"ecommerce",
"ecommerceHub",
"ecommerceTemplates",
"sizeTemplate",
"digitalHuman",
"characterMix",
"more",
]);
let legacyPageStylesPromise: Promise<unknown> | null = null;
function loadLegacyPageStyles(): Promise<unknown> {
legacyPageStylesPromise ??= import("./styles/pages/legacy-pages.css");
return legacyPageStylesPromise;
}
function normalizeViewKey(rawView: string): WebViewKey {
const normalized =
@@ -133,6 +146,8 @@ function normalizeViewKey(rawView: string): WebViewKey {
? "login"
: rawView === "ecommerceHub"
? "ecommerce"
: rawView === "bug-feedback" || rawView === "feedback"
? "report"
: rawView === "terms" || rawView === "agreement" || rawView === "user-agreement"
? "userAgreement"
: rawView === "privacy" || rawView === "privacy-policy"
@@ -233,62 +248,124 @@ function App() {
const canvasAutoOpenedRecentRef = useRef(false);
// Session store
const session = useSessionStore((s) => s.session);
const loginPromptOpen = useSessionStore((s) => s.loginPromptOpen);
const pendingAction = useSessionStore((s) => s.pendingAction);
const sessionReplacedOpen = useSessionStore((s) => s.sessionReplacedOpen);
const sessionReplacedMessage = useSessionStore((s) => s.sessionReplacedMessage);
const setSession = useSessionStore((s) => s.setSession);
const openLoginPrompt = useSessionStore((s) => s.openLoginPrompt);
const closeLoginPrompt = useSessionStore((s) => s.closeLoginPrompt);
const showSessionReplaced = useSessionStore((s) => s.showSessionReplaced);
const hideSessionReplaced = useSessionStore((s) => s.hideSessionReplaced);
const clearSessionState = useSessionStore((s) => s.clearSession);
const {
session,
loginPromptOpen,
pendingAction,
sessionReplacedOpen,
sessionReplacedMessage,
setSession,
openLoginPrompt,
closeLoginPrompt,
showSessionReplaced,
hideSessionReplaced,
clearSession: clearSessionState,
} = useSessionStore(useShallow((s) => ({
session: s.session,
loginPromptOpen: s.loginPromptOpen,
pendingAction: s.pendingAction,
sessionReplacedOpen: s.sessionReplacedOpen,
sessionReplacedMessage: s.sessionReplacedMessage,
setSession: s.setSession,
openLoginPrompt: s.openLoginPrompt,
closeLoginPrompt: s.closeLoginPrompt,
showSessionReplaced: s.showSessionReplaced,
hideSessionReplaced: s.hideSessionReplaced,
clearSession: s.clearSession,
})));
// Project store
const projects = useProjectStore((s) => s.projects);
const projectsLoaded = useProjectStore((s) => s.projectsLoaded);
const canvasWorkflow = useProjectStore((s) => s.canvasWorkflow);
const currentCanvasProjectId = useProjectStore((s) => s.currentCanvasProjectId);
const pendingDeleteProject = useProjectStore((s) => s.pendingDeleteProject);
const deleteProjectSubmitting = useProjectStore((s) => s.deleteProjectSubmitting);
const setProjects = useProjectStore((s) => s.setProjects);
const setProjectsLoaded = useProjectStore((s) => s.setProjectsLoaded);
const setCanvasWorkflow = useProjectStore((s) => s.setCanvasWorkflow);
const setCurrentCanvasProjectId = useProjectStore((s) => s.setCurrentCanvasProjectId);
const openDeleteProjectModal = useProjectStore((s) => s.openDeleteProject);
const closeDeleteProjectModal = useProjectStore((s) => s.closeDeleteProject);
const setDeleteProjectSubmitting = useProjectStore((s) => s.setDeleteProjectSubmitting);
const clearProjectState = useProjectStore((s) => s.clearProjectState);
const {
projects,
projectsLoaded,
canvasWorkflow,
currentCanvasProjectId,
pendingDeleteProject,
deleteProjectSubmitting,
setProjects,
setProjectsLoaded,
setCanvasWorkflow,
setCurrentCanvasProjectId,
openDeleteProject: openDeleteProjectModal,
closeDeleteProject: closeDeleteProjectModal,
setDeleteProjectSubmitting,
clearProjectState,
} = useProjectStore(useShallow((s) => ({
projects: s.projects,
projectsLoaded: s.projectsLoaded,
canvasWorkflow: s.canvasWorkflow,
currentCanvasProjectId: s.currentCanvasProjectId,
pendingDeleteProject: s.pendingDeleteProject,
deleteProjectSubmitting: s.deleteProjectSubmitting,
setProjects: s.setProjects,
setProjectsLoaded: s.setProjectsLoaded,
setCanvasWorkflow: s.setCanvasWorkflow,
setCurrentCanvasProjectId: s.setCurrentCanvasProjectId,
openDeleteProject: s.openDeleteProject,
closeDeleteProject: s.closeDeleteProject,
setDeleteProjectSubmitting: s.setDeleteProjectSubmitting,
clearProjectState: s.clearProjectState,
})));
// Task store
const tasks = useTaskStore((s) => s.tasks);
const setTasks = useTaskStore((s) => s.setTasks);
const appendTask = useTaskStore((s) => s.appendTask);
const mergeServerTasks = useTaskStore((s) => s.mergeServerTasks);
const clearTasks = useTaskStore((s) => s.clearTasks);
const {
tasks,
setTasks,
appendTask,
mergeServerTasks,
clearTasks,
} = useTaskStore(useShallow((s) => ({
tasks: s.tasks,
setTasks: s.setTasks,
appendTask: s.appendTask,
mergeServerTasks: s.mergeServerTasks,
clearTasks: s.clearTasks,
})));
// App store
const usage = useAppStore((s) => s.usage);
const runtimeNotifications = useAppStore((s) => s.runtimeNotifications);
const serverNotifications = useAppStore((s) => s.serverNotifications);
const activeView = useAppStore((s) => s.activeView);
const workspaceExpanded = useAppStore((s) => s.workspaceExpanded);
const imageWorkbenchTool = useAppStore((s) => s.imageWorkbenchTool);
const pendingEcommerceTemplate = useAppStore((s) => s.pendingEcommerceTemplate);
const backendHealth = useAppStore((s) => s.backendHealth);
const setUsage = useAppStore((s) => s.setUsage);
const pushNotification = useAppStore((s) => s.pushNotification);
const setRuntimeNotifications = useAppStore((s) => s.setRuntimeNotifications);
const setServerNotifications = useAppStore((s) => s.setServerNotifications);
const setView = useAppStore((s) => s.setView);
const setWorkspaceExpanded = useAppStore((s) => s.setWorkspaceExpanded);
const setImageWorkbenchTool = useAppStore((s) => s.setImageWorkbenchTool);
const setPendingEcommerceTemplate = useAppStore((s) => s.setPendingEcommerceTemplate);
const setBackendHealth = useAppStore((s) => s.setBackendHealth);
const markNotificationRead = useAppStore((s) => s.markNotificationRead);
const markAllNotificationsRead = useAppStore((s) => s.markAllNotificationsRead);
const clearAppState = useAppStore((s) => s.clearAppState);
const {
usage,
runtimeNotifications,
serverNotifications,
activeView,
workspaceExpanded,
imageWorkbenchTool,
pendingEcommerceTemplate,
backendHealth,
setUsage,
pushNotification,
setRuntimeNotifications,
setServerNotifications,
setView,
setWorkspaceExpanded,
setImageWorkbenchTool,
setPendingEcommerceTemplate,
setBackendHealth,
markNotificationRead,
markAllNotificationsRead,
clearAppState,
} = useAppStore(useShallow((s) => ({
usage: s.usage,
runtimeNotifications: s.runtimeNotifications,
serverNotifications: s.serverNotifications,
activeView: s.activeView,
workspaceExpanded: s.workspaceExpanded,
imageWorkbenchTool: s.imageWorkbenchTool,
pendingEcommerceTemplate: s.pendingEcommerceTemplate,
backendHealth: s.backendHealth,
setUsage: s.setUsage,
pushNotification: s.pushNotification,
setRuntimeNotifications: s.setRuntimeNotifications,
setServerNotifications: s.setServerNotifications,
setView: s.setView,
setWorkspaceExpanded: s.setWorkspaceExpanded,
setImageWorkbenchTool: s.setImageWorkbenchTool,
setPendingEcommerceTemplate: s.setPendingEcommerceTemplate,
setBackendHealth: s.setBackendHealth,
markNotificationRead: s.markNotificationRead,
markAllNotificationsRead: s.markAllNotificationsRead,
clearAppState: s.clearAppState,
})));
const [ecommerceEverMounted, setEcommerceEverMounted] = useState(false);
const isEcommerceActive = activeView === "ecommerce" || activeView === "ecommerceHub";
@@ -296,6 +373,12 @@ function App() {
if (isEcommerceActive && !ecommerceEverMounted) setEcommerceEverMounted(true);
}, [isEcommerceActive]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (LEGACY_PAGE_STYLE_VIEWS.has(activeView) || ecommerceEverMounted) {
void loadLegacyPageStyles();
}
}, [activeView, ecommerceEverMounted]);
// Dismiss boot splash after first render
useEffect(() => {
const splash = document.getElementById("app-boot-splash");
@@ -347,24 +430,24 @@ function App() {
const navItems = useMemo<WebNavItem[]>(
() => [
{ key: "home", label: "首页", hint: "项目入口", icon: <HomeOutlined /> },
{ key: "workbench", label: "生成", hint: "对话生成页面", icon: <RobotOutlined /> },
{ key: "home", label: "首页", hint: "项目入口", icon: <ShellIcon name="home" /> },
{ key: "workbench", label: "生成", hint: "对话生成页面", icon: <ShellIcon name="robot" /> },
{
key: "ecommerce",
label: "电商生成",
hint: "AI创作与海报生成",
icon: <ShoppingOutlined />,
icon: <ShellIcon name="shopping" />,
},
{ key: "canvas", label: "画布", hint: "进入自由画布编排", icon: <BranchesOutlined /> },
{ key: "community", label: "社区", hint: "案例分享与导入", icon: <GlobalOutlined /> },
{ key: "scriptTokens", label: "剧本评分", hint: "剧本评分系统", icon: <BarChartOutlined /> },
{ key: "tokenUsage", label: "Token消耗", hint: "成员、服务与调用记录", icon: <WalletOutlined /> },
{ key: "providerHealth", label: "服务商健康", hint: "AI 服务商状态与监控", icon: <HeartOutlined /> },
{ key: "assets", label: "资产库", hint: "角色、场景、道具", icon: <FolderOpenOutlined /> },
{ key: "agent", label: "Agent", hint: "拆解与规划", icon: <RobotOutlined /> },
{ key: "digitalHuman", label: "数字人", hint: "口播与人像生成", icon: <CustomerServiceOutlined /> },
{ key: "characterMix", label: "角色迁移", hint: "人物视频迁移", icon: <SwapOutlined /> },
{ key: "more", label: "工具盒", hint: "图像与镜头工具", icon: <ToolOutlined /> },
{ key: "canvas", label: "画布", hint: "进入自由画布编排", icon: <ShellIcon name="branches" /> },
{ key: "community", label: "社区", hint: "案例分享与导入", icon: <ShellIcon name="global" /> },
{ key: "scriptTokens", label: "剧本评分", hint: "剧本评分系统", icon: <ShellIcon name="bar-chart" /> },
{ key: "tokenUsage", label: "Token消耗", hint: "成员、服务与调用记录", icon: <ShellIcon name="wallet" /> },
{ key: "providerHealth", label: "服务商健康", hint: "AI 服务商状态与监控", icon: <ShellIcon name="heart" /> },
{ key: "assets", label: "资产库", hint: "角色、场景、道具", icon: <ShellIcon name="folder" /> },
{ key: "agent", label: "Agent", hint: "拆解与规划", icon: <ShellIcon name="robot" /> },
{ key: "digitalHuman", label: "数字人", hint: "口播与人像生成", icon: <ShellIcon name="customer-service" /> },
{ key: "characterMix", label: "角色迁移", hint: "人物视频迁移", icon: <ShellIcon name="swap" /> },
{ key: "more", label: "工具盒", hint: "图像与镜头工具", icon: <ShellIcon name="tool" /> },
],
[],
);
@@ -1090,6 +1173,30 @@ function App() {
case "ecommerce":
case "ecommerceHub":
return null;
case "ecommerceTemplates":
return (
<EcommerceTemplatesPage
projects={projects}
onOpenMore={() => handleSetView("more")}
onOpenEcommerce={() => handleSetView("ecommerce")}
onSelectTemplate={(template) => {
setPendingEcommerceTemplate(template);
handleSetView("ecommerce");
}}
onStartCreate={handleStartTemplateCanvasCreate}
onOpenProject={handleOpenProject}
onDeleteProject={handleDeleteProject}
/>
);
case "sizeTemplate":
return (
<SizeTemplatePage
isAuthenticated={Boolean(session)}
onOpenMore={() => handleSetView("more")}
onOpenEcommerce={() => handleSetView("ecommerce")}
onSelectView={handleSetView}
/>
);
case "digitalHuman":
return (
<DigitalHumanPage
@@ -1339,7 +1446,7 @@ function App() {
/>
<section className="project-delete-modal__panel">
<span className="project-delete-modal__icon">
<DeleteOutlined />
<ShellIcon name="delete" />
</span>
<h2 id="project-delete-title"></h2>
<p>{pendingDeleteProject.name}</p>