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:
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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`;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user