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 (
)
}
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 (
}>
总结配置
{!apiKeySetted &&
}
}>
当前选择的模型的分段字数上限是{aiTypeValue === 'gemini'?GEMINI_TOKENS:(MODEL_MAP[modelValue??MODEL_DEFAULT]?.tokens??'未知')}
(太接近上限总结会报错)
搜索配置
}>
提问配置
}>
{!promptsFold && PROMPT_TYPES.map((item, idx) =>
{item.name}
{
setPromptsValue({
...promptsValue,
// @ts-expect-error
[item.type]: PROMPT_DEFAULTS[item.type] ?? ''
})
}}>点击填充默认
} htmlFor={`prompt-${item.type}`}>
)}
)
}
export default OptionsPage