4800ada7ec
CI/CD Pipeline / Validate - Code Quality (push) Successful in 5m31s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m33s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m19s
CI/CD Pipeline / Unit Tests (push) Successful in 9m40s
CI/CD Pipeline / Integration Tests (push) Successful in 1m43s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m24s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14m30s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m40s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m49s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 39s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 57s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m57s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 36s
CI/CD Pipeline / Canary Release to Production (push) Failing after 1029h44m12s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1029h52m38s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1029h52m39s
CI/CD Pipeline / Deploy Production (push) Failing after 1029h52m38s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1029h52m39s
CI/CD Pipeline / Build Production API Image (push) Failing after 1029h52m39s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1030h2m16s
CI/CD Pipeline / Frontend Lint (push) Failing after 1030h2m19s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1030h2m21s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1030h2m17s
CI/CD Pipeline / CI Gate (push) Failing after 1030h25m55s
CI/CD Pipeline / PR Build API Image (push) Failing after 1030h35m34s
feat(title): AI自动选择标题 (#1236)
125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
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<AiTitleItem[]>([])
|
|
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])
|
|
|
|
/**
|
|
* 自动生成标题(供 AI 自动选择开关使用)
|
|
* 如果已有生成结果,直接从中选一个;否则用默认关键词生成
|
|
*/
|
|
const autoGenerateTitle = useCallback((): string => {
|
|
if (aiTitleResults.length > 0) {
|
|
const picked = aiTitleResults[Math.floor(Math.random() * aiTitleResults.length)]
|
|
if (!picked) return ""
|
|
onTitleSettingsChange({ ...titleSettings, title: picked.title })
|
|
return picked.title
|
|
}
|
|
// 没有已有结果,用默认关键词生成
|
|
const results = generateTitlesFromTopic("短视频")
|
|
setAiTitleResults(results)
|
|
setHasGeneratedTitles(true)
|
|
const picked = results[Math.floor(Math.random() * results.length)]
|
|
if (!picked) return ""
|
|
onTitleSettingsChange({ ...titleSettings, title: picked.title })
|
|
return picked.title
|
|
}, [aiTitleResults, titleSettings, onTitleSettingsChange])
|
|
|
|
const handleSelectAiTitle = useCallback(
|
|
(title: string) => {
|
|
onTitleSettingsChange({ ...titleSettings, title })
|
|
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,
|
|
autoGenerateTitle,
|
|
}
|
|
}
|