feat: 推送新闻前进行异常确认

This commit is contained in:
LittleBoy 2025-02-15 11:33:59 +08:00
parent 227688be25
commit 17b0348ca6
6 changed files with 171 additions and 14 deletions

View File

@ -550,4 +550,11 @@
}
}
}
}
.modal-count-normal {
color:rgba(87, 148, 247, 1);
}
.modal-count-warning {
color:rgba(255, 0, 4, 0.85);
}

View File

@ -152,5 +152,18 @@
"title_generated_time": "Time stamp",
"title_operation": "",
"title_thumb": "Cover"
},
"modal": {
"warning": "Warning",
"push_article": {
"content_normal": "<span class=\"modal-count-normal\">{{count}}</span> news are selected, do you want to transfer them into videos?",
"content_normal_single": "<span class=\"modal-count-normal\">{{count}}</span> news is selected. Do you want to transfer it to a video?",
"content_error": "<span class=\"modal-count-normal\">{{count}}</span> news are selected, and metahuman contents are too short in the news below. Do you want to transfer them to videos?",
"content_error_single": "<span class=\"modal-count-normal\">{{count}}</span> news is selected, and the metahuman content is too short in this news. Do you want to transfer it to a video?",
"error_title": "Abnormal news",
"action_cancel": "Cancel",
"action_skip": "Skip the news",
"action_all": "Still generating"
}
}
}

View File

@ -152,5 +152,18 @@
"title_generated_time": "生成时间",
"title_operation": "操作",
"title_thumb": "缩略图"
},
"modal": {
"warning": "操作提示",
"push_article": {
"content_normal": "已选中<span class=\"modal-count-normal\">{{count}}</span>条新闻,是否全部生成?",
"content_normal_single": "已选中<span class=\"modal-count-normal\">{{count}}</span>条新闻,是否生成?",
"content_error": "已选中<span class=\"modal-count-normal\">{{count}}</span>条新闻,<span class=\"modal-count-warning\">{{error_count}}</span>条新闻数字人播报字数过少,是否生成全部<span class=\"modal-count-normal\">{{count}}</span>条视频?",
"content_error_single": "已选中<span class=\"modal-count-normal\">{{count}}</span>条新闻,<span class=\"modal-count-warning\">{{error_count}}</span>条新闻数字人播报字数过少,是否生成全部<span class=\"modal-count-normal\">{{count}}</span>条视频?",
"error_title": "异常新闻",
"action_cancel": "全部取消",
"action_skip": "跳过异常新闻",
"action_all": "全部生成"
}
}
}

View File

@ -1,18 +1,44 @@
import React, {useState} from "react";
import {showErrorToast, showToast} from "@/components/message.ts";
import {push2video} from "@/service/api/article.ts";
import {IconArrowRight} from "@/components/icons";
import {Modal, Space, Button} from "antd";
import {useNavigate} from "react-router-dom";
import {useTranslation} from "react-i18next";
import {useSetState} from "ahooks";
import {showErrorToast, showToast} from "@/components/message.ts";
import {push2video} from "@/service/api/article.ts";
import {IconArrowRight, IconWarningCircle} from "@/components/icons";
export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => void; }) {
export enum ProcessResult {
All,
Skip,
Cancel
}
type PushVideoProps = {
ids: Id[];
articles?: ListArticleItem[];
onResult?: (result: ProcessResult, errorIds: Id[]) => void;
}
export default function ButtonPush2Video(props: PushVideoProps) {
const [loading, setLoading] = useState(false)
const [state, setState] = useSetState<{
modalVisible?: boolean;
errorTitle?: string[];
errorIds: Id[]
}>({
errorIds: []
})
const {t} = useTranslation()
const navigate = useNavigate()
const handlePush = () => {
const handlePush = (action: ProcessResult) => {
setLoading(true)
push2video(props.ids).then(() => {
const skip = action === ProcessResult.Skip && state.errorIds.length > 0
const ids = !skip ? props.ids : props.ids.filter(id => !state.errorIds.includes(id));
push2video(ids).then(() => {
if (skip) {
props.onResult?.(ProcessResult.Skip, state.errorIds || [])
return;
}
showToast(t('news.push_stream_success'), 'success')
navigate('/create?state=push-success', {
state: 'push-success'
@ -22,18 +48,64 @@ export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => v
setLoading(false)
})
}
// double check 25-02-15 https://pu7y37y121.feishu.cn/docx/FwRrddAFWotRZlxgbr5cP7b6nud
// 1.normal 数字人播报部分有内容不少于50字或者数字人播报部分无内容
// 2.error 数字人播报部分有内容但是少于50字
const checkArticleContent = () => {
const errors: string[] = [], ids: Id[] = [];
props.articles?.filter(s => {
return props.ids.includes(s.id) && (s.metahuman_text && s.metahuman_text.length < 50)
}).forEach(s => {
errors.push(s.title)
ids.push(s.id)
})
return {errors, ids}
}
const onPushClick = () => {
if (loading) return;
if (props.ids.length === 0) {
showToast(t('news.push_stream_empty'), 'warning')
return
}
// Modal.confirm({
// title: '操作提示',
// content: '是否确定一键开播选中新闻?',
// onOk: handlePush
// check article content
const result = checkArticleContent()
setState({modalVisible: true, errorTitle: result.errors, errorIds: result.ids})
//
// const instance = modal.confirm({
// title: <div className="text-base pt-0.5">{t('modal.warning')}</div>,
// wrapClassName: 'root-modal-confirm',
// centered: true,
// width: 440,
// icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
// content: <div className="confirm-message-wrapper pl-7">
// <div className="message text-gray-600">
// {t(
// errors && errors.length > 0
// ? (props.ids.length == 1 ? 'modal.push_article.content_error_single' : 'modal.push_article.content_error')
// : (props.ids.length == 1 ? 'modal.push_article.content_normal_single' : 'modal.push_article.content_normal'),
// {count: props.ids.length, error_count: errors?.length})}
// </div>
// {errors && errors.length > 0 && <div className="error-list text-red-400 mt-6">
// <div className="title">{t('modal.push_article.error_title')}:</div>
// <div className="max-h-[100px] overflow-auto" style={{lineHeight: '20px'}}>
// {errors.map(s => <div
// className="error-item overflow-hidden text-nowrap overflow-ellipsis">{s}</div>)}
// </div>
// </div>}
// </div>,
// footer: <div className="flex justify-end mt-6">
// <Space>
// <Button onClick={() => {
// instance.destroy()
// }}>{t('modal.push_article.action_cancel')}</Button>
// <Button type="primary">{t('modal.push_article.action_skip')}</Button>
// <Button>{t('modal.push_article.action_all')}</Button>
// </Space>
// </div>,
// })
handlePush();
// handlePush();
}
return (
<div>
@ -45,6 +117,51 @@ export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => v
<span className={'text'}>{loading ? t('news.push_streaming') : t('news.generate_video')}</span>
<IconArrowRight className={'text-white'}/>
</button>
<Modal
open={state.modalVisible}
centered
closeIcon={false}
footer={null}
width={440}
>
<div className="modal-title flex items-center">
<div className="anticon anticon-exclamation-circle text-red-400 w-10"><IconWarningCircle
style={{fontSize: 24, color: 'rgba(250, 173, 20, 1)'}}/></div>
<div className="text-base">{t('modal.warning')}</div>
</div>
<div className="confirm-message-wrapper flex mt-2">
<div className="min-w-10"></div>
<div>
<div className="message text-gray-600" dangerouslySetInnerHTML={{
__html: t(
state.errorTitle && state.errorTitle.length > 0
? (props.ids.length == 1 ? 'modal.push_article.content_error_single' : 'modal.push_article.content_error')
: (props.ids.length == 1 ? 'modal.push_article.content_normal_single' : 'modal.push_article.content_normal'),
{count: props.ids.length, error_count: state.errorTitle?.length})
}}>
</div>
{state.errorTitle && state.errorTitle.length > 0 &&
<div className="error-list text-red-400 mt-6 w-[350px]">
<div className="title">{t('modal.push_article.error_title')}:</div>
<div className="max-h-[100px] overflow-auto" style={{lineHeight: '20px'}}>
{state.errorTitle.map(s => <div
className="error-item overflow-hidden pr-1 text-nowrap overflow-ellipsis">{s}</div>)}
</div>
</div>}
</div>
</div>
<div className="flex justify-end mt-6">
<Space>
<Button onClick={() => {
setState({modalVisible: false})
}}>{t('modal.push_article.action_cancel')}</Button>
{state.errorIds?.length > 0 && <Button type="primary"
onClick={() => handlePush(ProcessResult.Skip)}>{t('modal.push_article.action_skip')}</Button>}
<Button type={state.errorIds.length == 0 ? 'primary' : 'default'}
onClick={() => handlePush(ProcessResult.All)}>{t('modal.push_article.action_all')}</Button>
</Space>
</div>
</Modal>
</div>
)
}

View File

@ -6,7 +6,7 @@ import {formatTime} from "@/util/strings.ts";
import ArticleEditModal from "@/components/article/edit-modal.tsx";
import {deleteByIds, getList} from "@/service/api/article.ts";
import EditSearchForm from "@/pages/news/components/edit-search-form.tsx";
import ButtonPush2Video from "@/pages/news/components/button-push2video.tsx";
import ButtonPush2Video, {ProcessResult} from "@/pages/news/components/button-push2video.tsx";
import styles from './components/style.module.scss'
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
@ -73,6 +73,12 @@ export default function NewEdit() {
showToast(t('delete_success'), 'success')
}).catch(showErrorToast)
}
const handlePushProcessResult = (result: ProcessResult,errorIds: Id[])=>{
if(result == ProcessResult.Skip){
// 跳过
setSelectedRowKeys(errorIds)
}
}
return (<div className="container pb-5 news-edit">
<div className="search-panel-container my-5">
@ -157,7 +163,7 @@ export default function NewEdit() {
</div>
</div>
})}
<div className="h-[100px]"></div>
<div className="h-[130px]"></div>
</div>
</InfiniteScroller>
</div>
@ -165,7 +171,7 @@ export default function NewEdit() {
<div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
{selectedRowKeys?.length > 0 && <ButtonDeleteBatch ids={selectedRowKeys} onSuccess={refresh}/>}
<ButtonPush2Video ids={selectedRowKeys} onSuccess={refresh}/>
<ButtonPush2Video ids={selectedRowKeys} articles={data?.list} onResult={handlePushProcessResult}/>
</div>
</div>
<ArticleEditModal

1
src/types/api.d.ts vendored
View File

@ -59,6 +59,7 @@ interface BasicArticleInfo {
id: number;
title: string;
summary: string;
metahuman_text: string;
publish_time: string;
media_name: string;
column_name?: string;