diff --git a/apps/web/src/pages/assets/components/AssetUploadZone.tsx b/apps/web/src/pages/assets/components/AssetUploadZone.tsx index 34f803b32..b40b21e96 100644 --- a/apps/web/src/pages/assets/components/AssetUploadZone.tsx +++ b/apps/web/src/pages/assets/components/AssetUploadZone.tsx @@ -11,10 +11,7 @@ export interface AssetUploadZoneProps { onUpload: (file: File) => void } -export const AssetUploadZone: React.FC = ({ - uploading, - onUpload, -}) => { +export const AssetUploadZone: React.FC = ({ uploading, onUpload }) => { return ( { diff --git a/apps/web/src/pages/assets/hooks/asset-operations/batchOperations.ts b/apps/web/src/pages/assets/hooks/asset-operations/batchOperations.ts index b78a2dcde..41e585758 100644 --- a/apps/web/src/pages/assets/hooks/asset-operations/batchOperations.ts +++ b/apps/web/src/pages/assets/hooks/asset-operations/batchOperations.ts @@ -17,7 +17,11 @@ interface UseBatchDeleteOptions { showResult: (result: BatchOperationResult, title: string, clear?: boolean) => void } -export const useBatchDelete = ({ selectedIds, invalidateAssets, showResult }: UseBatchDeleteOptions) => { +export const useBatchDelete = ({ + selectedIds, + invalidateAssets, + showResult, +}: UseBatchDeleteOptions) => { const [batchLoading, setBatchLoading] = useState(false) const handleBatchDelete = useCallback(async () => { @@ -135,7 +139,11 @@ interface UseBatchClassifyOptions { showResult: (result: BatchOperationResult, title: string, clear?: boolean) => void } -export const useBatchClassify = ({ selectedIds, queryClient, showResult }: UseBatchClassifyOptions) => { +export const useBatchClassify = ({ + selectedIds, + queryClient, + showResult, +}: UseBatchClassifyOptions) => { const [classifyModalOpen, setClassifyModalOpen] = useState(false) const [batchCategory, setBatchCategory] = useState("") const [batchLoading, setBatchLoading] = useState(false) diff --git a/apps/web/src/pages/assets/hooks/asset-operations/useSingleOperations.ts b/apps/web/src/pages/assets/hooks/asset-operations/useSingleOperations.ts index f42be42b1..a0bc07ada 100644 --- a/apps/web/src/pages/assets/hooks/asset-operations/useSingleOperations.ts +++ b/apps/web/src/pages/assets/hooks/asset-operations/useSingleOperations.ts @@ -73,10 +73,7 @@ interface UseBatchHelpersOptions { * 批量操作辅助函数 * 刷新数据、显示操作结果 */ -export const useBatchHelpers = ({ - queryClient, - setSelectedIds, -}: UseBatchHelpersOptions) => { +export const useBatchHelpers = ({ queryClient, setSelectedIds }: UseBatchHelpersOptions) => { const invalidateAssets = useCallback(() => { queryClient.invalidateQueries({ queryKey: ["assets"] }) queryClient.invalidateQueries({ queryKey: ["asset-libraries"] }) diff --git a/apps/web/src/pages/assets/hooks/useAssetOperations.ts b/apps/web/src/pages/assets/hooks/useAssetOperations.ts index 160ca3d23..84b78e4b5 100644 --- a/apps/web/src/pages/assets/hooks/useAssetOperations.ts +++ b/apps/web/src/pages/assets/hooks/useAssetOperations.ts @@ -78,7 +78,10 @@ export function useAssetOperations({ selectedIds, setSelectedIds }: UseAssetOper // 取任一批量操作的 loading 状态(任意一个在加载都算加载中) const batchLoading = - deleteLoading || tagResult.batchLoading || classifyResult.batchLoading || markResult.batchLoading + deleteLoading || + tagResult.batchLoading || + classifyResult.batchLoading || + markResult.batchLoading /* ── 关闭结果 Drawer ── */ const handleResultDrawerClose = useCallback(() => { diff --git a/apps/web/src/pages/editing-planner/components/GenerationProgressModal.tsx b/apps/web/src/pages/editing-planner/components/GenerationProgressModal.tsx index df7605e93..2f66ce31e 100644 --- a/apps/web/src/pages/editing-planner/components/GenerationProgressModal.tsx +++ b/apps/web/src/pages/editing-planner/components/GenerationProgressModal.tsx @@ -1,238 +1,6 @@ /** - * 生成进度弹窗 — 任务 2.17 - * 三阶段 UI:setup(配置)→ progress(进度轮询)→ completed / failed(结果) - * V21 设计系统,CSS 类名前缀 ep-gen- + * 生成进度弹窗 — 入口文件(向后兼容) + * 实际实现已移至 ./generation-progress-modal/ 目录 */ -import React, { useEffect, useRef } from "react" -import { Modal, Button } from "@/components/ui" -import type { TaskItem } from "@/api/tasks" - -/* ──────────── 类型 ──────────── */ - -export type GenPhase = "setup" | "progress" | "completed" | "failed" - -export interface GenerationProgressModalProps { - open: boolean - phase: GenPhase - - /* setup 阶段 */ - voiceoverDuration: number | null - estimatedDuration: number - onDurationChange: (v: number | null) => void - onGenerate: () => void - - /* progress / 结果阶段 */ - task: TaskItem | null - - /* 通用 */ - submitting: boolean - onCancel: () => void - onRetry?: () => void - onClose?: () => void -} - -/* ──────────── 步骤文案映射 ──────────── */ - -const STEP_LABELS: Record = { - queued: "排队中…", - preparing: "准备素材…", - generating_video: "渲染视频中…", - adding_effects: "添加特效…", - composing: "合成中…", - encoding: "编码输出中…", - completed: "生成完成!", - failed: "生成失败", -} - -const getStepLabel = (step: string) => STEP_LABELS[step] || step.replace(/_/g, " ") - -/* ──────────── 状态徽标颜色 ──────────── */ - -const STATUS_COLOR: Record = { - queued: "#6b7280", - pending: "#6b7280", - preparing: "#f59e0b", - generating_video: "#4f46e5", - adding_effects: "#7c3aed", - composing: "#2563eb", - encoding: "#0891b2", - completed: "#10b981", - failed: "#ef4444", -} - -/* ──────────── 组件 ──────────── */ - -const GenerationProgressModal: React.FC = ({ - open, - phase, - voiceoverDuration, - estimatedDuration, - onDurationChange, - onGenerate, - task, - submitting, - onCancel, - onRetry, - onClose, -}) => { - /* 关闭弹窗时重置(避免下次打开残留旧状态) */ - const prevOpen = useRef(false) - useEffect(() => { - if (prevOpen.current && !open) { - /* modal just closed — parent handles reset */ - } - prevOpen.current = open - }, [open]) - - const progress = task?.progress ?? 0 - const status = task?.status ?? "" - const currentStep = task?.current_step ?? "" - const userMessage = task?.user_message ?? "" - const errorMessage = task?.error_message ?? "" - const retryable = task?.retryable ?? false - - /* ── setup 阶段 ── */ - if (phase === "setup") { - return ( - -
- - { - const v = e.target.value ? Number(e.target.value) : null - onDurationChange(v) - }} - min={1} - max={600} - /> -
- 预估总时长:{estimatedDuration}s -
-
-
- ) - } - - /* ── progress 阶段 ── */ - if (phase === "progress") { - const stepColor = STATUS_COLOR[status] || STATUS_COLOR[currentStep] || "#4f46e5" - - return ( - -
- {/* 进度环 */} -
- - - - - - {progress}% - -
- - {/* 当前步骤 */} -
- {userMessage || getStepLabel(currentStep) || "处理中…"} -
- - {/* 进度条 */} -
-
-
- - {/* 任务 ID */} - {task?.id &&
任务 ID: {task.id}
} -
- - ) - } - - /* ── completed 阶段 ── */ - if (phase === "completed") { - return ( - -
-
🎉
-
视频生成完成!
- {userMessage &&
{userMessage}
} -
- -
-
-
- ) - } - - /* ── failed 阶段 ── */ - return ( - -
-
😥
-
视频生成失败
- {(errorMessage || userMessage) && ( -
- {errorMessage || userMessage} -
- )} -
- {retryable && onRetry && ( - - )} - -
-
-
- ) -} - -export default GenerationProgressModal +export { default } from "./generation-progress-modal" +export type { GenPhase, GenerationProgressModalProps } from "./generation-progress-modal" diff --git a/apps/web/src/pages/editing-planner/components/generation-progress-modal/ProgressPhase.tsx b/apps/web/src/pages/editing-planner/components/generation-progress-modal/ProgressPhase.tsx new file mode 100644 index 000000000..0e2597af5 --- /dev/null +++ b/apps/web/src/pages/editing-planner/components/generation-progress-modal/ProgressPhase.tsx @@ -0,0 +1,65 @@ +import React from "react" +import { Modal } from "@/components/ui" +import type { TaskItem } from "@/api/tasks" +import { getStepLabel, getStatusColor } from "./constants" + +interface ProgressPhaseProps { + open: boolean + task: TaskItem | null + onCancel: () => void +} + +/** progress(进度轮询)阶段弹窗 */ +export const ProgressPhase: React.FC = ({ open, task, onCancel }) => { + const progress = task?.progress ?? 0 + const status = task?.status ?? "" + const currentStep = task?.current_step ?? "" + const userMessage = task?.user_message ?? "" + const stepColor = getStatusColor(status, currentStep) + + return ( + +
+ {/* 进度环 */} +
+ + + + + + {progress}% + +
+ + {/* 当前步骤 */} +
+ {userMessage || getStepLabel(currentStep) || "处理中…"} +
+ + {/* 进度条 */} +
+
+
+ + {/* 任务 ID */} + {task?.id &&
任务 ID: {task.id}
} +
+ + ) +} diff --git a/apps/web/src/pages/editing-planner/components/generation-progress-modal/ResultPhase.tsx b/apps/web/src/pages/editing-planner/components/generation-progress-modal/ResultPhase.tsx new file mode 100644 index 000000000..14af7f83f --- /dev/null +++ b/apps/web/src/pages/editing-planner/components/generation-progress-modal/ResultPhase.tsx @@ -0,0 +1,82 @@ +import React from "react" +import { Modal, Button } from "@/components/ui" +import type { TaskItem } from "@/api/tasks" + +interface ResultPhaseProps { + open: boolean + phase: "completed" | "failed" + task: TaskItem | null + onCancel: () => void + onRetry?: () => void + onClose?: () => void +} + +/** completed / failed(结果)阶段弹窗 */ +export const ResultPhase: React.FC = ({ + open, + phase, + task, + onCancel, + onRetry, + onClose, +}) => { + const userMessage = task?.user_message ?? "" + const errorMessage = task?.error_message ?? "" + const retryable = task?.retryable ?? false + const handleClose = onClose || onCancel + + if (phase === "completed") { + return ( + +
+
🎉
+
视频生成完成!
+ {userMessage &&
{userMessage}
} +
+ +
+
+
+ ) + } + + return ( + +
+
😥
+
视频生成失败
+ {(errorMessage || userMessage) && ( +
+ {errorMessage || userMessage} +
+ )} +
+ {retryable && onRetry && ( + + )} + +
+
+
+ ) +} diff --git a/apps/web/src/pages/editing-planner/components/generation-progress-modal/SetupPhase.tsx b/apps/web/src/pages/editing-planner/components/generation-progress-modal/SetupPhase.tsx new file mode 100644 index 000000000..0c7e3f799 --- /dev/null +++ b/apps/web/src/pages/editing-planner/components/generation-progress-modal/SetupPhase.tsx @@ -0,0 +1,53 @@ +import React from "react" +import { Modal } from "@/components/ui" + +interface SetupPhaseProps { + open: boolean + voiceoverDuration: number | null + estimatedDuration: number + submitting: boolean + onDurationChange: (v: number | null) => void + onGenerate: () => void + onCancel: () => void +} + +/** setup(配置)阶段弹窗 */ +export const SetupPhase: React.FC = ({ + open, + voiceoverDuration, + estimatedDuration, + submitting, + onDurationChange, + onGenerate, + onCancel, +}) => ( + +
+ + { + const v = e.target.value ? Number(e.target.value) : null + onDurationChange(v) + }} + min={1} + max={600} + /> +
+ 预估总时长:{estimatedDuration}s +
+
+
+) diff --git a/apps/web/src/pages/editing-planner/components/generation-progress-modal/constants.ts b/apps/web/src/pages/editing-planner/components/generation-progress-modal/constants.ts new file mode 100644 index 000000000..eeecef559 --- /dev/null +++ b/apps/web/src/pages/editing-planner/components/generation-progress-modal/constants.ts @@ -0,0 +1,31 @@ +/* ──────────── 步骤文案映射 ──────────── */ + +export const STEP_LABELS: Record = { + queued: "排队中…", + preparing: "准备素材…", + generating_video: "渲染视频中…", + adding_effects: "添加特效…", + composing: "合成中…", + encoding: "编码输出中…", + completed: "生成完成!", + failed: "生成失败", +} + +export const getStepLabel = (step: string) => STEP_LABELS[step] || step.replace(/_/g, " ") + +/* ──────────── 状态徽标颜色 ──────────── */ + +export const STATUS_COLOR: Record = { + queued: "#6b7280", + pending: "#6b7280", + preparing: "#f59e0b", + generating_video: "#4f46e5", + adding_effects: "#7c3aed", + composing: "#2563eb", + encoding: "#0891b2", + completed: "#10b981", + failed: "#ef4444", +} + +export const getStatusColor = (status: string, currentStep: string) => + STATUS_COLOR[status] || STATUS_COLOR[currentStep] || "#4f46e5" diff --git a/apps/web/src/pages/editing-planner/components/generation-progress-modal/index.tsx b/apps/web/src/pages/editing-planner/components/generation-progress-modal/index.tsx new file mode 100644 index 000000000..14b4f94b5 --- /dev/null +++ b/apps/web/src/pages/editing-planner/components/generation-progress-modal/index.tsx @@ -0,0 +1,70 @@ +/** + * 生成进度弹窗 — 任务 2.17 + * 三阶段 UI:setup(配置)→ progress(进度轮询)→ completed / failed(结果) + * V21 设计系统,CSS 类名前缀 ep-gen- + */ +import React, { useEffect, useRef } from "react" +import type { GenPhase, GenerationProgressModalProps } from "./types" +import { SetupPhase } from "./SetupPhase" +import { ProgressPhase } from "./ProgressPhase" +import { ResultPhase } from "./ResultPhase" + +/* 重新导出类型,保持向后兼容 */ +export type { GenPhase, GenerationProgressModalProps } + +const GenerationProgressModal: React.FC = ({ + open, + phase, + voiceoverDuration, + estimatedDuration, + onDurationChange, + onGenerate, + task, + submitting, + onCancel, + onRetry, + onClose, +}) => { + /* 关闭弹窗时重置(避免下次打开残留旧状态) */ + const prevOpen = useRef(false) + useEffect(() => { + if (prevOpen.current && !open) { + /* modal just closed — parent handles reset */ + } + prevOpen.current = open + }, [open]) + + /* setup 阶段 */ + if (phase === "setup") { + return ( + + ) + } + + /* progress 阶段 */ + if (phase === "progress") { + return + } + + /* completed / failed 阶段 */ + return ( + + ) +} + +export default GenerationProgressModal diff --git a/apps/web/src/pages/editing-planner/components/generation-progress-modal/types.ts b/apps/web/src/pages/editing-planner/components/generation-progress-modal/types.ts new file mode 100755 index 000000000..5e0b3990d --- /dev/null +++ b/apps/web/src/pages/editing-planner/components/generation-progress-modal/types.ts @@ -0,0 +1,23 @@ +import type { TaskItem } from "@/api/tasks" + +export type GenPhase = "setup" | "progress" | "completed" | "failed" + +export interface GenerationProgressModalProps { + open: boolean + phase: GenPhase + + /* setup 阶段 */ + voiceoverDuration: number | null + estimatedDuration: number + onDurationChange: (v: number | null) => void + onGenerate: () => void + + /* progress / 结果阶段 */ + task: TaskItem | null + + /* 通用 */ + submitting: boolean + onCancel: () => void + onRetry?: () => void + onClose?: () => void +} diff --git a/apps/web/src/pages/titles/hooks/useTitleEdit.ts b/apps/web/src/pages/titles/hooks/useTitleEdit.ts index 40c11ea1d..d1b48286a 100644 --- a/apps/web/src/pages/titles/hooks/useTitleEdit.ts +++ b/apps/web/src/pages/titles/hooks/useTitleEdit.ts @@ -59,7 +59,7 @@ export const useTitleEdit = ({ updateMutation, createMutation }: UseTitleEditPro message.success("标题创建成功") }, }) - }, [newTitleContent, newTitleType, createMutation]) + }, [newTitleContent, createMutation]) /* 关闭新建弹窗 */ const handleCloseCreateModal = useCallback(() => { diff --git a/apps/web/src/pages/voice-materials/hooks/useVoiceMaterials/useVoiceMaterialActions.ts b/apps/web/src/pages/voice-materials/hooks/useVoiceMaterials/useVoiceMaterialActions.ts index f59b2e114..bb2be1a54 100644 --- a/apps/web/src/pages/voice-materials/hooks/useVoiceMaterials/useVoiceMaterialActions.ts +++ b/apps/web/src/pages/voice-materials/hooks/useVoiceMaterials/useVoiceMaterialActions.ts @@ -213,4 +213,4 @@ export function useVoiceMaterialActions({ handleEdit, handleDelete, } -} \ No newline at end of file +}