import React, {PropsWithChildren, useCallback, useMemo, useState} from 'react' import {setEnvData} from '../redux/envReducer' import {useAppDispatch, useAppSelector} from '../hooks/redux' import { ASK_ENABLED_DEFAULT, CUSTOM_MODEL_TOKENS, DEFAULT_SERVER_URL_OPENAI, GEMINI_TOKENS, LANGUAGE_DEFAULT, LANGUAGES, MODEL_DEFAULT, MODEL_MAP, MODEL_TIP, MODELS, PROMPT_DEFAULTS, PROMPT_TYPES, SUMMARIZE_LANGUAGE_DEFAULT, TRANSLATE_FETCH_DEFAULT, TRANSLATE_FETCH_MAX, TRANSLATE_FETCH_MIN, TRANSLATE_FETCH_STEP, WORDS_RATE, } from '../consts/const' import {IoWarning} from 'react-icons/all' import classNames from 'classnames' import toast from 'react-hot-toast' import {useBoolean, useEventTarget} from 'ahooks' import {useEventChecked} from '@kky002/kky-hooks' import { useMessage } from '@/hooks/message' import { FaChevronDown, FaChevronUp } from 'react-icons/fa' const OptionCard = ({ title, children, defaultExpanded = true }: { title: React.ReactNode; children: React.ReactNode; defaultExpanded?: boolean }) => { const [isExpanded, setIsExpanded] = useState(defaultExpanded); return (

setIsExpanded(!isExpanded)}> {title} {isExpanded ? : }

{isExpanded &&
{children}
}
); }; const FormItem = (props: { title: ShowElement tip?: string htmlFor?: string } & PropsWithChildren) => { const {title, tip, htmlFor, children} = props return (
{children}
) } const OptionsPage = () => { const dispatch = useAppDispatch() const envData = useAppSelector(state => state.env.envData) const {sendExtension} = useMessage() const {value: sidePanelValue, onChange: setSidePanelValue} = useEventChecked(envData.sidePanel) const {value: autoInsertValue, onChange: setAutoInsertValue} = useEventChecked(!envData.manualInsert) const {value: autoExpandValue, onChange: setAutoExpandValue} = useEventChecked(envData.autoExpand) // 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: askEnabledValue, onChange: setAskEnabledValue} = useEventChecked(envData.askEnabled??ASK_ENABLED_DEFAULT) 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) const [geminiApiKeyValue, { onChange: onChangeGeminiApiKeyValue }] = useEventTarget({initialValue: envData.geminiApiKey??''}) const [languageValue, { onChange: onChangeLanguageValue }] = useEventTarget({initialValue: envData.language??LANGUAGE_DEFAULT}) const [modelValue, { onChange: onChangeModelValue }] = useEventTarget({initialValue: envData.model??MODEL_DEFAULT}) const [customModelValue, { onChange: onChangeCustomModelValue }] = useEventTarget({initialValue: envData.customModel}) const [customModelTokensValue, setCustomModelTokensValue] = useState(envData.customModelTokens) const [summarizeLanguageValue, { onChange: onChangeSummarizeLanguageValue }] = useEventTarget({initialValue: envData.summarizeLanguage??SUMMARIZE_LANGUAGE_DEFAULT}) const [hideOnDisableAutoTranslateValue, setHideOnDisableAutoTranslateValue] = useState(envData.hideOnDisableAutoTranslate) const [themeValue, setThemeValue] = useState(envData.theme) const [fontSizeValue, setFontSizeValue] = useState(envData.fontSize) const [aiTypeValue, setAiTypeValue] = useState(envData.aiType) const [transDisplayValue, setTransDisplayValue] = useState(envData.transDisplay) const [wordsValue, setWordsValue] = useState(envData.words) const [fetchAmountValue, setFetchAmountValue] = useState(envData.fetchAmount??TRANSLATE_FETCH_DEFAULT) const [promptsFold, {toggle: togglePromptsFold}] = useBoolean(true) const [promptsValue, setPromptsValue] = useState<{[key: string]: string}>(envData.prompts??{}) // const wordsList = useMemo(() => { // const list = [] // for (let i = WORDS_MIN; i <= WORDS_MAX; i += WORDS_STEP) { // list.push(i) // } // return list // }, []) const transFetchAmountList = useMemo(() => { const list = [] for (let i = TRANSLATE_FETCH_MIN; i <= TRANSLATE_FETCH_MAX; i += TRANSLATE_FETCH_STEP) { list.push(i) } return list }, []) const apiKeySetted = useMemo(() => { if (aiTypeValue === 'gemini') { return !!geminiApiKeyValue } return !!apiKeyValue }, [aiTypeValue, apiKeyValue, geminiApiKeyValue]) const onChangeHideOnDisableAutoTranslate = useCallback((e: any) => { setHideOnDisableAutoTranslateValue(e.target.checked) }, []) const onSave = useCallback(() => { dispatch(setEnvData({ sidePanel: sidePanelValue, manualInsert: !autoInsertValue, autoExpand: autoExpandValue, aiType: aiTypeValue, apiKey: apiKeyValue, serverUrl: serverUrlValue, model: modelValue, customModel: customModelValue, customModelTokens: customModelTokensValue, geminiApiKey: geminiApiKeyValue, translateEnable: translateEnableValue, language: languageValue, hideOnDisableAutoTranslate: hideOnDisableAutoTranslateValue, theme: themeValue, transDisplay: transDisplayValue, summarizeEnable: summarizeEnableValue, summarizeFloat: summarizeFloatValue, summarizeLanguage: summarizeLanguageValue, words: wordsValue, fetchAmount: fetchAmountValue, fontSize: fontSizeValue, prompts: promptsValue, searchEnabled: searchEnabledValue, cnSearchEnabled: cnSearchEnabledValue, askEnabled: askEnabledValue, })) toast.success('保存成功') sendExtension('CLOSE_SIDE_PANEL') // 3秒后关闭 setTimeout(() => { window.close() }, 3000) }, [dispatch, sendExtension, sidePanelValue, autoInsertValue, autoExpandValue, aiTypeValue, apiKeyValue, serverUrlValue, modelValue, customModelValue, customModelTokensValue, geminiApiKeyValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue, askEnabledValue]) const onCancel = useCallback(() => { window.close() }, []) const onFetchAmountChange = useCallback((e: any) => { setFetchAmountValue(parseInt(e.target.value)) }, []) const onWordsChange = useCallback((e: any) => { setWordsValue(parseInt(e.target.value)) }, []) const onSel1 = useCallback(() => { setTransDisplayValue('originPrimary') }, []) const onSel2 = useCallback(() => { setTransDisplayValue('targetPrimary') }, []) const onSel3 = useCallback(() => { setTransDisplayValue('target') }, []) const onSelTheme1 = useCallback(() => { setThemeValue('system') }, []) const onSelTheme2 = useCallback(() => { setThemeValue('light') }, []) const onSelTheme3 = useCallback(() => { setThemeValue('dark') }, []) const onSelFontSize1 = useCallback(() => { setFontSizeValue('normal') }, []) const onSelFontSize2 = useCallback(() => { setFontSizeValue('large') }, []) const onSelOpenai = useCallback(() => { setAiTypeValue('openai') }, []) const onSelGemini = useCallback(() => { setAiTypeValue('gemini') }, []) return (
{!sidePanelValue && } {!sidePanelValue && }
{(!aiTypeValue || aiTypeValue === 'openai') && } {(!aiTypeValue || aiTypeValue === 'openai') && setServerUrlValue(e.target.value)}/> } {(!aiTypeValue || aiTypeValue === 'openai') &&
【官方地址】
官方网址:点击访问
{/*
【第三方代理】
*/} {/*
代理网址:点击访问
*/} {/* */} {/*
目前价格不到官方价格的6折
*/}
} {(!aiTypeValue || aiTypeValue === 'openai') && } {(!aiTypeValue || aiTypeValue === 'openai') &&
{MODEL_TIP}
} {modelValue === 'custom' && } {modelValue === 'custom' && setCustomModelTokensValue(e.target.value ? parseInt(e.target.value) : undefined)}/> } {aiTypeValue === 'gemini' && } {aiTypeValue === 'gemini' &&
官方网址:Google AI Studio (目前免费)
谷歌模型安全要求比较高,有些视频可能无法生成总结!
}
翻译配置 {!apiKeySetted &&
}
}>
{transFetchAmountList.map(amount => {amount})}
总结配置 {!apiKeySetted &&
} }>
setWordsValue(e.target.value?parseInt(e.target.value):undefined)}/> {/* */} {/*
*/} {/* {wordsList.map(words => {words})} */} {/*
*/}
当前选择的模型的分段字数上限是{aiTypeValue === 'gemini'?GEMINI_TOKENS:(MODEL_MAP[modelValue??MODEL_DEFAULT]?.tokens??'未知')} (太接近上限总结会报错)
搜索配置 }> 提问配置 }>
点击{promptsFold ? '展开' : '折叠'}
{!promptsFold && PROMPT_TYPES.map((item, idx) =>
{item.name}
{ setPromptsValue({ ...promptsValue, // @ts-expect-error [item.type]: PROMPT_DEFAULTS[item.type] ?? '' }) }}>点击填充默认
} htmlFor={`prompt-${item.type}`}>