添加字幕搜索功能

This commit is contained in:
IndieKKY
2023-11-28 13:53:01 +08:00
parent 9f486b7269
commit 8d1bac4623
13 changed files with 403 additions and 29 deletions

View File

@@ -7,14 +7,30 @@ import {
setFoldAll,
setNeedScroll,
setPage,
setSearchText,
setSegmentFold
} from '../redux/envReducer'
import {useAppDispatch, useAppSelector} from '../hooks/redux'
import {AiOutlineAim, FaRegArrowAltCircleDown, IoWarning, MdExpand, RiFileCopy2Line, RiTranslate} from 'react-icons/all'
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, SUMMARIZE_ALL_THRESHOLD, SUMMARIZE_TYPES, TITLE_HEIGHT} from '../const'
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'
@@ -41,6 +57,7 @@ const Body = () => {
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(setCompact(false))
@@ -118,6 +135,15 @@ const Body = () => {
}
}, [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) {
@@ -132,6 +158,7 @@ const Body = () => {
}, [autoScroll, checkAutoScroll, curOffsetTop, dispatch, floatKeyPointsSegIdx, needScroll, totalHeight])
return <div className='relative'>
{/* title */}
<div className='absolute top-1 left-6 flex-center gap-1'>
<AiOutlineAim className='cursor-pointer' onClick={posCallback} title='滚动到视频位置'/>
{segments != null && segments.length > 0 &&
@@ -159,19 +186,31 @@ const Body = () => {
<IoWarning className='text-warning'/>
</div>}
</div>
{/* search */}
{envData.searchEnabled && <div className='px-2 py-1 flex flex-col relative'>
<input type='text' className='input input-xs bg-base-200' placeholder='搜索字幕内容' value={searchText} onChange={onSearchTextChange}/>
{searchText && <button className='absolute top-1 right-2 btn btn-ghost btn-xs btn-circle text-base-content/75' onClick={onClearSearchText}><AiOutlineCloseCircle/></button>}
</div>}
{/* auto scroll btn */}
{!autoScroll && <div
className='absolute z-[999] top-[96px] right-6 tooltip tooltip-left cursor-pointer rounded-full bg-primary/25 hover:bg-primary/75 text-primary-content p-1.5 text-xl'
data-tip='开启自动滚动'
onClick={onEnableAutoScroll}>
<FaRegArrowAltCircleDown className={autoScroll ? 'text-accent' : ''}/>
</div>}
{/* body */}
<div ref={bodyRef} onWheel={onWheel}
className={classNames('flex flex-col gap-1.5 overflow-y-auto select-text scroll-smooth', floatKeyPointsSegIdx != null && 'pb-[100px]')}
style={{
height: `${totalHeight - HEADER_HEIGHT - TITLE_HEIGHT}px`
height: `${totalHeight - HEADER_HEIGHT - TITLE_HEIGHT - (envData.searchEnabled?SEARCH_BAR_HEIGHT:0)}px`
}}
>
{segments?.map((segment, segmentIdx) => <SegmentCard key={segment.startIdx} segment={segment} segmentIdx={segmentIdx} bodyRef={bodyRef}/>)}
{/* tip */}
<div className='flex flex-col items-center text-center pt-1 pb-2'>
<div className='font-semibold text-accent'>💡<span className='underline underline-offset-4'></span>💡</div>
<div className='text-sm desc px-2'><span className='text-amber-600 font-semibold'></span><span className='text-secondary/75 font-semibold'></span>🥳</div>

View File

@@ -4,11 +4,7 @@ import {getDisplay, getTransText} from '../util/biz_util'
import classNames from 'classnames'
const CompactSegmentItem = (props: {
item: {
from: number
to: number
content: string
}
item: TranscriptItem
idx: number
isIn: boolean
last: boolean
@@ -26,7 +22,7 @@ const CompactSegmentItem = (props: {
<span className={'pl-1 pr-0.5 py-0.5 cursor-pointer rounded-sm hover:bg-base-200'} onClick={moveCallback}>
<text className={classNames('font-medium', isIn ? 'text-primary underline' : '')}>{display.main}</text>
{display.sub && <text className='desc'>({display.sub})</text>}</span>
<span>{!last && ', '}</span>
<span>{!last && ' '}</span>
</div>
}

View File

@@ -5,11 +5,7 @@ import {getDisplay, getTransText} from '../util/biz_util'
import classNames from 'classnames'
const NormalSegmentItem = (props: {
item: {
from: number
to: number
content: string
}
item: TranscriptItem
idx: number
isIn: boolean
moveCallback: (event: any) => void

View File

@@ -1,4 +1,4 @@
import React, {useCallback, useEffect, useRef} from 'react'
import React, {useCallback, useEffect, useMemo, useRef} from 'react'
import {useAppDispatch, useAppSelector} from '../hooks/redux'
import useSubtitle from '../hooks/useSubtitle'
import {setCheckAutoScroll, setCurOffsetTop, setNeedScroll} from '../redux/envReducer'
@@ -7,21 +7,27 @@ import CompactSegmentItem from './CompactSegmentItem'
const SegmentItem = (props: {
bodyRef: any
item: {
from: number
to: number
content: string
}
item: TranscriptItem
idx: number
isIn: boolean
needScroll?: boolean
last: boolean
}) => {
const dispatch = useAppDispatch()
const {bodyRef, item, idx, isIn, needScroll, last} = props
const dispatch = useAppDispatch()
const ref = useRef<any>()
const {move} = useSubtitle()
const compact = useAppSelector(state => state.env.compact)
const searchText = useAppSelector(state => state.env.searchText)
const searchResult = useAppSelector(state => state.env.searchResult)
const display = useMemo(() => {
if (searchText) {
return searchResult.has(item.idx) ? 'inline' : 'none'
} else {
return 'inline'
}
}, [item.idx, searchResult, searchText])
const moveCallback = useCallback((event: any) => {
if (event.altKey) { // 复制
@@ -47,7 +53,9 @@ const SegmentItem = (props: {
}
}, [dispatch, isIn])
return <span ref={ref}>
return <span ref={ref} style={{
display
}}>
{compact
? <CompactSegmentItem
item={item}

View File

@@ -58,6 +58,8 @@ const Settings = () => {
// const {value: autoScrollValue, onChange: setAutoScrollValue} = useEventChecked(envData.autoScroll)
const {value: translateEnableValue, onChange: setTranslateEnableValue} = useEventChecked(envData.translateEnable)
const {value: summarizeEnableValue, onChange: setSummarizeEnableValue} = useEventChecked(envData.summarizeEnable)
const {value: searchEnabledValue, onChange: setSearchEnabledValue} = useEventChecked(envData.searchEnabled)
const {value: cnSearchEnabledValue, onChange: setCnSearchEnabledValue} = useEventChecked(envData.cnSearchEnabled)
const {value: summarizeFloatValue, onChange: setSummarizeFloatValue} = useEventChecked(envData.summarizeFloat)
const [apiKeyValue, { onChange: onChangeApiKeyValue }] = useEventTarget({initialValue: envData.apiKey??''})
const [serverUrlValue, setServerUrlValue] = useState(envData.serverUrl)
@@ -112,10 +114,12 @@ const Settings = () => {
fetchAmount: fetchAmountValue,
fontSize: fontSizeValue,
prompts: promptsValue,
searchEnabled: searchEnabledValue,
cnSearchEnabled: cnSearchEnabledValue,
}))
dispatch(setPage(PAGE_MAIN))
toast.success('保存成功')
}, [modelValue, promptsValue, fontSizeValue, apiKeyValue, autoExpandValue, dispatch, fetchAmountValue, hideOnDisableAutoTranslateValue, languageValue, serverUrlValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, themeValue, transDisplayValue, translateEnableValue, wordsValue])
}, [dispatch, autoExpandValue, apiKeyValue, serverUrlValue, modelValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue])
const onCancel = useCallback(() => {
dispatch(setPage(PAGE_MAIN))
@@ -300,6 +304,18 @@ const Settings = () => {
</div>
</FormItem>
</Section>
<Section title={<div className='flex items-center'>
</div>}>
<FormItem title='启用搜索' htmlFor='searchEnabled' tip='是否启用字幕搜索功能'>
<input id='searchEnabled' type='checkbox' className='toggle toggle-primary' checked={searchEnabledValue}
onChange={setSearchEnabledValue}/>
</FormItem>
<FormItem title='拼音搜索' htmlFor='cnSearchEnabled' tip='是否启用中文拼音搜索'>
<input id='cnSearchEnabled' type='checkbox' className='toggle toggle-primary' checked={cnSearchEnabledValue}
onChange={setCnSearchEnabledValue}/>
</FormItem>
</Section>
<div className='flex justify-center gap-5'>
<button className='btn btn-primary btn-sm' onClick={onSave}></button>
<button className='btn btn-sm' onClick={onCancel}></button>