741eaca358
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m40s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m47s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m46s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m3s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m51s
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
根因:
1. api/scripts 中 SCRIPTS_API_MOCK 被硬编码为 true,文案库/AI数字人/智能剪辑
都拿 mock 数据,用户在文案库新建的文案、AI数字人手动输入的文案在其他
页面选不到。
2. AI数字人页(PanelTitleConfig)、智能剪辑 step4(useStep4Title)仍在
调已废弃的 @/api/titles,标题下拉为空。
3. AI数字人页自维护一份 getScripts 本地实现,返回类型/字段与共享
@/api/scripts 不一致,且未共享 mock 开关。
修复:
- api/scripts/scripts.ts: SCRIPTS_API_MOCK 改为 env 开关(同 POINTS_API_MOCK
模式,默认 false 走真实 API)。
- 删除废弃 api/titles/ 整个目录 + 相关单测(test/api/titles.test.ts、
ai-avatar/components/TitleLibraryModal.tsx 孤儿组件)。
- ai-avatar/api/aiAvatar.ts: 文案库相关方法(getScripts/createScript/
getScript/deleteScript)统一 re-export from @/api/scripts,不再
本地重复实现。
- ai-avatar/types.ts: Script 类型改为 import("@/api/scripts").ScriptItem
别名,保证字段对齐。
- ai-avatar/AiAvatarPage.tsx 内 ScriptSelectModalLazy: getScripts 适配
{ items, total } 分页结构,传 page_size:200 一次拿全。
- ai-avatar/components/PanelScript.tsx: 手动输入 tab 下增加「💾 保存到文案库」
按钮,一键把当前口播文案以正文前20字为默认标题自动入库,解决主人反馈
「历史文案在新文案库找不到」的问题;保存成功 toast 1s 自动关闭。
- ai-avatar/components/PanelTitleConfig.tsx: 标题 AutoComplete 数据源改为
getScripts(),取 scripts[].title 去重作为候选,文案从「📚 标题库」
改为「📚 文案库标题」。
- generate/hooks/useStep4Title/index.ts: userTitles 数据源改为 getScripts(),
映射为 { content: s.title },复用下游消费逻辑(ut.content)。
- test/pages/GeneratePage.test.tsx: 把 @/api/titles mock 替换为
@/api/scripts mock。
待跟进:
- P2 抖音错误提示友好化(等后端 #1963 合入后联调)。
139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
import { useEffect, useRef } from "react"
|
|
import { useQuery } from "@tanstack/react-query"
|
|
// #1894: 标题候选从文案库 scripts[].title 获取,不再调用废弃的 /api/titles
|
|
import { getScripts } from "@/api/scripts"
|
|
import type { TitleSettings } from "../../types"
|
|
import { useAiTitleGenerator } from "./useAiTitleGenerator"
|
|
import { useTitleStyleUpdaters } from "./useTitleStyleUpdaters"
|
|
import { useDraftAutoSave } from "../useDraftAutoSave"
|
|
|
|
interface UseStep4TitleProps {
|
|
titleSettings: TitleSettings
|
|
onTitleSettingsChange: (settings: TitleSettings) => void
|
|
/** 当前选中的模板/草稿 ID,用于自动保存 */
|
|
selectedTemplate?: string
|
|
}
|
|
|
|
/**
|
|
* Step 4 标题设置 Hook
|
|
* 封装 AI 标题生成、标题样式设置等逻辑
|
|
*/
|
|
export function useStep4Title({
|
|
titleSettings,
|
|
onTitleSettingsChange,
|
|
selectedTemplate,
|
|
}: UseStep4TitleProps) {
|
|
// 标题候选(#1894:统一从文案库取 scripts[].title,去重)
|
|
const { data: userTitles = [] } = useQuery({
|
|
queryKey: ["scripts", "titles-source"],
|
|
queryFn: async () => {
|
|
const res = await getScripts({ page_size: 200 })
|
|
const items = Array.isArray(res) ? res : (res.items ?? [])
|
|
return items.map((s) => ({ content: (s.title || "").trim() })).filter((s) => !!s.content)
|
|
},
|
|
staleTime: 30_000,
|
|
})
|
|
|
|
// AI 标题生成
|
|
const {
|
|
aiTitleInput,
|
|
setAiTitleInput,
|
|
aiTitleGenerating,
|
|
aiTitleResults,
|
|
hasGeneratedTitles,
|
|
handleGenerateAiTitles,
|
|
handleSelectAiTitle,
|
|
handleRefreshAiTitles,
|
|
autoGenerateTitle,
|
|
} = useAiTitleGenerator({ titleSettings, onTitleSettingsChange })
|
|
|
|
// 样式更新
|
|
const styleUpdaters = useTitleStyleUpdaters({ titleSettings, onTitleSettingsChange })
|
|
|
|
// 追踪 AI 自动选择开关的上一次值 & 是否首次挂载
|
|
const prevAiAutoSelect = useRef(titleSettings.aiAutoSelect)
|
|
const isFirstMount = useRef(true)
|
|
|
|
/* ── Step4 标题内容/样式变化后自动保存草稿(防抖 800ms,失败静默) ── */
|
|
const { scheduleSave: scheduleTitleSave } = useDraftAutoSave(selectedTemplate)
|
|
useEffect(() => {
|
|
if (!selectedTemplate) return
|
|
scheduleTitleSave(
|
|
{
|
|
title: {
|
|
text: titleSettings.title,
|
|
font: titleSettings.font,
|
|
font_size: titleSettings.size,
|
|
color: titleSettings.color,
|
|
position: titleSettings.position,
|
|
bold: titleSettings.bold,
|
|
stroke: titleSettings.stroke,
|
|
shadow: titleSettings.shadow,
|
|
},
|
|
},
|
|
800,
|
|
)
|
|
}, [
|
|
selectedTemplate,
|
|
titleSettings.title,
|
|
titleSettings.font,
|
|
titleSettings.size,
|
|
titleSettings.color,
|
|
titleSettings.position,
|
|
titleSettings.bold,
|
|
titleSettings.stroke,
|
|
titleSettings.shadow,
|
|
scheduleTitleSave,
|
|
])
|
|
|
|
// 当 AI 自动选择开关打开时,自动生成/选择一个标题填入
|
|
// 首次挂载时如果开关已经是 true 且无标题,也需要触发
|
|
useEffect(() => {
|
|
if (isFirstMount.current) {
|
|
isFirstMount.current = false
|
|
if (titleSettings.aiAutoSelect && !titleSettings.title) {
|
|
autoGenerateTitle()
|
|
}
|
|
prevAiAutoSelect.current = titleSettings.aiAutoSelect
|
|
return
|
|
}
|
|
if (titleSettings.aiAutoSelect && !prevAiAutoSelect.current && !titleSettings.title) {
|
|
autoGenerateTitle()
|
|
}
|
|
prevAiAutoSelect.current = titleSettings.aiAutoSelect
|
|
}, [titleSettings.aiAutoSelect, titleSettings.title, autoGenerateTitle])
|
|
|
|
return {
|
|
// 数据
|
|
userTitles,
|
|
titleSettings,
|
|
// AI 标题状态
|
|
aiTitleInput,
|
|
setAiTitleInput,
|
|
aiTitleGenerating,
|
|
aiTitleResults,
|
|
hasGeneratedTitles,
|
|
activePreset: styleUpdaters.activePreset,
|
|
titlePresets: styleUpdaters.titlePresets,
|
|
// AI 标题操作
|
|
handleGenerateAiTitles,
|
|
handleSelectAiTitle,
|
|
handleRefreshAiTitles,
|
|
autoGenerateTitle,
|
|
// 标题设置操作
|
|
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
|