From 3ccf2c6fa6058c30e1db1f1019953a6027f5829d Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 07:24:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor(generate):=20=E6=8B=86=E5=88=86=20useS?= =?UTF-8?q?tep4Title=20Hook=EF=BC=8C=E6=8C=89=E8=81=8C=E8=B4=A3=E6=8B=86?= =?UTF-8?q?=E4=B8=BA=20AI=20=E7=94=9F=E6=88=90=20+=20=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主文件 251→75 行(-70%),拆分为: - useAiTitleGenerator: AI 标题生成、刷新、选择逻辑 - useTitleStyleUpdaters: 标题文字/位置/字体/样式等所有设置更新 - index: 组合入口 + 标题库数据查询 导入路径保持向后兼容 --- .../src/pages/generate/hooks/useStep4Title.ts | 251 ------------------ .../generate/hooks/useStep4Title/index.ts | 61 +++++ .../useStep4Title/useAiTitleGenerator.ts | 102 +++++++ .../useStep4Title/useTitleStyleUpdaters.ts | 123 +++++++++ 4 files changed, 286 insertions(+), 251 deletions(-) delete mode 100644 apps/web/src/pages/generate/hooks/useStep4Title.ts create mode 100644 apps/web/src/pages/generate/hooks/useStep4Title/index.ts create mode 100644 apps/web/src/pages/generate/hooks/useStep4Title/useAiTitleGenerator.ts create mode 100644 apps/web/src/pages/generate/hooks/useStep4Title/useTitleStyleUpdaters.ts diff --git a/apps/web/src/pages/generate/hooks/useStep4Title.ts b/apps/web/src/pages/generate/hooks/useStep4Title.ts deleted file mode 100644 index eb9957f8a..000000000 --- a/apps/web/src/pages/generate/hooks/useStep4Title.ts +++ /dev/null @@ -1,251 +0,0 @@ -/** - * Step 4 标题设置 Hook - * 封装 AI 标题生成、标题样式设置等逻辑 - */ -import { useState, useCallback, useMemo } from "react" -import { message } from "antd" -import { useQuery } from "@tanstack/react-query" -import { getTitles } from "@/api/titles" -import { TITLE_PRESETS, AI_TITLE_TEMPLATES } from "../constants" -import type { TitleSettings } from "../types" - -interface UseStep4TitleProps { - titleSettings: TitleSettings - onTitleSettingsChange: (settings: TitleSettings) => void -} - -interface AiTitleItem { - title: string - highlight: string - style: "catchy" | "emotional" | "informative" -} - -export function useStep4Title({ titleSettings, onTitleSettingsChange }: UseStep4TitleProps) { - /* ── 标题库 API ── */ - const { data: userTitles = [] } = useQuery({ - queryKey: ["titles"], - queryFn: () => getTitles(), - staleTime: 30_000, - }) - - /* ── AI 标题生成状态 ── */ - const [aiTitleInput, setAiTitleInput] = useState("") - const [aiTitleGenerating, setAiTitleGenerating] = useState(false) - const [aiTitleResults, setAiTitleResults] = useState([]) - const [hasGeneratedTitles, setHasGeneratedTitles] = useState(false) - - /* ── 辅助函数 ── */ - const extractTopic = (text: string): string => { - const keywords = text - .replace(/[,。!?、,.!?]/g, " ") - .split(/\s+/) - .filter(Boolean) - if (keywords.length === 0) return "这个话题" - // 取前3个关键词组合 - return keywords.slice(0, 3).join("") - } - - const getActivePreset = (settings: TitleSettings): string | null => { - for (const p of TITLE_PRESETS) { - if ( - settings.size === p.style.size && - settings.color === p.style.color && - settings.bold === p.style.bold && - settings.italic === p.style.italic && - settings.stroke === p.style.stroke && - settings.shadow === p.style.shadow - ) { - return p.key - } - } - return null - } - - const activePreset = useMemo(() => getActivePreset(titleSettings), [titleSettings]) - - /* ── AI 标题生成 ── */ - const handleGenerateAiTitles = useCallback(async () => { - if (!aiTitleInput.trim()) { - message.warning("请先输入视频描述或关键词") - return - } - setAiTitleGenerating(true) - setHasGeneratedTitles(true) - - // 模拟 AI 生成延迟 - await new Promise((resolve) => setTimeout(resolve, 1200)) - - const topic = extractTopic(aiTitleInput) - const results: AiTitleItem[] = [] - - const styles: Array<"catchy" | "emotional" | "informative"> = [ - "catchy", - "emotional", - "informative", - ] - styles.forEach((style) => { - const templates = AI_TITLE_TEMPLATES[style] - // 每种风格随机选2个 - const shuffled = [...templates].sort(() => Math.random() - 0.5).slice(0, 2) - shuffled.forEach((tpl) => { - const title = tpl.replace(/\{topic\}/g, topic) - const highlights = { - catchy: "吸睛标题", - emotional: "情感共鸣", - informative: "知识干货", - } - results.push({ - title, - highlight: highlights[style], - style, - }) - }) - }) - - // 打乱顺序 - results.sort(() => Math.random() - 0.5) - setAiTitleResults(results) - setAiTitleGenerating(false) - }, [aiTitleInput]) - - const handleSelectAiTitle = useCallback( - (title: string) => { - onTitleSettingsChange({ ...titleSettings, title, aiAutoSelect: false }) - message.success("已选用此标题") - }, - [titleSettings, onTitleSettingsChange], - ) - - const handleRefreshAiTitles = useCallback(async () => { - if (!aiTitleInput.trim()) return - setAiTitleGenerating(true) - await new Promise((resolve) => setTimeout(resolve, 800)) - // 重新生成一批 - const topic = extractTopic(aiTitleInput) - const results: AiTitleItem[] = [] - const styles: Array<"catchy" | "emotional" | "informative"> = [ - "catchy", - "emotional", - "informative", - ] - const highlights = { catchy: "吸睛标题", emotional: "情感共鸣", informative: "知识干货" } - styles.forEach((style) => { - const templates = AI_TITLE_TEMPLATES[style] - const shuffled = [...templates].sort(() => Math.random() - 0.5).slice(0, 2) - shuffled.forEach((tpl) => { - results.push({ - title: tpl.replace(/\{topic\}/g, topic), - highlight: highlights[style], - style, - }) - }) - }) - results.sort(() => Math.random() - 0.5) - setAiTitleResults(results) - setAiTitleGenerating(false) - }, [aiTitleInput]) - - /* ── 标题设置更新 ── */ - const updateTitle = useCallback( - (title: string) => { - onTitleSettingsChange({ ...titleSettings, title }) - }, - [titleSettings, onTitleSettingsChange], - ) - - const toggleAiAutoSelect = useCallback(() => { - onTitleSettingsChange({ ...titleSettings, aiAutoSelect: !titleSettings.aiAutoSelect }) - }, [titleSettings, onTitleSettingsChange]) - - const updatePosition = useCallback( - (position: string) => { - onTitleSettingsChange({ ...titleSettings, position }) - }, - [titleSettings, onTitleSettingsChange], - ) - - const updateFont = useCallback( - (font: string) => { - onTitleSettingsChange({ ...titleSettings, font }) - }, - [titleSettings, onTitleSettingsChange], - ) - - const updateSize = useCallback( - (size: number) => { - onTitleSettingsChange({ ...titleSettings, size }) - }, - [titleSettings, onTitleSettingsChange], - ) - - const updateColor = useCallback( - (color: string) => { - onTitleSettingsChange({ ...titleSettings, color }) - }, - [titleSettings, onTitleSettingsChange], - ) - - const toggleBold = useCallback(() => { - onTitleSettingsChange({ ...titleSettings, bold: !titleSettings.bold }) - }, [titleSettings, onTitleSettingsChange]) - - const toggleItalic = useCallback(() => { - onTitleSettingsChange({ ...titleSettings, italic: !titleSettings.italic }) - }, [titleSettings, onTitleSettingsChange]) - - const toggleStroke = useCallback(() => { - onTitleSettingsChange({ ...titleSettings, stroke: !titleSettings.stroke }) - }, [titleSettings, onTitleSettingsChange]) - - const toggleShadow = useCallback(() => { - onTitleSettingsChange({ ...titleSettings, shadow: !titleSettings.shadow }) - }, [titleSettings, onTitleSettingsChange]) - - const applyPreset = useCallback( - (presetKey: string) => { - const preset = TITLE_PRESETS.find((p) => p.key === presetKey) - if (!preset) return - onTitleSettingsChange({ - ...titleSettings, - size: preset.style.size, - color: preset.style.color, - bold: preset.style.bold, - italic: preset.style.italic, - stroke: preset.style.stroke, - shadow: preset.style.shadow, - }) - }, - [titleSettings, onTitleSettingsChange], - ) - - return { - // 数据 - userTitles, - titleSettings, - aiTitleInput, - setAiTitleInput, - aiTitleGenerating, - aiTitleResults, - hasGeneratedTitles, - activePreset, - titlePresets: TITLE_PRESETS, - // AI 标题操作 - handleGenerateAiTitles, - handleSelectAiTitle, - handleRefreshAiTitles, - // 标题设置操作 - updateTitle, - toggleAiAutoSelect, - updatePosition, - updateFont, - updateSize, - updateColor, - toggleBold, - toggleItalic, - toggleStroke, - toggleShadow, - applyPreset, - } -} - -export default useStep4Title diff --git a/apps/web/src/pages/generate/hooks/useStep4Title/index.ts b/apps/web/src/pages/generate/hooks/useStep4Title/index.ts new file mode 100644 index 000000000..eb8114623 --- /dev/null +++ b/apps/web/src/pages/generate/hooks/useStep4Title/index.ts @@ -0,0 +1,61 @@ +import { useQuery } from "@tanstack/react-query" +import { getTitles } from "@/api/titles" +import type { TitleSettings } from "../../types" +import { useAiTitleGenerator } from "./useAiTitleGenerator" +import { useTitleStyleUpdaters } from "./useTitleStyleUpdaters" + +interface UseStep4TitleProps { + titleSettings: TitleSettings + onTitleSettingsChange: (settings: TitleSettings) => void +} + +/** + * Step 4 标题设置 Hook + * 封装 AI 标题生成、标题样式设置等逻辑 + */ +export function useStep4Title({ titleSettings, onTitleSettingsChange }: UseStep4TitleProps) { + // 标题库数据 + const { data: userTitles = [] } = useQuery({ + queryKey: ["titles"], + queryFn: () => getTitles(), + staleTime: 30_000, + }) + + // AI 标题生成 + const aiGenerator = useAiTitleGenerator({ titleSettings, onTitleSettingsChange }) + + // 样式更新 + const styleUpdaters = useTitleStyleUpdaters({ titleSettings, onTitleSettingsChange }) + + return { + // 数据 + userTitles, + titleSettings, + // AI 标题状态 + aiTitleInput: aiGenerator.aiTitleInput, + setAiTitleInput: aiGenerator.setAiTitleInput, + aiTitleGenerating: aiGenerator.aiTitleGenerating, + aiTitleResults: aiGenerator.aiTitleResults, + hasGeneratedTitles: aiGenerator.hasGeneratedTitles, + activePreset: styleUpdaters.activePreset, + titlePresets: styleUpdaters.titlePresets, + // AI 标题操作 + handleGenerateAiTitles: aiGenerator.handleGenerateAiTitles, + handleSelectAiTitle: aiGenerator.handleSelectAiTitle, + handleRefreshAiTitles: aiGenerator.handleRefreshAiTitles, + // 标题设置操作 + updateTitle: styleUpdaters.updateTitle, + toggleAiAutoSelect: styleUpdaters.toggleAiAutoSelect, + updatePosition: styleUpdaters.updatePosition, + updateFont: styleUpdaters.updateFont, + updateSize: styleUpdaters.updateSize, + updateColor: styleUpdaters.updateColor, + toggleBold: styleUpdaters.toggleBold, + toggleItalic: styleUpdaters.toggleItalic, + toggleStroke: styleUpdaters.toggleStroke, + toggleShadow: styleUpdaters.toggleShadow, + applyPreset: styleUpdaters.applyPreset, + } +} + +export default useStep4Title diff --git a/apps/web/src/pages/generate/hooks/useStep4Title/useAiTitleGenerator.ts b/apps/web/src/pages/generate/hooks/useStep4Title/useAiTitleGenerator.ts new file mode 100644 index 000000000..0e2f778bd --- /dev/null +++ b/apps/web/src/pages/generate/hooks/useStep4Title/useAiTitleGenerator.ts @@ -0,0 +1,102 @@ +import { useState, useCallback } from "react" +import { message } from "antd" +import { AI_TITLE_TEMPLATES } from "../../constants" +import type { TitleSettings } from "../../types" + +export interface AiTitleItem { + title: string + highlight: string + style: "catchy" | "emotional" | "informative" +} + +interface UseAiTitleGeneratorOptions { + titleSettings: TitleSettings + onTitleSettingsChange: (settings: TitleSettings) => void +} + +/** + * AI 标题生成 Hook + * 封装 AI 标题生成、刷新、选择等逻辑 + */ +export function useAiTitleGenerator({ + titleSettings, + onTitleSettingsChange, +}: UseAiTitleGeneratorOptions) { + const [aiTitleInput, setAiTitleInput] = useState("") + const [aiTitleGenerating, setAiTitleGenerating] = useState(false) + const [aiTitleResults, setAiTitleResults] = useState([]) + const [hasGeneratedTitles, setHasGeneratedTitles] = useState(false) + + const extractTopic = (text: string): string => { + const keywords = text + .replace(/[,。!?、,.!?]/g, " ") + .split(/\s+/) + .filter(Boolean) + if (keywords.length === 0) return "这个话题" + return keywords.slice(0, 3).join("") + } + + const generateTitlesFromTopic = (topic: string): AiTitleItem[] => { + const results: AiTitleItem[] = [] + const styles: Array<"catchy" | "emotional" | "informative"> = [ + "catchy", + "emotional", + "informative", + ] + const highlights = { catchy: "吸睛标题", emotional: "情感共鸣", informative: "知识干货" } + styles.forEach((style) => { + const templates = AI_TITLE_TEMPLATES[style] + const shuffled = [...templates].sort(() => Math.random() - 0.5).slice(0, 2) + shuffled.forEach((tpl) => { + results.push({ + title: tpl.replace(/\{topic\}/g, topic), + highlight: highlights[style], + style, + }) + }) + }) + results.sort(() => Math.random() - 0.5) + return results + } + + const handleGenerateAiTitles = useCallback(async () => { + if (!aiTitleInput.trim()) { + message.warning("请先输入视频描述或关键词") + return + } + setAiTitleGenerating(true) + setHasGeneratedTitles(true) + await new Promise((resolve) => setTimeout(resolve, 1200)) + const topic = extractTopic(aiTitleInput) + setAiTitleResults(generateTitlesFromTopic(topic)) + setAiTitleGenerating(false) + }, [aiTitleInput]) + + const handleSelectAiTitle = useCallback( + (title: string) => { + onTitleSettingsChange({ ...titleSettings, title, aiAutoSelect: false }) + message.success("已选用此标题") + }, + [titleSettings, onTitleSettingsChange], + ) + + const handleRefreshAiTitles = useCallback(async () => { + if (!aiTitleInput.trim()) return + setAiTitleGenerating(true) + await new Promise((resolve) => setTimeout(resolve, 800)) + const topic = extractTopic(aiTitleInput) + setAiTitleResults(generateTitlesFromTopic(topic)) + setAiTitleGenerating(false) + }, [aiTitleInput]) + + return { + aiTitleInput, + setAiTitleInput, + aiTitleGenerating, + aiTitleResults, + hasGeneratedTitles, + handleGenerateAiTitles, + handleSelectAiTitle, + handleRefreshAiTitles, + } +} diff --git a/apps/web/src/pages/generate/hooks/useStep4Title/useTitleStyleUpdaters.ts b/apps/web/src/pages/generate/hooks/useStep4Title/useTitleStyleUpdaters.ts new file mode 100644 index 000000000..4b70269a7 --- /dev/null +++ b/apps/web/src/pages/generate/hooks/useStep4Title/useTitleStyleUpdaters.ts @@ -0,0 +1,123 @@ +import { useCallback, useMemo } from "react" +import { TITLE_PRESETS } from "../../constants" +import type { TitleSettings } from "../../types" + +interface UseTitleStyleUpdatersOptions { + titleSettings: TitleSettings + onTitleSettingsChange: (settings: TitleSettings) => void +} + +/** + * 标题样式更新 Hook + * 封装标题文字、位置、字体、样式等所有设置更新函数 + */ +export function useTitleStyleUpdaters({ + titleSettings, + onTitleSettingsChange, +}: UseTitleStyleUpdatersOptions) { + const getActivePreset = (settings: TitleSettings): string | null => { + for (const p of TITLE_PRESETS) { + if ( + settings.size === p.style.size && + settings.color === p.style.color && + settings.bold === p.style.bold && + settings.italic === p.style.italic && + settings.stroke === p.style.stroke && + settings.shadow === p.style.shadow + ) { + return p.key + } + } + return null + } + + const activePreset = useMemo(() => getActivePreset(titleSettings), [titleSettings]) + + const updateTitle = useCallback( + (title: string) => { + onTitleSettingsChange({ ...titleSettings, title }) + }, + [titleSettings, onTitleSettingsChange], + ) + + const toggleAiAutoSelect = useCallback(() => { + onTitleSettingsChange({ ...titleSettings, aiAutoSelect: !titleSettings.aiAutoSelect }) + }, [titleSettings, onTitleSettingsChange]) + + const updatePosition = useCallback( + (position: string) => { + onTitleSettingsChange({ ...titleSettings, position }) + }, + [titleSettings, onTitleSettingsChange], + ) + + const updateFont = useCallback( + (font: string) => { + onTitleSettingsChange({ ...titleSettings, font }) + }, + [titleSettings, onTitleSettingsChange], + ) + + const updateSize = useCallback( + (size: number) => { + onTitleSettingsChange({ ...titleSettings, size }) + }, + [titleSettings, onTitleSettingsChange], + ) + + const updateColor = useCallback( + (color: string) => { + onTitleSettingsChange({ ...titleSettings, color }) + }, + [titleSettings, onTitleSettingsChange], + ) + + const toggleBold = useCallback(() => { + onTitleSettingsChange({ ...titleSettings, bold: !titleSettings.bold }) + }, [titleSettings, onTitleSettingsChange]) + + const toggleItalic = useCallback(() => { + onTitleSettingsChange({ ...titleSettings, italic: !titleSettings.italic }) + }, [titleSettings, onTitleSettingsChange]) + + const toggleStroke = useCallback(() => { + onTitleSettingsChange({ ...titleSettings, stroke: !titleSettings.stroke }) + }, [titleSettings, onTitleSettingsChange]) + + const toggleShadow = useCallback(() => { + onTitleSettingsChange({ ...titleSettings, shadow: !titleSettings.shadow }) + }, [titleSettings, onTitleSettingsChange]) + + const applyPreset = useCallback( + (presetKey: string) => { + const preset = TITLE_PRESETS.find((p) => p.key === presetKey) + if (!preset) return + onTitleSettingsChange({ + ...titleSettings, + size: preset.style.size, + color: preset.style.color, + bold: preset.style.bold, + italic: preset.style.italic, + stroke: preset.style.stroke, + shadow: preset.style.shadow, + }) + }, + [titleSettings, onTitleSettingsChange], + ) + + return { + activePreset, + titlePresets: TITLE_PRESETS, + updateTitle, + toggleAiAutoSelect, + updatePosition, + updateFont, + updateSize, + updateColor, + toggleBold, + toggleItalic, + toggleStroke, + toggleShadow, + applyPreset, + } +} -- 2.54.0