import React, {useCallback, useEffect, useRef} from 'react' import { setAutoScroll, setAutoTranslate, setCheckAutoScroll, setFoldAll, setNeedScroll, setPage, setSearchText, setSegmentFold, setTempData } from '../redux/envReducer' import {useAppDispatch, useAppSelector} from '../hooks/redux' import { AiOutlineAim, AiOutlineCloseCircle, FaRegArrowAltCircleDown, IoWarning, MdExpand, RiFileCopy2Line, RiTranslate } from 'react-icons/all' import classNames from 'classnames' import toast from 'react-hot-toast' import SegmentCard from './SegmentCard' import { HEADER_HEIGHT, PAGE_SETTINGS, SEARCH_BAR_HEIGHT, SUMMARIZE_ALL_THRESHOLD, SUMMARIZE_TYPES, TITLE_HEIGHT } from '../const' import {FaClipboardList} from 'react-icons/fa' import useTranslate from '../hooks/useTranslate' import {getSummarize} from '../util/biz_util' import {openUrl} from '@kky002/kky-util' const Body = () => { const dispatch = useAppDispatch() const noVideo = useAppSelector(state => state.env.noVideo) const autoTranslate = useAppSelector(state => state.env.autoTranslate) const autoScroll = useAppSelector(state => state.env.autoScroll) const segments = useAppSelector(state => state.env.segments) const foldAll = useAppSelector(state => state.env.foldAll) const envData = useAppSelector(state => state.env.envData) const compact = useAppSelector(state => state.env.tempData.compact) const floatKeyPointsSegIdx = useAppSelector(state => state.env.floatKeyPointsSegIdx) const translateEnable = useAppSelector(state => state.env.envData.translateEnable) const summarizeEnable = useAppSelector(state => state.env.envData.summarizeEnable) const {addSummarizeTask} = useTranslate() const bodyRef = useRef() const curOffsetTop = useAppSelector(state => state.env.curOffsetTop) const checkAutoScroll = useAppSelector(state => state.env.checkAutoScroll) const needScroll = useAppSelector(state => state.env.needScroll) const totalHeight = useAppSelector(state => state.env.totalHeight) const curSummaryType = useAppSelector(state => state.env.tempData.curSummaryType) const title = useAppSelector(state => state.env.title) const searchText = useAppSelector(state => state.env.searchText) const normalCallback = useCallback(() => { dispatch(setTempData({ compact: false })) }, [dispatch]) const compactCallback = useCallback(() => { dispatch(setTempData({ compact: true })) }, [dispatch]) const posCallback = useCallback(() => { dispatch(setNeedScroll(true)) }, [dispatch]) const onSummarizeAll = useCallback(() => { const apiKey = envData.aiType === 'gemini'?envData.geminiApiKey:envData.apiKey if (!apiKey) { dispatch(setPage(PAGE_SETTINGS)) toast.error('需要先设置ApiKey!') return } const segments_ = [] for (const segment of segments ?? []) { const summary = segment.summaries[curSummaryType] if (!summary || summary.status === 'init' || (summary.status === 'done' && summary.error)) { segments_.push(segment) } } if (segments_.length === 0) { toast.error('没有可总结的段落!') return } if (segments_.length < SUMMARIZE_ALL_THRESHOLD || confirm(`确定总结${segments_.length}个段落?`)) { for (const segment of segments_) { addSummarizeTask(curSummaryType, segment).catch(console.error) } toast.success(`已添加${segments_.length}个总结任务!`) } }, [addSummarizeTask, curSummaryType, dispatch, envData.aiType, envData.apiKey, envData.geminiApiKey, segments]) const onFoldAll = useCallback(() => { dispatch(setFoldAll(!foldAll)) for (const segment of segments ?? []) { dispatch(setSegmentFold({ segmentStartIdx: segment.startIdx, fold: !foldAll })) } }, [dispatch, foldAll, segments]) const toggleAutoTranslateCallback = useCallback(() => { const apiKey = envData.aiType === 'gemini'?envData.geminiApiKey:envData.apiKey if (apiKey) { dispatch(setAutoTranslate(!autoTranslate)) } else { dispatch(setPage(PAGE_SETTINGS)) toast.error('需要先设置ApiKey!') } }, [autoTranslate, dispatch, envData.aiType, envData.apiKey, envData.geminiApiKey]) const onEnableAutoScroll = useCallback(() => { dispatch(setAutoScroll(true)) dispatch(setNeedScroll(true)) }, [dispatch]) const onWheel = useCallback(() => { if (autoScroll) { dispatch(setAutoScroll(false)) } }, [autoScroll, dispatch]) const onCopy = useCallback(() => { const [success, content] = getSummarize(title, segments, curSummaryType) if (success) { navigator.clipboard.writeText(content).then(() => { toast.success('复制成功') }).catch(console.error) } }, [curSummaryType, segments, title]) const onSearchTextChange = useCallback((e: any) => { const searchText = e.target.value dispatch(setSearchText(searchText)) }, [dispatch]) const onClearSearchText = useCallback(() => { dispatch(setSearchText('')) }, [dispatch]) // 自动滚动 useEffect(() => { if (checkAutoScroll && curOffsetTop && autoScroll && !needScroll) { if (bodyRef.current.scrollTop <= curOffsetTop - bodyRef.current.offsetTop - (totalHeight-120) + (floatKeyPointsSegIdx != null ? 100 : 0) || bodyRef.current.scrollTop >= curOffsetTop - bodyRef.current.offsetTop - 40 - 10 ) { dispatch(setNeedScroll(true)) dispatch(setCheckAutoScroll(false)) console.debug('need scroll') } } }, [autoScroll, checkAutoScroll, curOffsetTop, dispatch, floatKeyPointsSegIdx, needScroll, totalHeight]) return
{/* title */}
{segments != null && segments.length > 0 && }
列表视图 文章视图
{translateEnable &&
} {summarizeEnable &&
} {noVideo &&
}
{/* search */} {envData.searchEnabled &&
{searchText && }
} {/* auto scroll btn */} {!autoScroll &&
} {/* body */}
{segments?.map((segment, segmentIdx) => )} {/* tip */}
💡提示💡
可以尝试将概览生成的内容粘贴到视频评论里,发布后看看有什么效果🥳
{(segments?.length??0) > 0 && }
youtube caption proYouTube Caption Pro
这是YouTube版的字幕列表
} export default Body