diff --git a/src/components/article/HotNews.tsx b/src/components/article/HotNews.tsx new file mode 100644 index 0000000..b78064d --- /dev/null +++ b/src/components/article/HotNews.tsx @@ -0,0 +1,61 @@ +import styles from './article.module.scss' +import {useTranslation} from "react-i18next"; +import {Input, Switch} from "antd"; +import {useMemo} from "react"; + +type HotNewsProps = { + news: string[]; + mode: string; + onValueChange: (values: { + news: string[], + mode: string + }) => void; +} + + +function HotNews({news, mode, onValueChange}: HotNewsProps) { + const {t,i18n} = useTranslation() + const demoPlaceholderList = useMemo(()=>{ + return i18n.language == 'zh-CN' ? [ + '例:韩正会见英国汇丰集团主席', + '例: 丁薛祥出席全国高校毕业生等青年就业创业工作视频...', + '例:俄称乌方再度袭击俄能源设施 乌称击退俄军进攻', + ] : [ + 'please type hot news', + 'please type hot news', + 'please type hot news', + ] + },[i18n.language]) + const handleValueChange = (value: string, index: number) => { + const values = [...news] + values[index] = value + onValueChange({news: values, mode}) + } + return ( +
+
+
+ {t("modal.hot_news.title")} +
+
+ {mode == 'auto' ? t("modal.hot_news.edit_auto") : t("modal.hot_news.edit_manual")} + { + onValueChange({news, mode: checked ? 'auto' : 'manual'}) + }}/> +
+
+
+ {news.map((item, index) =>
+ handleValueChange(e.target.value, index)}/> +
)} +
+
+ ) +} + +export default HotNews \ No newline at end of file diff --git a/src/components/article/article.module.scss b/src/components/article/article.module.scss index 77236ae..2b06d5a 100644 --- a/src/components/article/article.module.scss +++ b/src/components/article/article.module.scss @@ -46,7 +46,7 @@ @apply flex gap-4; :global{ .area-title{ - @apply text-gray-400 text-sm text-gray-800; + @apply text-gray-400 text-base text-gray-800; } .digital-person{ width: 450px; @@ -132,4 +132,10 @@ .textarea { @apply border-0 +} + +// hot news +.hotNews{ + .title{} + } \ No newline at end of file diff --git a/src/components/article/edit-modal.tsx b/src/components/article/edit-modal.tsx index 9c8163f..fe399a7 100644 --- a/src/components/article/edit-modal.tsx +++ b/src/components/article/edit-modal.tsx @@ -1,12 +1,15 @@ -import {Modal} from "antd"; -import ArticleGroup from "@/components/article/group.tsx"; -import {useEffect, useState} from "react"; +import {Modal,App} from "antd"; +import React, {useEffect, useState} from "react"; import {useSetState} from "ahooks"; +import {useTranslation} from "react-i18next"; import * as article from "@/service/api/article.ts"; import {regenerate} from "@/service/api/video.ts"; import {push2video} from "@/service/api/article.ts"; import {showErrorToast, showToast} from "@/components/message.ts"; -import {useTranslation} from "react-i18next"; +import ArticleGroup, {HotNewsData} from "@/components/article/group.tsx"; +import type {HookAPI as ModalHookAPI} from "antd/es/modal/useModal"; +import {TFunction} from "i18next"; +import {IconWarningCircle} from "@/components/icons"; type Props = { id?: number; @@ -70,7 +73,7 @@ function groupHasImageAndText(blocks: BlockContent[]) { function checkGroupsValid(_groups: BlockContent[][]) { const groups = _groups.filter((_,index)=>{ if (index == 0) return true; - return _.length>1; + return _.length > 1 || (_.length == 1 && _[0].content.trim().length > 0) ; }) if (groups.length == 1) return true; for (let index = 1;index< groups.length; index ++) { @@ -78,19 +81,46 @@ function checkGroupsValid(_groups: BlockContent[][]) { } return true; } +function checkHotNewsValid(hotNews: HotNewsData,modal:ModalHookAPI,t:TFunction<"translation", undefined>) { + return new Promise((resolve)=>{ + + // 验证热点新闻数据是否正确 + if(hotNews.mode == 'manual' && hotNews.list.filter(s=>s.trim().length > 0).length < 3){ + modal.confirm({ + wrapClassName: 'root-modal-confirm', + icon: , + title: t('modal.hot_news.empty_notice_title'), + content: t('modal.hot_news.empty_notice_message'), + centered:true, + onOk: () => { + resolve(true) + }, + onCancel: () => { + resolve(false) + } + }) + return; + } + resolve(true) + }) +} export default function ArticleEditModal(props: Props) { const {t} = useTranslation() + const {modal} = App.useApp() const [groups, setGroups] = useState([]); const [title, setTitle] = useState('') - + const [hotNews,setHotNews] = useState({ + list: ['','',''], + mode: 'auto' + }) const [state, setState] = useSetState({ ...DEFAULT_STATE, generating:false }) // 保存数据 - const handleSave = () => { + const handleSave = async () => { setState({error: ''}) if (!title) { // setState({msgTitle: '请输入标题内容'}); @@ -106,13 +136,21 @@ export default function ArticleEditModal(props: Props) { setState({msgGroup: t('news.edit_empty_group_content')}); return; } + const hotNewsValid = await checkHotNewsValid(hotNews,modal,t) + if(!hotNewsValid) return; // if (groups.length == 0 || groups[0].length == 0 || !groups[0][0].content) { // // setState({msgGroup: '请输入正文文本内容'}); // return; // } const save = props.type == 'news' ? article.save : regenerate setState({loading: true}) - save(title, groups[0][0].content, groups.slice(1), props.id && props.id > 0 ? props.id : undefined).then(() => { + save({ + title, + metahuman_text: groups[0][0].content, + content_group: groups.slice(1), + hot_news: hotNews.list, + id: props.id && props.id > 0 ? props.id : undefined + }).then(() => { props.onClose?.(true) }).catch(e => { setState({error: e.message || t('news.edit_save_failed')}) @@ -121,6 +159,7 @@ export default function ArticleEditModal(props: Props) { }); } const handlePush2Video = async () =>{ + // if (!title) { // setState({msgTitle: '请输入标题内容'}); return; @@ -136,8 +175,16 @@ export default function ArticleEditModal(props: Props) { return; } if(!props.id || state.generating) return; + const hotNewsValid = await checkHotNewsValid(hotNews,modal,t) + if(!hotNewsValid) return; setState({generating:true}) - await article.save(title, groups[0][0].content, groups.slice(1), props.id) + await article.save({ + title, + metahuman_text: groups[0][0].content, + content_group: groups.slice(1), + hot_news: hotNews.list, + id: props.id, + }) push2video([props.id]).then(() => { showToast(t('news.push_stream_success'), 'success') // navigate('/create?state=push-success',{ @@ -154,6 +201,13 @@ export default function ArticleEditModal(props: Props) { // 如果传入了id则获取数据 if (props.id > 0) { article.getById(props.id).then(res => { + const len = res.hot_news.length + const list = len >= 3 ? res.hot_news :res.hot_news.concat(Array(3 - len).fill('')) + console.log('list,',list,res.hot_news) + setHotNews({ + list, + mode: res.hot_news_mode ?? 'auto' + }) setGroups(rebuildGroups([[{content: res.metahuman_text, type: "text"}], ...res.content_group])) setTitle(res.title) }) @@ -190,8 +244,12 @@ export default function ArticleEditModal(props: Props) {
{ + errorMessage={state.msgGroup} + editable + groups={groups} + hotNews={hotNews} + onChange={(list,hotNews) => { + setHotNews(hotNews) setGroups(() => list) setState({msgGroup: (list.length == 0 || list[0].length == 0 || !list[0][0].content) ? t('news.edit_empty_human_content') : ''}); }} diff --git a/src/components/article/group.tsx b/src/components/article/group.tsx index ae9b7e7..5a2ae29 100644 --- a/src/components/article/group.tsx +++ b/src/components/article/group.tsx @@ -6,23 +6,29 @@ import {showToast} from "@/components/message.ts"; import React from "react"; import {useTranslation} from "react-i18next"; import {IconAdd} from "@/components/icons"; +import HotNews from "@/components/article/HotNews.tsx"; +export type HotNewsData = { + list: string[]; + mode: string +} type Props = { groups: BlockContent[][]; editable?: boolean; - onChange?: (groups: BlockContent[][]) => void; + onChange?: (groups: BlockContent[][], hotNews: HotNewsData) => void; errorMessage?: string; + hotNews: HotNewsData; } -export default function ArticleGroup({groups, editable, onChange, errorMessage}: Props) { - const {t,i18n} = useTranslation() +export default function ArticleGroup({groups, editable, onChange, errorMessage, hotNews}: Props) { + const {t, i18n} = useTranslation() // const groups = rebuildGroups(_groups) /** * 添加一个组 * @param insertIndex 插入的位置,-1表示插入到末尾 */ - const handleAddGroup = (insertIndex: number,checkId:number) => { + const handleAddGroup = (insertIndex: number, checkId: number) => { // && insertIndex !== 1 if (checkId > 0 && checkId < groups.length) { //const index = insertIndex == -1 || insertIndex >= groups.length ? groups.length - 1 : insertIndex - 1 @@ -41,12 +47,12 @@ export default function ArticleGroup({groups, editable, onChange, errorMessage}: } else { _groups.splice(insertIndex, 0, newGroup) } - onChange?.(_groups) + onChange?.(_groups, hotNews) } - const handleDigitalPersonContentChange = (content:string) => { + const handleDigitalPersonContentChange = (content: string) => { groups[0] = [{type: 'text', content}] - onChange?.([...groups]) + onChange?.([...groups], hotNews) } return
@@ -57,20 +63,31 @@ export default function ArticleGroup({groups, editable, onChange, errorMessage}:
{/* value={groups || groups[0][0].content}*/} -
- {editable ?
- 0 ? groups[0][0].content : ''} - autoSize={{minRows: 20, maxRows: 21}} - variant={"borderless"} - onChange={e => { - handleDigitalPersonContentChange(e.target.value) - }} - /> -
:

{groups && groups.length > 0 ? groups[0][0].content : ''}

} +
+
+ {editable ?
+ 0 ? groups[0][0].content : ''} + autoSize={{minRows: 20, maxRows: 21}} + variant={"borderless"} + onChange={e => { + handleDigitalPersonContentChange(e.target.value) + }} + /> +
:

{groups && groups.length > 0 ? groups[0][0].content : ''}

} +
+
+ { + onChange?.([...groups], { + list:hotNews.news,mode: hotNews.mode + }) + }}/> +
@@ -81,9 +98,12 @@ export default function ArticleGroup({groups, editable, onChange, errorMessage}:
- {editable && groups.length == 1 &&
- handleAddGroup?.(1,1)} className="article-action-add" title={t('news.materials.add_group')}> -
} + {editable && groups.length == 1 &&
+
+ handleAddGroup?.(1, 1)} className="article-action-add" + title={t('news.materials.add_group')}> +
+
} {groups.map((g, index) => ( index == 0 ? null : { groups[index] = blocks - onChange?.([...groups]) + onChange?.([...groups], hotNews) }} errorMessage={errorMessage} index={index} - onAdd={(_index,checkIndex) => { - handleAddGroup?.(_index ? _index :index + 1,checkIndex) + onAdd={(_index, checkIndex) => { + handleAddGroup?.(_index ? _index : index + 1, checkIndex) }} - disableRemoveMessage={groups.length <= 1?t('news.edit_notice_keep_1'):''} + disableRemoveMessage={groups.length <= 1 ? t('news.edit_notice_keep_1') : ''} onRemove={async () => { if (groups.length <= 1) { message.warning(t('news.edit_notice_keep_1')) return; } - onChange?.(groups.filter((_, idx) => index !== idx)) + onChange?.(groups.filter((_, idx) => index !== idx), hotNews) }} /> ))} @@ -113,7 +133,7 @@ export default function ArticleGroup({groups, editable, onChange, errorMessage}:
{groups.length == 0 && editable && - onChange?.([blocks])} index={0} - blocks={[{type: 'text', content: ''}]}/>} + onChange?.([blocks],hotNews)} index={0} + blocks={[{type: 'text', content: ''}]}/>}
} \ No newline at end of file diff --git a/src/i18n/translations/en-US.json b/src/i18n/translations/en-US.json index 9024c99..297cdc5 100644 --- a/src/i18n/translations/en-US.json +++ b/src/i18n/translations/en-US.json @@ -1,14 +1,8 @@ { "AppTitle": "AI Livesteam", - "go_to_home": "Go to Homepage" , "Hello": "Hello", "cancel": "Cancel", "close": "Close", - "service_error": "Service exception, please contact customer support.", - "error_401": "You do not have permission to access this page", - "error_403": "You do not have permission to access this page", - "error_404": "Page not found", - "error_500": "Service exception, please contact customer support.", "confirm": { "push_title": "Push Notice", "push_video": "Are you sure editing selected news?", @@ -20,9 +14,14 @@ "delete_failed": "Delete failed", "delete_success": "Delete success", "download": "Download", + "error_401": "You do not have permission to access this page", + "error_403": "You do not have permission to access this page", + "error_404": "Page not found", + "error_500": "Service exception, please contact customer support.", "generating": { "title": "Preview - Click the video to play" }, + "go_to_home": "Go to Homepage", "history": { "delete_confirm": "Are you sure you want to delete this video?", "push_success": "Streaming success", @@ -49,6 +48,26 @@ "username": "Please enter your phone number", "welcome": "Welcome" }, + "modal": { + "hot_news": { + "edit_auto": "Smart", + "edit_manual": "Manual", + "empty_notice_message": "\"Hot News\" has not been filled in yet. Should it be filled in automatically by the system?", + "empty_notice_title": "Notice", + "title": "Hot news" + }, + "push_article": { + "action_all": "Still generating", + "action_cancel": "Cancel", + "action_skip": "Skip the news", + "content_error": "{{count}} news are selected, and {{error_count}} metahuman contents are too short in these news below. Do you want to transfer them to videos?", + "content_error_single": "{{count}} news is selected, and the metahuman content is too short in this news. Do you want to transfer it to a video?", + "content_normal": "{{count}} news are selected, Do you want to transfer them into videos?", + "content_normal_single": "{{count}} news is selected. Do you want to transfer it to a video?", + "error_title": "Abnormal news" + }, + "warning": "Warning" + }, "nav": { "editing": "Editing", "generating": "Generating", @@ -89,8 +108,8 @@ "get_detail_error": "Get new details failed", "image_count": "Images", "materials": { - "title": "News Materials", - "add_group": "Add Group" + "add_group": "Add Group", + "title": "News Materials" }, "news_all_source": "All", "push_empty": "please select the news to edit", @@ -118,6 +137,7 @@ "text": "Select", "total": "Total: {{count}}" }, + "service_error": "Service exception, please contact customer support.", "time_filter": { "all": "All", "last_week": "Last week", @@ -142,8 +162,8 @@ "delete_description_count": "Are you sure you want to delete these {{count}} videos?", "delete_empty": "Select the video you want to delete", "download": "Download", - "generating": "Generating", "generate_failed": "Generate Failed", + "generating": "Generating", "playing": "Playing", "push_confirm": "Are you sure you want to streaming these video?", "push_empty": "Select the video you want to streaming", @@ -159,18 +179,5 @@ "title_generated_time": "Time stamp", "title_operation": "", "title_thumb": "Cover" - }, - "modal": { - "warning": "Warning", - "push_article": { - "content_normal": "{{count}} news are selected, Do you want to transfer them into videos?", - "content_normal_single": "{{count}} news is selected. Do you want to transfer it to a video?", - "content_error": "{{count}} news are selected, and {{error_count}} metahuman contents are too short in these news below. Do you want to transfer them to videos?", - "content_error_single": "{{count}} 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" - } } } \ No newline at end of file diff --git a/src/i18n/translations/zh-CN.json b/src/i18n/translations/zh-CN.json index 11bf1f4..4c1f6cf 100644 --- a/src/i18n/translations/zh-CN.json +++ b/src/i18n/translations/zh-CN.json @@ -1,14 +1,8 @@ { "AppTitle": "数字人直播", - "go_to_home": "返回首页" , "Hello": "你好", "cancel": "取消", "close": "关闭", - "service_error": "新闻异常,无法生成,请咨询客服", - "error_401": "您没有权限访问本页面", - "error_403": "您没有权限访问本页面", - "error_404": "访问的页面不存在", - "error_500": "服务异常,请咨询客服.", "confirm": { "push_title": "推流提示", "push_video": "是否确定一键推流选中新闻视频?", @@ -20,9 +14,14 @@ "delete_failed": "删除失败", "delete_success": "删除成功", "download": "下载", + "error_401": "您没有权限访问本页面", + "error_403": "您没有权限访问本页面", + "error_404": "访问的页面不存在", + "error_500": "服务异常,请咨询客服.", "generating": { "title": "预览视频 - 点击视频列表播放" }, + "go_to_home": "返回首页", "history": { "delete_confirm": "是否要删除该视频", "push_success": "一键推流成功,已推流至数字人直播间,请查看!", @@ -49,6 +48,26 @@ "username": "请输入账号", "welcome": "欢迎登录" }, + "modal": { + "hot_news": { + "edit_auto": "智能填充", + "edit_manual": "手动编辑", + "empty_notice_message": "“新闻热点”尚未填写,是否由系统自动填充?", + "empty_notice_title": "操作提示", + "title": "新闻热点" + }, + "push_article": { + "action_all": "全部生成", + "action_cancel": "全部取消", + "action_skip": "跳过异常新闻", + "content_error": "已选中{{count}}条新闻,{{error_count}}条新闻数字人播报字数过少,是否生成全部{{count}}条视频?", + "content_error_single": "已选中{{count}}条新闻,{{error_count}}条新闻数字人播报字数过少,是否生成全部{{count}}条视频?", + "content_normal": "已选中{{count}}条新闻,是否全部生成?", + "content_normal_single": "已选中{{count}}条新闻,是否生成?", + "error_title": "异常新闻" + }, + "warning": "操作提示" + }, "nav": { "editing": "新闻编辑", "generating": "视频生成", @@ -89,8 +108,8 @@ "get_detail_error": "获取新闻详情失败", "image_count": "图片数", "materials": { - "title": "新闻素材", - "add_group": "新增分组" + "add_group": "新增分组", + "title": "新闻素材" }, "news_all_source": "全部来源", "push_empty": "请选择要推入编辑的新闻", @@ -118,6 +137,7 @@ "text": "选择", "total": "总共 {{count}} 条" }, + "service_error": "新闻异常,无法生成,请咨询客服", "time_filter": { "all": "所有时间", "last_week": "近一周", @@ -142,8 +162,8 @@ "delete_description_count": "已选择{{count}}条,确定要全部删除吗?", "delete_empty": "请选择要删除的视频", "download": "下载视频", - "generating": "生成中", "generate_failed": "生成失败", + "generating": "生成中", "playing": "播放中", "push_confirm": "是否确定一键推流选中新闻视频?", "push_empty": "请选择要推流的新闻视频", @@ -159,18 +179,5 @@ "title_generated_time": "生成时间", "title_operation": "操作", "title_thumb": "缩略图" - }, - "modal": { - "warning": "操作提示", - "push_article": { - "content_normal": "已选中{{count}}条新闻,是否全部生成?", - "content_normal_single": "已选中{{count}}条新闻,是否生成?", - "content_error": "已选中{{count}}条新闻,{{error_count}}条新闻数字人播报字数过少,是否生成全部{{count}}条视频?", - "content_error_single": "已选中{{count}}条新闻,{{error_count}}条新闻数字人播报字数过少,是否生成全部{{count}}条视频?", - "error_title": "异常新闻", - "action_cancel": "全部取消", - "action_skip": "跳过异常新闻", - "action_all": "全部生成" - } } } \ No newline at end of file diff --git a/src/service/api/article.ts b/src/service/api/article.ts index 1e3118f..70f0662 100644 --- a/src/service/api/article.ts +++ b/src/service/api/article.ts @@ -20,13 +20,8 @@ export function getById(id: Id) { return post({url: '/article/detail/' + id}) } -export function save(title: string, metahuman_text: string, content_group: BlockContent[][], id?: number) { - return post<{ content: string }>(id && id > 0 ? '/article/modify' : '/article/create/new', { - title, - metahuman_text, - content_group, - id - }) +export function save(params:{title: string, metahuman_text: string, content_group: BlockContent[][],hot_news: string[], id?: number}) { + return post<{ content: string }>(params.id && params.id > 0 ? '/article/modify' : '/article/create/new',params) } export function push2video(article_ids: Id[]) { diff --git a/src/service/api/video.ts b/src/service/api/video.ts index 5cffff9..4827bac 100644 --- a/src/service/api/video.ts +++ b/src/service/api/video.ts @@ -17,21 +17,24 @@ export function deleteHistories(ids: Id[]) { * @param content_group * @param article_id */ -export function regenerate(title: string, metahuman_text: string, content_group: BlockContent[][], article_id?: Id) { +export function regenerate(params:{title: string, metahuman_text: string, content_group: BlockContent[][], id?: Id}) { return post<{ content: string }>({ url: '/video/regenerate', data: { - title, - metahuman_text, - content_group, - article_id + ...params, + article_id:params.id } }) } // 重新生成视频 export async function regenerateById(article_id: Id) { const article = await getArticle(article_id); - return await regenerate(article.title, article.metahuman_text, article.content_group, article_id) + return await regenerate({ + title:article.title, + metahuman_text:article.metahuman_text, + content_group:article.content_group, + id:article_id + }) } export function getById(id: Id) { diff --git a/src/types/core.d.ts b/src/types/core.d.ts index 0545cd2..8a227f7 100644 --- a/src/types/core.d.ts +++ b/src/types/core.d.ts @@ -32,6 +32,8 @@ declare interface ArticleDetail { id: number; title: string; metahuman_text: string; + hot_news_mode?: string; + hot_news: string[]; // 4月 6 日新增 content_group: BlockContent[][] }