From 8637ed15764695f9dcdc7cfa9376adb4dcc6a550 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 16 Sep 2026 16:04:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(ux):=20#1894=20=E5=88=A0=E9=99=A4=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E5=BA=93=E5=85=A5=E5=8F=A3=20+=20=E6=96=87=E6=A1=88?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=94=B9=E6=A0=87=E9=A2=98=20+=20AI=E6=94=B9?= =?UTF-8?q?=E5=86=99UX=20+=20=E6=B8=85=E7=90=86=E6=97=A7=E5=AE=9A=E4=BB=B7?= =?UTF-8?q?=E7=A1=AC=E7=BC=96=E7=A0=81=20(#1965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: xiaoxia Co-committed-by: xiaoxia --- .../components/layout/PageHead/constants.ts | 1 - apps/web/src/config/navigation.ts | 13 - apps/web/src/pages/scripts/ScriptLibrary.tsx | 282 ++-------- apps/web/src/pages/scripts/scripts.css | 48 ++ .../subscription/UpgradeSubscription.tsx | 62 ++- apps/web/src/pages/subscription/constants.ts | 37 +- apps/web/src/pages/titles/TitleLibrary.tsx | 137 ----- .../title-library/AIGenerateModal.tsx | 121 ----- .../title-library/CategorySidebar.tsx | 44 -- .../title-library/CreateTitleModal.tsx | 89 ---- .../components/title-library/FilterBar.tsx | 76 --- .../components/title-library/TitleCard.tsx | 121 ----- .../components/title-library/TitleGrid.tsx | 63 --- .../pages/titles/constants/titleLibrary.ts | 34 -- apps/web/src/pages/titles/hooks/useTitleAI.ts | 84 --- .../src/pages/titles/hooks/useTitleEdit.ts | 87 ---- .../titles/hooks/useTitleLibrary/index.ts | 55 -- .../hooks/useTitleLibrary/useTitleActions.ts | 34 -- .../hooks/useTitleLibrary/useTitleData.ts | 47 -- .../hooks/useTitleLibrary/useTitleFilters.ts | 77 --- .../useTitleLibrary/useTitleMutations.ts | 34 -- apps/web/src/pages/titles/titles.css | 488 ------------------ .../src/pages/titles/types/titleLibrary.ts | 20 - .../src/pages/titles/utils/titleLibrary.ts | 49 -- apps/web/src/router/appRoutes.tsx | 4 - apps/web/src/test/pages/TitleLibrary.test.tsx | 90 ---- apps/web/src/test/pages/titles/smoke.test.tsx | 29 -- tests/unit/test_extract_from_douyin_errors.py | 70 ++- 28 files changed, 186 insertions(+), 2110 deletions(-) delete mode 100644 apps/web/src/pages/titles/TitleLibrary.tsx delete mode 100644 apps/web/src/pages/titles/components/title-library/AIGenerateModal.tsx delete mode 100644 apps/web/src/pages/titles/components/title-library/CategorySidebar.tsx delete mode 100644 apps/web/src/pages/titles/components/title-library/CreateTitleModal.tsx delete mode 100644 apps/web/src/pages/titles/components/title-library/FilterBar.tsx delete mode 100644 apps/web/src/pages/titles/components/title-library/TitleCard.tsx delete mode 100644 apps/web/src/pages/titles/components/title-library/TitleGrid.tsx delete mode 100644 apps/web/src/pages/titles/constants/titleLibrary.ts delete mode 100644 apps/web/src/pages/titles/hooks/useTitleAI.ts delete mode 100644 apps/web/src/pages/titles/hooks/useTitleEdit.ts delete mode 100644 apps/web/src/pages/titles/hooks/useTitleLibrary/index.ts delete mode 100644 apps/web/src/pages/titles/hooks/useTitleLibrary/useTitleActions.ts delete mode 100644 apps/web/src/pages/titles/hooks/useTitleLibrary/useTitleData.ts delete mode 100644 apps/web/src/pages/titles/hooks/useTitleLibrary/useTitleFilters.ts delete mode 100644 apps/web/src/pages/titles/hooks/useTitleLibrary/useTitleMutations.ts delete mode 100644 apps/web/src/pages/titles/titles.css delete mode 100644 apps/web/src/pages/titles/types/titleLibrary.ts delete mode 100644 apps/web/src/pages/titles/utils/titleLibrary.ts delete mode 100644 apps/web/src/test/pages/TitleLibrary.test.tsx delete mode 100644 apps/web/src/test/pages/titles/smoke.test.tsx diff --git a/apps/web/src/components/layout/PageHead/constants.ts b/apps/web/src/components/layout/PageHead/constants.ts index 1082d633b..f8aa5ad16 100644 --- a/apps/web/src/components/layout/PageHead/constants.ts +++ b/apps/web/src/components/layout/PageHead/constants.ts @@ -4,7 +4,6 @@ export const ROUTE_TITLE_MAP: Record = { "/app/generate": "智能剪辑", "/app/assets": "视频库", "/app/voices": "配音库", - "/app/titles": "标题库", "/app/products": "成片库", "/app/templates": "模板库", "/app/history": "任务历史", diff --git a/apps/web/src/config/navigation.ts b/apps/web/src/config/navigation.ts index 99bc5107d..846a246a7 100644 --- a/apps/web/src/config/navigation.ts +++ b/apps/web/src/config/navigation.ts @@ -6,7 +6,6 @@ import React from "react" import { DashboardOutlined, FileOutlined, - FileTextOutlined, AudioOutlined, EditOutlined, VideoCameraOutlined, @@ -51,12 +50,6 @@ export const NAV_ITEMS: NavItem[] = [ path: "/app/assets", icon: React.createElement(FileOutlined), }, - { - key: "titles", - label: "标题库", - path: "/app/titles", - icon: React.createElement(FileTextOutlined), - }, { key: "scripts", label: "文案库", @@ -160,12 +153,6 @@ export const NAV_GROUPS: NavGroup[] = [ path: "/app/voices", icon: React.createElement(AudioOutlined), }, - { - key: "titles", - label: "标题库", - path: "/app/titles", - icon: React.createElement(FileTextOutlined), - }, { key: "scripts", label: "文案库", diff --git a/apps/web/src/pages/scripts/ScriptLibrary.tsx b/apps/web/src/pages/scripts/ScriptLibrary.tsx index 123e4a5ef..54c942c0e 100644 --- a/apps/web/src/pages/scripts/ScriptLibrary.tsx +++ b/apps/web/src/pages/scripts/ScriptLibrary.tsx @@ -1,19 +1,17 @@ /** - * 文案库页面 — Issue #1811(v2 完整版) + #1893 AI 能力 + * 文案库页面 — Issue #1811(#1894 方向修正后) * 功能: * - 列表页:卡片列表,搜索(标题/正文)、分类标签筛选、分页 - * 每条卡片展示:title、content 前 100 字摘要、title_text、分类 Tag、tags、使用次数、时间 + * 每条卡片展示:title(标题)、content 前 100 字摘要、分类 Tag、tags、使用次数、时间 * 操作:编辑 / 删除 / 复制 / 使用(跳创作页预填) - * - 新建/编辑弹窗:title、content 多行、segments(按空行自动拆分+手动编辑)、title_text、title_category、 - * title_config(字体/颜色/位置/字号)、tags - * - #1893 AI 能力: + * - 新建/编辑弹窗:标题(原"名称")、正文(含 AI 改写)、分类、标签 + * - #1893/#1894 AI 能力: * - 顶部「🎬 从抖音提取」按钮 → 输入抖音链接 → ASR 提取文案 → 自动填充到新建弹窗 - * - 新建/编辑弹窗中 content 下方「✨ AI 改写」按钮(带风格选择) → 对比弹窗让用户确认 - * - title 旁「✨ AI 生成标题」按钮 → 候选列表一键填入 + * - 正文下方「✨ AI 改写」按钮 → 点击直接执行(美化 loading spinner + "正在改写..."), + * 成功自动替换正文并 toast「改写成功」1s 自动关闭;失败 toast 错误 + * - 标题旁「✨ AI 生成标题」按钮 → 候选列表一键填入 * - 删除确认(Popconfirm) * - 对接 api/scripts CRUD(mock 阶段 SCRIPTS_API_MOCK=true,AI 接口始终走真实 API) - * - * 风格对齐标题库(.xx-scripts-* 命名,沿用 CSS 变量) */ import React, { useCallback, useEffect, useState } from "react" import { @@ -22,8 +20,6 @@ import { Empty, Form, Input, - InputNumber, - List, Modal, Pagination, Popconfirm, @@ -59,13 +55,7 @@ import { aiGenerateTitles, REWRITE_STYLE_OPTIONS, } from "@/api/scripts" -import type { - ScriptItem, - ScriptCategory, - ScriptUpsertRequest, - RewriteStyle, - AiRewriteResponse, -} from "@/api/scripts" +import type { ScriptItem, ScriptCategory, ScriptUpsertRequest, RewriteStyle } from "@/api/scripts" import { SCRIPT_CATEGORY_LABEL } from "@/api/scripts" import "./scripts.css" @@ -81,19 +71,6 @@ const CATEGORY_OPTIONS: { value: ScriptCategory | "all"; label: string }[] = [ })), ] -const FONT_OPTIONS = [ - { value: "default", label: "默认" }, - { value: "bold", label: "粗体" }, - { value: "handwritten", label: "手写" }, - { value: "serif", label: "衬线" }, -] - -const POSITION_OPTIONS = [ - { value: "top", label: "顶部" }, - { value: "center", label: "居中" }, - { value: "bottom", label: "底部" }, -] as const - /** 提取后端返回的错误 detail(全局拦截器可能已弹 toast,但这里再兜一层) */ function extractErrMsg(err: unknown, fallback: string): string { const e = err as { @@ -132,11 +109,9 @@ const ScriptLibrary: React.FC = () => { const [douyinUrl, setDouyinUrl] = useState("") const [douyinLoading, setDouyinLoading] = useState(false) - // AI 改写 - const [rewriteModalOpen, setRewriteModalOpen] = useState(false) + // AI 改写(#1894: 点击直接执行,美化 loading + 1s 自动关闭 toast,不弹确认弹窗) const [rewriteStyle, setRewriteStyle] = useState("口语化") const [rewriteLoading, setRewriteLoading] = useState(false) - const [rewriteResult, setRewriteResult] = useState(null) // AI 生成标题 const [titleGenLoading, setTitleGenLoading] = useState(false) @@ -176,19 +151,8 @@ const ScriptLibrary: React.FC = () => { content: "", segments: [], tags: [], - title_text: "", title_category: "other", - title_config: { - font: "default", - color: "#ffffff", - stroke: "#000000", - position: "center", - size: 48, - bold: true, - italic: false, - }, }) - setRewriteResult(null) setTitleCandidates([]) } @@ -205,17 +169,8 @@ const ScriptLibrary: React.FC = () => { content: item.content, segments: item.segments ?? item.content.split(/\n\n+/).filter(Boolean), tags: item.tags ?? [], - title_text: item.title_text ?? "", title_category: item.title_category ?? "other", - title_config: item.title_config ?? { - font: "default", - color: "#ffffff", - stroke: "#000000", - position: "center", - size: 48, - }, }) - setRewriteResult(null) setTitleCandidates([]) setModalOpen(true) } @@ -223,7 +178,6 @@ const ScriptLibrary: React.FC = () => { const closeModal = () => { setModalOpen(false) setEditing(null) - setRewriteResult(null) setTitleCandidates([]) } @@ -237,9 +191,8 @@ const ScriptLibrary: React.FC = () => { content: values.content, segments: values.segments?.filter(Boolean) ?? values.content.split(/\n\n+/).filter(Boolean), tags: values.tags ?? [], - title_text: values.title_text?.trim() || undefined, title_category: values.title_category, - title_config: values.title_config, + // #1894: 配套标题 / 标题样式配置字段已从 UI 移除,后端即将删除,不再传 } if (editing) { await updateScript(editing.id, payload) @@ -343,17 +296,7 @@ const ScriptLibrary: React.FC = () => { title: "", content: res.text, tags: [], - title_text: "", title_category: "other", - title_config: { - font: "default", - color: "#ffffff", - stroke: "#000000", - position: "center", - size: 48, - bold: true, - italic: false, - }, }) setModalOpen(true) } catch (err) { @@ -363,18 +306,18 @@ const ScriptLibrary: React.FC = () => { } } - /** 执行 AI 改写,结果写入 rewriteResult 让用户对比确认 */ - const handleAiRewrite = async () => { + /** #1894: 执行 AI 改写,完成后自动替换正文并 toast 1 秒关闭 */ + const handleAiRewrite = async (style: RewriteStyle) => { const content = form.getFieldValue("content") as string | undefined if (!content || !content.trim()) { message.warning("请先填写文案正文再改写") return } setRewriteLoading(true) - setRewriteResult(null) try { - const res = await aiRewriteScript({ content, style: rewriteStyle }) - setRewriteResult(res) + const res = await aiRewriteScript({ content, style }) + form.setFieldsValue({ content: res.rewritten }) + message.success({ content: "改写成功", duration: 1 }) } catch (err) { message.error(extractErrMsg(err, "AI 改写失败")) } finally { @@ -382,15 +325,6 @@ const ScriptLibrary: React.FC = () => { } } - /** 应用改写结果:替换 content 字段,关闭改写弹窗 */ - const applyRewrite = () => { - if (!rewriteResult) return - form.setFieldsValue({ content: rewriteResult.rewritten }) - setRewriteResult(null) - setRewriteModalOpen(false) - message.success("已应用改写结果") - } - /** 执行 AI 生成标题,生成候选 */ const handleGenerateTitles = async () => { const content = form.getFieldValue("content") as string | undefined @@ -527,13 +461,6 @@ const ScriptLibrary: React.FC = () => {
{preview(s.content)}
- {s.title_text && ( -
- 配套标题: - {s.title_text} -
- )} - {s.tags && s.tags.length > 0 && (
{ layout="vertical" initialValues={{ title_category: "other", - title_config: { - font: "default", - color: "#ffffff", - stroke: "#000000", - position: "center", - size: 48, - bold: true, - }, }} > - 名称 + 标题 {/* #1893 UX: disabled 时原生 title 在 antd Button 上不触发, 用 Tooltip + span 包裹保证提示可见 */} { } - rules={[{ required: true, message: "请填写文案名称" }, { max: 200 }]} + rules={[{ required: true, message: "请填写视频标题" }, { max: 200 }]} > - + {/* AI 生成标题候选列表 */} @@ -661,9 +580,9 @@ const ScriptLibrary: React.FC = () => {