11 Commits

Author SHA1 Message Date
IndieKKY
da7357c7eb chore: release 1.10.6 2024-09-19 16:33:44 +08:00
IndieKKY
1ff47ee148 fix 2024-09-19 16:33:29 +08:00
IndieKKY
1bf8188980 chore: release 1.10.5 2024-09-19 16:25:23 +08:00
IndieKKY
3a9a8d9d56 fix 2024-09-19 16:24:55 +08:00
IndieKKY
78b5d7a18b fix 2024-09-19 16:23:13 +08:00
IndieKKY
ff6dae7a21 chore: release 1.10.4 2024-09-19 16:05:58 +08:00
IndieKKY
3adb541e99 fix 2024-09-19 16:05:42 +08:00
IndieKKY
1ef7537251 chore: release 1.10.3 2024-09-19 15:08:30 +08:00
IndieKKY
be6b94164d 跳转评论 2024-09-19 15:08:15 +08:00
IndieKKY
1f1d48b56a chore: release 1.10.2 2024-07-23 10:01:09 +08:00
IndieKKY
02b7a09f42 更新模型 2024-07-23 10:00:51 +08:00
17 changed files with 3221 additions and 2362 deletions

1
.cursorrules Normal file
View File

@@ -0,0 +1 @@
This chrome extension project use typescript, react, tailwindcss, daisyui.

View File

@@ -1 +1 @@
VITE_ENV=web-dev
VITE_ENV=web-dev

View File

@@ -1,3 +1,3 @@
NODE_ENV=production
VITE_ENV=chrome
VITE_ENV=chrome

View File

@@ -1,7 +1,7 @@
{
"name": "哔哩哔哩字幕列表",
"description": "显示B站视频的字幕列表,可点击跳转与下载字幕,并支持翻译和总结字幕!",
"version": "1.10.1",
"version": "1.10.6",
"manifest_version": 3,
"permissions": [
"storage"

View File

@@ -1,15 +1,14 @@
{
"private": true,
"name": "bilibili-subtitle",
"version": "1.10.1",
"version": "1.10.6",
"type": "module",
"description": "哔哩哔哩字幕列表",
"main": "index.js",
"scripts": {
"dev": "vite",
"build": "tsc && vite build -m production_chrome",
"build_chrome": "pnpm run build && node fixChrome.cjs",
"build_firefox": "pnpm run build && node fixFirefox.cjs",
"build_chrome": "tsc && vite build -m production_chrome && node fixChrome.cjs",
"build_firefox": "tsc && vite build -m production_chrome && node fixFirefox.cjs",
"fix": "eslint --fix --quiet ."
},
"author": "IndieKKY",

5407
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,7 @@ import {openUrl} from '@kky002/kky-util'
import useKeyService from '../hooks/useKeyService'
import Ask from './Ask'
import {v4} from 'uuid'
import RateExtension from '../components/RateExtension'
const Body = () => {
const dispatch = useAppDispatch()
@@ -368,8 +369,8 @@ const Body = () => {
{/* </div> */}
{/* </div> */}
</div>
<div className='p-2'><RateExtension/></div>
</div>
{/* recommend */}
{/* <div className='p-0.5' style={{ */}
{/* height: `${RECOMMEND_HEIGHT}px` */}

View File

@@ -1,5 +1,5 @@
import React, {PropsWithChildren, useCallback, useMemo, useState} from 'react'
import {setEnvData, setPage} from '../redux/envReducer'
import {setEnvData, setPage, setTempData} from '../redux/envReducer'
import {useAppDispatch, useAppSelector} from '../hooks/redux'
import {
ASK_ENABLED_DEFAULT,
@@ -11,6 +11,7 @@ import {
LANGUAGES,
MODEL_DEFAULT,
MODEL_MAP,
MODEL_TIP,
MODELS,
PAGE_MAIN,
PROMPT_DEFAULTS,
@@ -257,13 +258,18 @@ const Settings = () => {
{MODELS.map(model => <option key={model.code} value={model.code}>{model.name}</option>)}
</select>
</FormItem>
<div className='desc text-xs'>
{MODEL_TIP}
</div>
{modelValue === 'custom' && <FormItem title='模型名' htmlFor='customModel'>
<input id='customModel' type='text' className='input input-sm input-bordered w-full' placeholder='llama2'
value={customModelValue} onChange={onChangeCustomModelValue}/>
</FormItem>}
{modelValue === 'custom' && <FormItem title='Token上限' htmlFor='customModelTokens'>
<input id='customModelTokens' type='number' className='input input-sm input-bordered w-full' placeholder={''+CUSTOM_MODEL_TOKENS}
value={customModelTokensValue} onChange={e => setCustomModelTokensValue(e.target.value?parseInt(e.target.value):undefined)}/>
<input id='customModelTokens' type='number' className='input input-sm input-bordered w-full'
placeholder={'' + CUSTOM_MODEL_TOKENS}
value={customModelTokensValue}
onChange={e => setCustomModelTokensValue(e.target.value ? parseInt(e.target.value) : undefined)}/>
</FormItem>}
</Section>}
@@ -274,10 +280,12 @@ const Settings = () => {
</FormItem>
<div>
<div className='desc text-xs'>
<div><a className='link link-primary' href='https://makersuite.google.com/app/apikey' target='_blank'
rel="noreferrer">Google AI Studio</a> ()
<div><a className='link link-primary' href='https://makersuite.google.com/app/apikey'
target='_blank'
rel="noreferrer">Google AI Studio</a> ()
</div>
<div className='text-xs text-error flex items-center'><IoWarning className='text-sm text-warning'/>!
</div>
<div className='text-xs text-error flex items-center'><IoWarning className='text-sm text-warning'/>!</div>
</div>
</div>
</Section>}
@@ -400,6 +408,12 @@ const Settings = () => {
<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>
{/* <button className='btn btn-sm' onClick={() => {
dispatch(setTempData({
reviewed: undefined,
// reviewActions: 0
}))
}}>重置</button> */}
</div>
</div>
</div>

View File

@@ -0,0 +1,58 @@
import React, { useState } from 'react';
import { FaStar } from 'react-icons/fa';
import { IoMdClose } from 'react-icons/io';
import { setTempData } from '../redux/envReducer';
import { useAppDispatch, useAppSelector } from '../hooks/redux';
import { openUrl } from '@kky002/kky-util';
import { isEdgeBrowser } from '../util/util';
const RateExtension: React.FC = () => {
const dispatch = useAppDispatch()
const [isHovered, setIsHovered] = useState(false);
const reviewed = useAppSelector(state => state.env.tempData.reviewed)
const handleRateClick = () => {
dispatch(setTempData({
reviewed: true
}))
// Chrome Web Store URL for your extension
if (isEdgeBrowser()) {
openUrl('https://microsoftedge.microsoft.com/addons/detail/lignnlhlpiefmcjkdkmfjdckhlaiajan')
} else {
openUrl('https://chromewebstore.google.com/webstore/detail/bciglihaegkdhoogebcdblfhppoilclp/reviews')
}
};
if (reviewed === true || reviewed === undefined) return null;
return (
<div className="relative bg-gradient-to-r from-primary to-secondary text-primary-content p-4 rounded-lg shadow-lg text-sm transition-all duration-300 ease-in-out hover:shadow-xl">
<button
onClick={() => {
dispatch(setTempData({
reviewed: true
}))
}}
className="absolute top-2 right-2 text-primary-content opacity-70 hover:opacity-100 transition-opacity"
>
<IoMdClose size={20} />
</button>
<h3 className="text-lg font-bold mb-2 animate-pulse"></h3>
<p className="mb-3"></p>
<button
onClick={handleRateClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className="btn btn-accent btn-sm gap-2 transition-all duration-300 ease-in-out hover:scale-105"
>
<FaStar className={`inline-block text-yellow-300 ${isHovered ? 'animate-spin' : ''}`} />
<span className="transition-transform duration-300 ease-in-out transform inline-block">
{isHovered ? '🚀' : '→'}
</span>
</button>
</div>
);
};
export default RateExtension;

View File

@@ -222,24 +222,21 @@ export const ASK_ENABLED_DEFAULT = true
export const DEFAULT_SERVER_URL_OPENAI = 'https://api.openai.com'
export const CUSTOM_MODEL_TOKENS = 16385
export const MODEL_TIP = '推荐gpt-4o-mini能力强价格低token上限大'
export const MODELS = [{
code: 'gpt-3.5-turbo',
name: 'gpt-3.5-turbo',
tokens: 4096,
code: 'gpt-4o-mini',
name: 'gpt-4o-mini',
tokens: 128000,
}, {
code: 'gpt-3.5-turbo-0125',
name: 'gpt-3.5-turbo-0125',
tokens: 16385,
}, {
code: 'gpt-3.5-turbo-1106',
name: 'gpt-3.5-turbo-1106',
tokens: 16385,
}, {
code: 'custom',
name: '自定义',
}]
export const GEMINI_TOKENS = 32768
export const MODEL_DEFAULT = MODELS[1].code
export const MODEL_DEFAULT = MODELS[0].code
export const MODEL_MAP: {[key: string]: typeof MODELS[number]} = {}
for (const model of MODELS) {
MODEL_MAP[model.code] = model

View File

@@ -1,13 +1,24 @@
import {useAppDispatch} from './redux'
import {useAppDispatch, useAppSelector} from './redux'
import React, {useCallback} from 'react'
import {setNeedScroll} from '../redux/envReducer'
import {setNeedScroll, setReviewAction, setTempData} from '../redux/envReducer'
const useSubtitle = () => {
const dispatch = useAppDispatch()
const reviewed = useAppSelector(state => state.env.tempData.reviewed)
const reviewAction = useAppSelector(state => state.env.reviewAction)
const reviewActions = useAppSelector(state => state.env.tempData.reviewActions)
const move = useCallback((time: number, togglePause: boolean) => {
window.parent.postMessage({type: 'move', time, togglePause}, '*')
}, [])
//review action
if (reviewed === undefined && !reviewAction) {
dispatch(setReviewAction(true))
dispatch(setTempData({
reviewActions: (reviewActions ?? 0) + 1
}))
}
}, [dispatch, reviewAction, reviewActions, reviewed])
const scrollIntoView = useCallback((ref: React.RefObject<HTMLDivElement>) => {
ref.current?.scrollIntoView({behavior: 'smooth', block: 'center'})

View File

@@ -13,6 +13,7 @@ import {
setTitle,
setTotalHeight,
setUrl,
setTempData,
} from '../redux/envReducer'
import {EventBusContext} from '../Router'
import {EVENT_EXPAND, GEMINI_TOKENS, TOTAL_HEIGHT_MAX, TOTAL_HEIGHT_MIN, WORDS_MIN, WORDS_RATE} from '../const'
@@ -39,6 +40,17 @@ const useSubtitleService = () => {
const transResults = useAppSelector(state => state.env.transResults)
const hideOnDisableAutoTranslate = useAppSelector(state => state.env.envData.hideOnDisableAutoTranslate)
const autoTranslate = useAppSelector(state => state.env.autoTranslate)
const reviewed = useAppSelector(state => state.env.tempData.reviewed)
const reviewActions = useAppSelector(state => state.env.tempData.reviewActions)
//如果reviewActions达到15次则设置reviewed为false
useEffect(() => {
if (reviewed === undefined && reviewActions && reviewActions >= 15) {
dispatch(setTempData({
reviewed: false
}))
}
}, [reviewActions, dispatch, reviewed])
// 监听消息
useEffect(() => {

View File

@@ -9,7 +9,9 @@ import {
setLastTransTime,
setSummaryContent,
setSummaryError,
setSummaryStatus
setSummaryStatus,
setReviewAction,
setTempData
} from '../redux/envReducer'
import {
LANGUAGE_DEFAULT,
@@ -38,6 +40,9 @@ const useTranslate = () => {
const language = LANGUAGES_MAP[envData.language??LANGUAGE_DEFAULT]
const summarizeLanguage = LANGUAGES_MAP[envData.summarizeLanguage??SUMMARIZE_LANGUAGE_DEFAULT]
const title = useAppSelector(state => state.env.title)
const reviewed = useAppSelector(state => state.env.tempData.reviewed)
const reviewAction = useAppSelector(state => state.env.reviewAction)
const reviewActions = useAppSelector(state => state.env.tempData.reviewActions)
/**
* 获取下一个需要翻译的行
@@ -137,6 +142,14 @@ const useTranslate = () => {
}, [data?.body, envData, language.name, title, dispatch])
const addSummarizeTask = useCallback(async (type: SummaryType, segment: Segment) => {
//review action
if (reviewed === undefined && !reviewAction) {
dispatch(setReviewAction(true))
dispatch(setTempData({
reviewActions: (reviewActions ?? 0) + 1
}))
}
if (segment.text.length >= SUMMARIZE_THRESHOLD) {
let subtitles = ''
for (const item of segment.items) {

View File

@@ -48,6 +48,9 @@ interface EnvState {
searchText: string
searchResult: Set<number>
//当前视频是否计算过操作
reviewAction: boolean
}
const initialState: EnvState = {
@@ -77,6 +80,8 @@ const initialState: EnvState = {
searchResult: new Set(),
asks: [],
reviewAction: false,
}
export const slice = createSlice({
@@ -98,6 +103,9 @@ export const slice = createSlice({
...action.payload,
}
},
setReviewAction: (state, action: PayloadAction<boolean>) => {
state.reviewAction = action.payload
},
setTempReady: (state) => {
state.tempReady = true
},
@@ -319,6 +327,7 @@ export const {
setAutoTranslate,
setAutoScroll,
setNoVideo,
setReviewAction,
setNeedScroll,
setCurIdx,
setEnvData,

2
src/typings.d.ts vendored
View File

@@ -40,6 +40,8 @@ interface TempData {
curSummaryType: SummaryType
downloadType?: string
compact?: boolean // 是否紧凑视图
reviewActions?: number // 点击或总结行为达到一定次数后显示评分一个视频最多只加1次
reviewed?: boolean // 是否点击过评分,undefined: 不显示true: 已点击false: 未点击(需要显示)
}
interface TaskDef {

View File

@@ -1,5 +1,10 @@
import {SyntheticEvent} from 'react'
export const isEdgeBrowser = () => {
const userAgent = navigator.userAgent.toLowerCase();
return userAgent.includes('edg/') && !userAgent.includes('edge/');
}
export const formatTime = (time: number) => {
if (!time) return '00:00'

View File

@@ -12,7 +12,7 @@ export default ({mode}) => {
visualizer() as PluginOption,
]
// @ts-ignore
if (mode === 'production_chrome') {
if (mode === 'production_chrome' || mode === 'production_edge') {
plugins.push(crx({
manifest,
}))