refactor: 清理未使用参数、移除死代码、聚焦电商核心模块

主要变更概述:
================

1. 清理未使用的函数参数 (TypeScript noUnusedParameters)
------------------------------------------------------
- AppShell.tsx: 移除未使用的 backendHealth prop 及 ServerConnectionHealth 导入
- canvasUtils.ts: 移除 resolveWorkflowVideoModel 的 workflowModel 参数
- canvasWorkflowDeserialize.ts: 同步更新调用方
- CanvasPage.tsx: 移除 resolveWorkflowVideoModel 未使用导入
- HomePage.tsx: 移除 onOpenTokenMonitor、onOpenImageTool 未使用 props
- ToolboxSection.tsx: 移除 onOpenImageTool 未使用 prop 及 WebImageWorkbenchTool 类型导入
- ScriptTokensPage.tsx: 移除 formatReportMarkdown 的 script 参数,更新 2 处调用
- TokenUsagePage.tsx: 移除 onOpenImageTool、onSelectView 未使用 props
- WorkbenchPage.tsx: 移除 renderComposerToolbar 的 showStop 参数,更新 2 处调用

2. 移除未使用的模块和死代码
--------------------------
删除以下未在电商模块中使用的功能模块:
- 画布模块 (canvas/): CanvasPage, canvasUtils, canvasWorkflow* 等
- 主页模块 (home/): HomePage, ToolboxSection, WelcomeSplash 等
- 工作台模块 (workbench/): WorkbenchPage, ConversationSidebar 等
- 社区模块 (community/, community-review/)
- 数字人模块 (digital-human/)
- 图片工作台 (image-workbench/)
- 其他独立工具页: agent, assets, beta-applications, character-mix,
  compliance, dialog-generator, more, profile, provider-health,
  report, resolution-upscale, script-tokens, settings, size-template,
  subtitle-removal, watermark-removal

3. 移除未使用的公共组件
----------------------
- AnimatedPanel, BeforeAfterCompare, BetaApplicationModal
- CookieConsentBanner, DropZone, EmptyState, NotFoundPage
- NotificationCenter, OnboardingTour, OptimizedImage
- PageTransition, RechargeModal, ShellIcon, Skeleton
- StudioToolLayout, TaskStatusBar, WorkspacePageShell

4. 移除未使用的 API 客户端
--------------------------
- betaApplicationClient, communityClient, conversationClient
- draftClient, modelCapabilitiesClient, notificationClient
- projectTaskClient, providerHealthClient, publicConfigClient
- referenceUploadService, reportClient, scriptEvalClient
- uploadWithProgress

5. 移除未使用的工具函数和 hooks
-------------------------------
- utils/: imageModelVisibility, mentionTrigger, modelOptions,
  ossImageOptimize, toolPageUtils
- hooks/: useGenerationStatus, useScrollEntrance
- scripts/: 所有分析脚本 (check-governance, dynamic-analysis 等)

6. 移除未使用的样式文件
----------------------
删除与已移除模块对应的 CSS 文件,保留电商模块专用样式

7. 新增电商模块功能文件
----------------------
+ src/api/generationRecordClient.ts (生成记录客户端)
+ src/features/ecommerce/ecommerceGenerationPersistence.ts (生成持久化)

验证:
- TypeScript 编译 (tsc --noEmit --noUnusedParameters) 零错误通过
- 所有保留文件的功能完整性未受影响
This commit is contained in:
2026-06-12 11:12:55 +08:00
parent 52e704375c
commit 6d93c2b9b8
184 changed files with 2146 additions and 89530 deletions
-40
View File
@@ -1,40 +0,0 @@
export interface ImageModelVisibilityOption {
value: string;
label?: string;
}
export interface ImageModelVisibilitySession {
user?: {
role?: unknown;
enterpriseRole?: unknown;
isEnterpriseAdmin?: unknown;
} | null;
}
function normalizeRole(value: unknown): string {
return typeof value === "string" ? value.trim().toLowerCase() : "";
}
export function isVipImageModelOption(option: ImageModelVisibilityOption): boolean {
const value = String(option.value || "").trim().toLowerCase();
const label = String(option.label || "").trim().toLowerCase();
return value.endsWith("-vip") || value.includes("-vip-") || /\bvip\b/.test(label);
}
export function isAdminImageModelUser(session: ImageModelVisibilitySession | null | undefined): boolean {
const user = session?.user;
if (!user) return false;
return (
normalizeRole(user.role) === "admin" ||
normalizeRole(user.enterpriseRole) === "admin" ||
user.isEnterpriseAdmin === true
);
}
export function filterImageModelOptionsForSession<T extends ImageModelVisibilityOption>(
options: T[],
session: ImageModelVisibilitySession | null | undefined,
): T[] {
if (isAdminImageModelUser(session)) return options;
return options.filter((option) => !isVipImageModelOption(option));
}
-49
View File
@@ -1,49 +0,0 @@
/**
* Shared mention trigger detection.
*
* Detects when the user has typed "@" followed by an optional query
* at the cursor position, and returns the match info for opening
* a mention panel.
*/
/** Characters that BLOCK @ trigger (only @ itself, to prevent @@). */
const BLOCKED_BEFORE_AT = /[@]/;
export interface MentionTriggerMatch {
/** Index of the @ character in the full text. */
atIndex: number;
/** The query text after @ (may be empty). */
query: string;
}
/**
* Check whether the text before the cursor contains an active @ mention trigger.
*
* Rules:
* - @ must be at position 0, or preceded by a boundary character
* (whitespace, Chinese/English punctuation, brackets).
* - The query (text after @ up to the cursor) must not contain
* any boundary character or space.
* - Returns null if no trigger is found.
*/
export function detectMentionTrigger(textBeforeCursor: string): MentionTriggerMatch | null {
const atIdx = textBeforeCursor.lastIndexOf("@");
if (atIdx < 0) return null;
// @ must be at start or not preceded by another @
if (atIdx > 0 && BLOCKED_BEFORE_AT.test(textBeforeCursor[atIdx - 1])) {
return null;
}
const query = textBeforeCursor.slice(atIdx + 1);
// Query must not contain spaces or punctuation that would break a mention token
if (/[\s,。、;:!??!.,;:(){}\[\]<>""''《》【】@]/.test(query)) {
return null;
}
return { atIndex: atIdx, query };
}
/** Token pattern for @图片1, @视频1, @文本1, @音频1, @附件1, @素材1, etc. */
export const MENTION_TOKEN_RE = /@(?:图片|视频|文本|音频|附件|素材)\d+/g;
-72
View File
@@ -1,72 +0,0 @@
/**
* Shared model option utilities.
*
* Single source of truth for image/video quality option lookups and defaults,
* consumed by both the canvas and workbench feature modules.
*/
import type { CanvasOption } from "../features/canvas/canvasTypes";
import {
fallbackVideoQualityOptions,
image4kCapableModels,
imageQualityOptions,
videoDefaultQualityByModel,
videoResolutionByModel,
} from "../features/canvas/canvasConstants";
import { toHappyHorseDisplayModel } from "./happyHorseRouting";
import { toPixverseDisplayModel } from "./pixverseRouting";
import { toViduDisplayModel } from "./viduRouting";
// ─── Image quality ────────────────────────────────────────────────────────────
export function getImageQualityOptions(model: string): CanvasOption[] {
return image4kCapableModels.has(String(model || "").toLowerCase())
? imageQualityOptions
: imageQualityOptions.filter((option) => option.value !== "4K");
}
export function getImageQualityOptionsForContext(
model: string,
context?: { hasReferenceImages?: boolean; isGridMode?: boolean },
): CanvasOption[] {
const options = getImageQualityOptions(model);
const shouldLimitTo2K =
String(model || "").toLowerCase() === "wan2.7-image-pro" &&
(context?.hasReferenceImages || context?.isGridMode);
return shouldLimitTo2K ? options.filter((option) => option.value !== "4K") : options;
}
export function getDefaultImageQuality(model: string): string {
const options = getImageQualityOptions(model);
return options.some((option) => option.value === "2K") ? "2K" : options[0]?.value || "1K";
}
export function getDefaultImageQualityForContext(
model: string,
context?: { hasReferenceImages?: boolean; isGridMode?: boolean },
): string {
const options = getImageQualityOptionsForContext(model, context);
return options.some((option) => option.value === "2K") ? "2K" : options[0]?.value || "1K";
}
// ─── Video quality ────────────────────────────────────────────────────────────
function normalizeVideoModel(model: string): string {
return toPixverseDisplayModel(toViduDisplayModel(toHappyHorseDisplayModel(model)));
}
export function getVideoQualityOptions(model: string): CanvasOption[] {
const normalized = normalizeVideoModel(model);
return videoResolutionByModel[normalized] || fallbackVideoQualityOptions;
}
export function getDefaultVideoQuality(model: string): string {
const options = getVideoQualityOptions(model);
const normalized = normalizeVideoModel(model);
const preferred = videoDefaultQualityByModel[normalized] || "pro";
return options.some((option) => option.value === preferred) ? preferred : options[0]?.value || preferred;
}
export function getVideoQualityLabel(model: string, value: string): string {
return getVideoQualityOptions(model).find((item) => item.value === value)?.label || value;
}
-17
View File
@@ -1,17 +0,0 @@
const OSS_HOST_RE = /\.aliyuncs\.com/;
export function ossThumb(url: string | undefined | null, width = 400): string {
if (!url) return "";
if (!OSS_HOST_RE.test(url)) return url;
if (url.includes("x-oss-process")) return url;
const sep = url.includes("?") ? "&" : "?";
return `${url}${sep}x-oss-process=image/resize,w_${width}/format,webp`;
}
export function ossWebp(url: string | undefined | null): string {
if (!url) return "";
if (!OSS_HOST_RE.test(url)) return url;
if (url.includes("x-oss-process")) return url;
const sep = url.includes("?") ? "&" : "?";
return `${url}${sep}x-oss-process=image/format,webp`;
}
-32
View File
@@ -1,32 +0,0 @@
/** Summarize a URL for display in tool pages. */
export function summarizeUrl(value: string): string {
try {
const url = new URL(value);
const lastSegment = url.pathname.split("/").filter(Boolean).pop();
return lastSegment ? `${url.host}/.../${lastSegment}` : url.host;
} catch {
return value.length > 72 ? `${value.slice(0, 34)}...${value.slice(-28)}` : value;
}
}
/** Format a byte count to human-readable file size. */
export function formatFileSize(bytes: number): string {
if (!Number.isFinite(bytes) || bytes <= 0) return "0 KB";
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
return `${(bytes / 1024).toFixed(1)} KB`;
}
/** Read a File as a data URL string via FileReader. */
export function fileToDataUrl(file: File): Promise<string> {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ""));
reader.onerror = () => reject(new Error("读取素材失败"));
reader.readAsDataURL(file);
});
}
/** Return a Promise that resolves after the given milliseconds. */
export function wait(ms: number): Promise<void> {
return new Promise((resolve) => window.setTimeout(resolve, ms));
}