You've already forked bilibili-subtitle
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1a475d1f13 | ||
![]() |
4a1ea0dbbe | ||
![]() |
af80b8a51c | ||
![]() |
404ab904cc | ||
![]() |
d53884269a | ||
![]() |
e4693cde6a | ||
![]() |
fea22fae61 | ||
![]() |
d5b5b5d374 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "bilibili-subtitle",
|
"name": "bilibili-subtitle",
|
||||||
"version": "1.13.0",
|
"version": "1.14.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "哔哩哔哩字幕列表",
|
"description": "哔哩哔哩字幕列表",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import {MutableRefObject, useCallback, useEffect, useMemo, useRef} from 'react'
|
import {MutableRefObject, useCallback, useEffect, useMemo, useRef} from 'react'
|
||||||
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
||||||
import {setFloatKeyPointsSegIdx, setSegmentFold, setTempData} from '../redux/envReducer'
|
import {setFloatKeyPointsSegIdx, setNeedScroll, setSegmentFold, setTempData} from '../redux/envReducer'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import {FaClipboardList, FaComments} from 'react-icons/fa'
|
import {FaClipboardList, FaComments} from 'react-icons/fa'
|
||||||
import {SUMMARIZE_THRESHOLD, SUMMARIZE_TYPES} from '../consts/const'
|
import {SUMMARIZE_THRESHOLD, SUMMARIZE_TYPES} from '../consts/const'
|
||||||
@@ -13,6 +13,7 @@ import SegmentItem from './SegmentItem'
|
|||||||
import {stopPopFunc} from '../utils/util'
|
import {stopPopFunc} from '../utils/util'
|
||||||
import useSubtitle from '../hooks/useSubtitle'
|
import useSubtitle from '../hooks/useSubtitle'
|
||||||
import DebateChat from './DebateChat'
|
import DebateChat from './DebateChat'
|
||||||
|
import { RootState } from '../store'
|
||||||
|
|
||||||
const SummarizeItemOverview = (props: {
|
const SummarizeItemOverview = (props: {
|
||||||
segment: Segment
|
segment: Segment
|
||||||
@@ -25,7 +26,7 @@ const SummarizeItemOverview = (props: {
|
|||||||
|
|
||||||
const {move} = useSubtitle()
|
const {move} = useSubtitle()
|
||||||
const time = parseStrTimeToSeconds(overviewItem.time)
|
const time = parseStrTimeToSeconds(overviewItem.time)
|
||||||
const currentTime = useAppSelector(state => state.env.currentTime)
|
const currentTime = useAppSelector((state: RootState) => state.currentTime.currentTime)
|
||||||
const isIn = useMemo(() => {
|
const isIn = useMemo(() => {
|
||||||
if (currentTime != null) {
|
if (currentTime != null) {
|
||||||
// check in current segment
|
// check in current segment
|
||||||
@@ -158,6 +159,7 @@ const SegmentCard = (props: {
|
|||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
}, [curSummaryType, segment.summaries])
|
}, [curSummaryType, segment.summaries])
|
||||||
|
const {move} = useSubtitle()
|
||||||
|
|
||||||
const onFold = useCallback(() => {
|
const onFold = useCallback(() => {
|
||||||
dispatch(setSegmentFold({
|
dispatch(setSegmentFold({
|
||||||
@@ -166,6 +168,21 @@ const SegmentCard = (props: {
|
|||||||
}))
|
}))
|
||||||
}, [dispatch, segment.fold, segment.startIdx])
|
}, [dispatch, segment.fold, segment.startIdx])
|
||||||
|
|
||||||
|
const onChapterClick = useCallback(() => {
|
||||||
|
if (segment.items && segment.items.length > 0) {
|
||||||
|
// 展开当前segment
|
||||||
|
if (segment.fold) {
|
||||||
|
dispatch(setSegmentFold({
|
||||||
|
segmentStartIdx: segment.startIdx,
|
||||||
|
fold: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstItem = segment.items[0]
|
||||||
|
move(firstItem.from, false)
|
||||||
|
}
|
||||||
|
}, [dispatch, move, segment.fold, segment.items, segment.startIdx])
|
||||||
|
|
||||||
// 检测设置floatKeyPointsSegIdx
|
// 检测设置floatKeyPointsSegIdx
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (summarizeFloat) { // 已启用
|
if (summarizeFloat) { // 已启用
|
||||||
@@ -211,6 +228,10 @@ const SegmentCard = (props: {
|
|||||||
|
|
||||||
return <div
|
return <div
|
||||||
className={classNames('border border-base-300 bg-base-200/25 rounded flex flex-col m-1.5 p-1.5 gap-1', showCurrent && 'shadow shadow-md')}>
|
className={classNames('border border-base-300 bg-base-200/25 rounded flex flex-col m-1.5 p-1.5 gap-1', showCurrent && 'shadow shadow-md')}>
|
||||||
|
{/* 章节标题 */}
|
||||||
|
{segment.chapterTitle && <div className='text-center py-1 px-2 bg-primary/10 rounded text-sm font-semibold text-primary border-b border-primary/20 cursor-pointer hover:bg-primary/20 transition-colors' onClick={onChapterClick}>
|
||||||
|
{segment.chapterTitle}
|
||||||
|
</div>}
|
||||||
<div className='relative flex justify-center min-h-[20px]'>
|
<div className='relative flex justify-center min-h-[20px]'>
|
||||||
{segments != null && segments.length > 0 &&
|
{segments != null && segments.length > 0 &&
|
||||||
<div className='absolute left-0 top-0 bottom-0 text-xs select-none flex-center desc'>
|
<div className='absolute left-0 top-0 bottom-0 text-xs select-none flex-center desc'>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { setAuthor, setCtime, setCurFetched, setCurInfo, setData, setInfos, setTitle, setUrl } from '@/redux/envReducer'
|
import { setAuthor, setChapters, setCtime, setCurFetched, setCurInfo, setData, setInfos, setTitle, setUrl } from '@/redux/envReducer'
|
||||||
import { useAppDispatch, useAppSelector } from './redux'
|
import { useAppDispatch, useAppSelector } from './redux'
|
||||||
import { AllAPPMessages, AllExtensionMessages, AllInjectMessages } from '@/message-typings'
|
import { AllAPPMessages, AllExtensionMessages, AllInjectMessages } from '@/message-typings'
|
||||||
import { useMessaging, useMessagingService } from '@kky002/kky-message'
|
import { useMessaging, useMessagingService } from '@kky002/kky-message'
|
||||||
@@ -19,6 +19,7 @@ const useMessageService = () => {
|
|||||||
dispatch(setData(undefined))
|
dispatch(setData(undefined))
|
||||||
},
|
},
|
||||||
SET_VIDEO_INFO: async (params, context: MethodContext) => {
|
SET_VIDEO_INFO: async (params, context: MethodContext) => {
|
||||||
|
dispatch(setChapters(params.chapters))
|
||||||
dispatch(setInfos(params.infos))
|
dispatch(setInfos(params.infos))
|
||||||
dispatch(setUrl(params.url))
|
dispatch(setUrl(params.url))
|
||||||
dispatch(setTitle(params.title))
|
dispatch(setTitle(params.title))
|
||||||
|
@@ -4,15 +4,11 @@ import {
|
|||||||
setCurFetched,
|
setCurFetched,
|
||||||
setCurIdx,
|
setCurIdx,
|
||||||
setCurInfo,
|
setCurInfo,
|
||||||
setCurrentTime,
|
|
||||||
setData,
|
setData,
|
||||||
setInfos,
|
|
||||||
setNoVideo,
|
setNoVideo,
|
||||||
setSegmentFold,
|
setSegmentFold,
|
||||||
setSegments,
|
setSegments,
|
||||||
setTitle,
|
|
||||||
setTotalHeight,
|
setTotalHeight,
|
||||||
setUrl,
|
|
||||||
setTempData,
|
setTempData,
|
||||||
} from '../redux/envReducer'
|
} from '../redux/envReducer'
|
||||||
import {EventBusContext} from '../Router'
|
import {EventBusContext} from '../Router'
|
||||||
@@ -20,6 +16,8 @@ import {EVENT_EXPAND, TOTAL_HEIGHT_MAX, TOTAL_HEIGHT_MIN, WORDS_MIN, WORDS_RATE}
|
|||||||
import {useAsyncEffect, useInterval} from 'ahooks'
|
import {useAsyncEffect, useInterval} from 'ahooks'
|
||||||
import {getModelMaxTokens, getWholeText} from '../utils/bizUtil'
|
import {getModelMaxTokens, getWholeText} from '../utils/bizUtil'
|
||||||
import { useMessage } from './useMessageService'
|
import { useMessage } from './useMessageService'
|
||||||
|
import { setCurrentTime } from '../redux/currentTimeReducer'
|
||||||
|
import { RootState } from '../store'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service是单例,类似后端的服务概念
|
* Service是单例,类似后端的服务概念
|
||||||
@@ -31,10 +29,11 @@ const useSubtitleService = () => {
|
|||||||
const curFetched = useAppSelector(state => state.env.curFetched)
|
const curFetched = useAppSelector(state => state.env.curFetched)
|
||||||
const fold = useAppSelector(state => state.env.fold)
|
const fold = useAppSelector(state => state.env.fold)
|
||||||
const envReady = useAppSelector(state => state.env.envReady)
|
const envReady = useAppSelector(state => state.env.envReady)
|
||||||
const envData = useAppSelector(state => state.env.envData)
|
const envData = useAppSelector((state: RootState) => state.env.envData)
|
||||||
const data = useAppSelector(state => state.env.data)
|
const data = useAppSelector((state: RootState) => state.env.data)
|
||||||
const currentTime = useAppSelector(state => state.env.currentTime)
|
const chapters = useAppSelector((state: RootState) => state.env.chapters)
|
||||||
const curIdx = useAppSelector(state => state.env.curIdx)
|
const currentTime = useAppSelector((state: RootState) => state.currentTime.currentTime)
|
||||||
|
const curIdx = useAppSelector((state: RootState) => state.env.curIdx)
|
||||||
const eventBus = useContext(EventBusContext)
|
const eventBus = useContext(EventBusContext)
|
||||||
const needScroll = useAppSelector(state => state.env.needScroll)
|
const needScroll = useAppSelector(state => state.env.needScroll)
|
||||||
const segments = useAppSelector(state => state.env.segments)
|
const segments = useAppSelector(state => state.env.segments)
|
||||||
@@ -113,19 +112,22 @@ const useSubtitleService = () => {
|
|||||||
|
|
||||||
// 更新当前位置
|
// 更新当前位置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let curIdx
|
let newCurIdx
|
||||||
if (((data?.body) != null) && currentTime) {
|
if (((data?.body) != null) && currentTime) {
|
||||||
for (let i=0; i<data.body.length; i++) {
|
for (let i=0; i<data.body.length; i++) {
|
||||||
const item = data.body[i]
|
const item = data.body[i]
|
||||||
if (item.from && currentTime < item.from) {
|
if (item.from && currentTime < item.from) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
curIdx = i
|
newCurIdx = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch(setCurIdx(curIdx))
|
// 只有当索引发生变化时才更新状态
|
||||||
}, [currentTime, data?.body, dispatch])
|
if (newCurIdx !== curIdx) {
|
||||||
|
dispatch(setCurIdx(newCurIdx))
|
||||||
|
}
|
||||||
|
}, [currentTime, data?.body, dispatch, curIdx])
|
||||||
|
|
||||||
// 需要滚动 => segment自动展开
|
// 需要滚动 => segment自动展开
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -157,24 +159,78 @@ const useSubtitleService = () => {
|
|||||||
size = Math.max(size, WORDS_MIN)
|
size = Math.max(size, WORDS_MIN)
|
||||||
|
|
||||||
segments = []
|
segments = []
|
||||||
let transcriptItems: TranscriptItem[] = []
|
|
||||||
let totalLength = 0
|
// 如果启用章节模式且有章节信息,按章节分割
|
||||||
for (let i = 0; i < items.length; i++) {
|
if ((envData.chapterMode ?? true) && chapters && chapters.length > 0) {
|
||||||
const item = items[i]
|
for (let chapterIdx = 0; chapterIdx < chapters.length; chapterIdx++) {
|
||||||
transcriptItems.push(item)
|
const chapter = chapters[chapterIdx]
|
||||||
totalLength += item.content.length
|
const nextChapter = chapters[chapterIdx + 1]
|
||||||
if (totalLength >= size || i === items.length-1) { // new segment or last
|
|
||||||
// add
|
// 找到属于当前章节的字幕项
|
||||||
segments.push({
|
const chapterItems = items.filter(item => {
|
||||||
items: transcriptItems,
|
const itemTime = item.from
|
||||||
startIdx: transcriptItems[0].idx,
|
return itemTime >= chapter.from && (nextChapter ? itemTime < nextChapter.from : true)
|
||||||
endIdx: transcriptItems[transcriptItems.length - 1].idx,
|
|
||||||
text: getWholeText(transcriptItems.map(item => item.content)),
|
|
||||||
summaries: {},
|
|
||||||
})
|
})
|
||||||
// reset
|
|
||||||
transcriptItems = []
|
if (chapterItems.length === 0) continue
|
||||||
totalLength = 0
|
|
||||||
|
// 如果章节内容过长,需要进一步分割
|
||||||
|
const chapterText = getWholeText(chapterItems.map(item => item.content))
|
||||||
|
if (chapterText.length <= size) {
|
||||||
|
// 章节内容不长,作为一个segment
|
||||||
|
segments.push({
|
||||||
|
items: chapterItems,
|
||||||
|
startIdx: chapterItems[0].idx,
|
||||||
|
endIdx: chapterItems[chapterItems.length - 1].idx,
|
||||||
|
text: chapterText,
|
||||||
|
chapterTitle: chapter.content,
|
||||||
|
summaries: {},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 章节内容过长,需要分割成多个segment
|
||||||
|
let transcriptItems: TranscriptItem[] = []
|
||||||
|
let totalLength = 0
|
||||||
|
for (let i = 0; i < chapterItems.length; i++) {
|
||||||
|
const item = chapterItems[i]
|
||||||
|
transcriptItems.push(item)
|
||||||
|
totalLength += item.content.length
|
||||||
|
if (totalLength >= size || i === chapterItems.length - 1) {
|
||||||
|
segments.push({
|
||||||
|
items: transcriptItems,
|
||||||
|
startIdx: transcriptItems[0].idx,
|
||||||
|
endIdx: transcriptItems[transcriptItems.length - 1].idx,
|
||||||
|
text: getWholeText(transcriptItems.map(item => item.content)),
|
||||||
|
chapterTitle: chapter.content,
|
||||||
|
summaries: {},
|
||||||
|
})
|
||||||
|
// reset
|
||||||
|
transcriptItems = []
|
||||||
|
totalLength = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有章节信息,按原来的逻辑分割
|
||||||
|
let transcriptItems: TranscriptItem[] = []
|
||||||
|
let totalLength = 0
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const item = items[i]
|
||||||
|
transcriptItems.push(item)
|
||||||
|
totalLength += item.content.length
|
||||||
|
if (totalLength >= size || i === items.length-1) { // new segment or last
|
||||||
|
// add
|
||||||
|
segments.push({
|
||||||
|
items: transcriptItems,
|
||||||
|
startIdx: transcriptItems[0].idx,
|
||||||
|
endIdx: transcriptItems[transcriptItems.length - 1].idx,
|
||||||
|
text: getWholeText(transcriptItems.map(item => item.content)),
|
||||||
|
summaries: {},
|
||||||
|
})
|
||||||
|
// reset
|
||||||
|
transcriptItems = []
|
||||||
|
totalLength = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // 都放一个分段
|
} else { // 都放一个分段
|
||||||
@@ -188,12 +244,15 @@ const useSubtitleService = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch(setSegments(segments))
|
dispatch(setSegments(segments))
|
||||||
}, [data?.body, dispatch, envData])
|
}, [data?.body, dispatch, envData, chapters])
|
||||||
|
|
||||||
// 每0.5秒更新当前视频时间
|
// 每0.5秒更新当前视频时间
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
sendInject(null, 'GET_VIDEO_STATUS', {}).then(status => {
|
sendInject(null, 'GET_VIDEO_STATUS', {}).then(status => {
|
||||||
dispatch(setCurrentTime(status.currentTime))
|
// 只有当时间发生显著变化时才更新状态(差异大于0.1秒),避免不必要的重新渲染
|
||||||
|
if (currentTime == null || Math.abs(status.currentTime - currentTime) > 0.1) {
|
||||||
|
dispatch(setCurrentTime(status.currentTime))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
|
@@ -161,6 +161,21 @@ const debug = (...args: any[]) => {
|
|||||||
if (aidOrBvid) {
|
if (aidOrBvid) {
|
||||||
// aid,pages
|
// aid,pages
|
||||||
let cid: string | undefined
|
let cid: string | undefined
|
||||||
|
/**
|
||||||
|
* [
|
||||||
|
{
|
||||||
|
"type": 2,
|
||||||
|
"from": 0,
|
||||||
|
"to": 152, //单位秒
|
||||||
|
"content": "发现美",
|
||||||
|
"imgUrl": "http://i0.hdslb.com/bfs/vchapter/29168372111_0.jpg",
|
||||||
|
"logoUrl": "",
|
||||||
|
"team_type": "",
|
||||||
|
"team_name": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
let chapters: any[] = []
|
||||||
let subtitles
|
let subtitles
|
||||||
if (aidOrBvid.toLowerCase().startsWith('av')) { // avxxx
|
if (aidOrBvid.toLowerCase().startsWith('av')) { // avxxx
|
||||||
aid = parseInt(aidOrBvid.slice(2))
|
aid = parseInt(aidOrBvid.slice(2))
|
||||||
@@ -170,6 +185,7 @@ const debug = (...args: any[]) => {
|
|||||||
author = pages[0].owner?.name
|
author = pages[0].owner?.name
|
||||||
title = pages[0].part
|
title = pages[0].part
|
||||||
await fetch(`https://api.bilibili.com/x/player/wbi/v2?aid=${aid}&cid=${cid!}`, { credentials: 'include' }).then(async res => await res.json()).then(res => {
|
await fetch(`https://api.bilibili.com/x/player/wbi/v2?aid=${aid}&cid=${cid!}`, { credentials: 'include' }).then(async res => await res.json()).then(res => {
|
||||||
|
chapters = res.data.view_points ?? []
|
||||||
subtitles = res.data.subtitle.subtitles
|
subtitles = res.data.subtitle.subtitles
|
||||||
})
|
})
|
||||||
} else { // bvxxx
|
} else { // bvxxx
|
||||||
@@ -182,10 +198,14 @@ const debug = (...args: any[]) => {
|
|||||||
pages = res.data.pages
|
pages = res.data.pages
|
||||||
})
|
})
|
||||||
await fetch(`https://api.bilibili.com/x/player/wbi/v2?aid=${aid!}&cid=${cid!}`, { credentials: 'include' }).then(async res => await res.json()).then(res => {
|
await fetch(`https://api.bilibili.com/x/player/wbi/v2?aid=${aid!}&cid=${cid!}`, { credentials: 'include' }).then(async res => await res.json()).then(res => {
|
||||||
|
chapters = res.data.view_points ?? []
|
||||||
subtitles = res.data.subtitle.subtitles
|
subtitles = res.data.subtitle.subtitles
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//筛选chapters里type为2的
|
||||||
|
chapters = chapters.filter(chapter => chapter.type === 2)
|
||||||
|
|
||||||
// pagesMap
|
// pagesMap
|
||||||
pagesMap = {}
|
pagesMap = {}
|
||||||
pages.forEach(page => {
|
pages.forEach(page => {
|
||||||
@@ -202,6 +222,7 @@ const debug = (...args: any[]) => {
|
|||||||
ctime,
|
ctime,
|
||||||
author,
|
author,
|
||||||
pages,
|
pages,
|
||||||
|
chapters,
|
||||||
infos: subtitles,
|
infos: subtitles,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
2
src/message-typings.d.ts
vendored
2
src/message-typings.d.ts
vendored
@@ -80,7 +80,7 @@ interface AppSetInfosMessage extends AppMessage<{ infos: any }> {
|
|||||||
method: 'SET_INFOS'
|
method: 'SET_INFOS'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AppSetVideoInfoMessage extends AppMessage<{ url: string, title: string, aid: number | null, ctime: number | null, author?: string, pages: any, infos: any }> {
|
interface AppSetVideoInfoMessage extends AppMessage<{ url: string, title: string, aid: number | null, ctime: number | null, author?: string, pages: any, chapters: any, infos: any }> {
|
||||||
method: 'SET_VIDEO_INFO'
|
method: 'SET_VIDEO_INFO'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -77,6 +77,7 @@ const OptionsPage = () => {
|
|||||||
const {value: askEnabledValue, onChange: setAskEnabledValue} = useEventChecked(envData.askEnabled??ASK_ENABLED_DEFAULT)
|
const {value: askEnabledValue, onChange: setAskEnabledValue} = useEventChecked(envData.askEnabled??ASK_ENABLED_DEFAULT)
|
||||||
const {value: cnSearchEnabledValue, onChange: setCnSearchEnabledValue} = useEventChecked(envData.cnSearchEnabled)
|
const {value: cnSearchEnabledValue, onChange: setCnSearchEnabledValue} = useEventChecked(envData.cnSearchEnabled)
|
||||||
const {value: summarizeFloatValue, onChange: setSummarizeFloatValue} = useEventChecked(envData.summarizeFloat)
|
const {value: summarizeFloatValue, onChange: setSummarizeFloatValue} = useEventChecked(envData.summarizeFloat)
|
||||||
|
const {value: chapterModeValue, onChange: setChapterModeValue} = useEventChecked(envData.chapterMode ?? true)
|
||||||
const [apiKeyValue, { onChange: onChangeApiKeyValue }] = useEventTarget({initialValue: envData.apiKey??''})
|
const [apiKeyValue, { onChange: onChangeApiKeyValue }] = useEventTarget({initialValue: envData.apiKey??''})
|
||||||
const [serverUrlValue, setServerUrlValue] = useState(envData.serverUrl)
|
const [serverUrlValue, setServerUrlValue] = useState(envData.serverUrl)
|
||||||
const [languageValue, { onChange: onChangeLanguageValue }] = useEventTarget({initialValue: envData.language??LANGUAGE_DEFAULT})
|
const [languageValue, { onChange: onChangeLanguageValue }] = useEventTarget({initialValue: envData.language??LANGUAGE_DEFAULT})
|
||||||
@@ -139,6 +140,7 @@ const OptionsPage = () => {
|
|||||||
searchEnabled: searchEnabledValue,
|
searchEnabled: searchEnabledValue,
|
||||||
cnSearchEnabled: cnSearchEnabledValue,
|
cnSearchEnabled: cnSearchEnabledValue,
|
||||||
askEnabled: askEnabledValue,
|
askEnabled: askEnabledValue,
|
||||||
|
chapterMode: chapterModeValue,
|
||||||
}))
|
}))
|
||||||
toast.success('保存成功')
|
toast.success('保存成功')
|
||||||
sendExtension(null, 'CLOSE_SIDE_PANEL')
|
sendExtension(null, 'CLOSE_SIDE_PANEL')
|
||||||
@@ -146,7 +148,7 @@ const OptionsPage = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.close()
|
window.close()
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}, [dispatch, sendExtension, sidePanelValue, autoInsertValue, autoExpandValue, apiKeyValue, serverUrlValue, modelValue, customModelValue, customModelTokensValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue, askEnabledValue])
|
}, [dispatch, sendExtension, sidePanelValue, autoInsertValue, autoExpandValue, apiKeyValue, serverUrlValue, modelValue, customModelValue, customModelTokensValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue, askEnabledValue, chapterModeValue])
|
||||||
|
|
||||||
const onCancel = useCallback(() => {
|
const onCancel = useCallback(() => {
|
||||||
window.close()
|
window.close()
|
||||||
@@ -207,6 +209,10 @@ const OptionsPage = () => {
|
|||||||
<input id='autoExpand' type='checkbox' className='toggle toggle-primary' checked={autoExpandValue}
|
<input id='autoExpand' type='checkbox' className='toggle toggle-primary' checked={autoExpandValue}
|
||||||
onChange={setAutoExpandValue}/>
|
onChange={setAutoExpandValue}/>
|
||||||
</FormItem>}
|
</FormItem>}
|
||||||
|
<FormItem title='章节模式' htmlFor='chapterMode' tip='如果视频包含章节,则会按章节分割(会导致总结只能按章节来)'>
|
||||||
|
<input id='chapterMode' type='checkbox' className='toggle toggle-primary' checked={chapterModeValue}
|
||||||
|
onChange={setChapterModeValue}/>
|
||||||
|
</FormItem>
|
||||||
<FormItem title='主题'>
|
<FormItem title='主题'>
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
<button onClick={onSelTheme1} className={classNames('btn btn-sm no-animation', (!themeValue || themeValue === 'system')?'btn-active':'')}>系统</button>
|
<button onClick={onSelTheme1} className={classNames('btn btn-sm no-animation', (!themeValue || themeValue === 'system')?'btn-active':'')}>系统</button>
|
||||||
|
23
src/redux/currentTimeReducer.ts
Normal file
23
src/redux/currentTimeReducer.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
interface CurrentTimeState {
|
||||||
|
currentTime?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: CurrentTimeState = {
|
||||||
|
currentTime: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const slice = createSlice({
|
||||||
|
name: 'currentTime',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setCurrentTime: (state, action: PayloadAction<number | undefined>) => {
|
||||||
|
state.currentTime = action.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { setCurrentTime } = slice.actions
|
||||||
|
|
||||||
|
export default slice.reducer
|
@@ -23,7 +23,7 @@ interface EnvState {
|
|||||||
totalHeight: number
|
totalHeight: number
|
||||||
curIdx?: number // 从0开始
|
curIdx?: number // 从0开始
|
||||||
needScroll?: boolean
|
needScroll?: boolean
|
||||||
currentTime?: number
|
chapters?: Chapter[]
|
||||||
infos?: any[]
|
infos?: any[]
|
||||||
curInfo?: any
|
curInfo?: any
|
||||||
curFetched?: boolean
|
curFetched?: boolean
|
||||||
@@ -263,9 +263,6 @@ export const slice = createSlice({
|
|||||||
setNeedScroll: (state, action: PayloadAction<boolean>) => {
|
setNeedScroll: (state, action: PayloadAction<boolean>) => {
|
||||||
state.needScroll = action.payload
|
state.needScroll = action.payload
|
||||||
},
|
},
|
||||||
setCurrentTime: (state, action: PayloadAction<number | undefined>) => {
|
|
||||||
state.currentTime = action.payload
|
|
||||||
},
|
|
||||||
setUrl: (state, action: PayloadAction<string | undefined>) => {
|
setUrl: (state, action: PayloadAction<string | undefined>) => {
|
||||||
state.url = action.payload
|
state.url = action.payload
|
||||||
},
|
},
|
||||||
@@ -278,6 +275,9 @@ export const slice = createSlice({
|
|||||||
setAuthor: (state, action: PayloadAction<string | undefined>) => {
|
setAuthor: (state, action: PayloadAction<string | undefined>) => {
|
||||||
state.author = action.payload
|
state.author = action.payload
|
||||||
},
|
},
|
||||||
|
setChapters: (state, action: PayloadAction<Chapter[]>) => {
|
||||||
|
state.chapters = action.payload
|
||||||
|
},
|
||||||
setInfos: (state, action: PayloadAction<any[]>) => {
|
setInfos: (state, action: PayloadAction<any[]>) => {
|
||||||
state.infos = action.payload
|
state.infos = action.payload
|
||||||
},
|
},
|
||||||
@@ -337,7 +337,6 @@ export const {
|
|||||||
setCurIdx,
|
setCurIdx,
|
||||||
setEnvData,
|
setEnvData,
|
||||||
setEnvReady,
|
setEnvReady,
|
||||||
setCurrentTime,
|
|
||||||
setInfos,
|
setInfos,
|
||||||
setCurInfo,
|
setCurInfo,
|
||||||
setCurFetched,
|
setCurFetched,
|
||||||
@@ -351,6 +350,7 @@ export const {
|
|||||||
mergeAskInfo,
|
mergeAskInfo,
|
||||||
setCtime,
|
setCtime,
|
||||||
setAuthor,
|
setAuthor,
|
||||||
|
setChapters,
|
||||||
} = slice.actions
|
} = slice.actions
|
||||||
|
|
||||||
export default slice.reducer
|
export default slice.reducer
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import {configureStore} from '@reduxjs/toolkit'
|
import {configureStore} from '@reduxjs/toolkit'
|
||||||
import envReducer from './redux/envReducer'
|
import envReducer from './redux/envReducer'
|
||||||
|
import currentTimeReducer from './redux/currentTimeReducer'
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
env: envReducer,
|
env: envReducer,
|
||||||
|
currentTime: currentTimeReducer,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
10
src/typings.d.ts
vendored
10
src/typings.d.ts
vendored
@@ -30,6 +30,9 @@ interface EnvData {
|
|||||||
theme?: 'system' | 'light' | 'dark'
|
theme?: 'system' | 'light' | 'dark'
|
||||||
fontSize?: 'normal' | 'large'
|
fontSize?: 'normal' | 'large'
|
||||||
|
|
||||||
|
// chapter
|
||||||
|
chapterMode?: boolean // 是否启用章节模式,undefined/null/true表示启用,false表示禁用
|
||||||
|
|
||||||
// search
|
// search
|
||||||
searchEnabled?: boolean
|
searchEnabled?: boolean
|
||||||
cnSearchEnabled?: boolean
|
cnSearchEnabled?: boolean
|
||||||
@@ -88,12 +91,19 @@ interface TranscriptItem {
|
|||||||
idx: number
|
idx: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Chapter {
|
||||||
|
from: number
|
||||||
|
to: number
|
||||||
|
content: string // 标题
|
||||||
|
}
|
||||||
|
|
||||||
interface Segment {
|
interface Segment {
|
||||||
items: TranscriptItem[]
|
items: TranscriptItem[]
|
||||||
startIdx: number // 从1开始
|
startIdx: number // 从1开始
|
||||||
endIdx: number
|
endIdx: number
|
||||||
text: string
|
text: string
|
||||||
fold?: boolean
|
fold?: boolean
|
||||||
|
chapterTitle?: string // 章节标题
|
||||||
summaries: {
|
summaries: {
|
||||||
[type: string]: Summary
|
[type: string]: Summary
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user