diff --git a/src/components/ConfigExporter.jsx b/src/components/ConfigExporter.jsx index fbcdd4d..6a588a2 100644 --- a/src/components/ConfigExporter.jsx +++ b/src/components/ConfigExporter.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import toast from 'react-hot-toast'; import { getTokenSupportedModels } from '../api'; @@ -25,6 +25,145 @@ const CCSWITCH_RELEASE_URL = 'https://github.com/farion1231/cc-switch/releases/latest'; const CCSWITCH_REPO_URL = 'https://github.com/farion1231/cc-switch'; +function ThemedSelect({ + value, + onChange, + options, + placeholder, + disabled = false, + renderValue, + renderOption, + emptyLabel, +}) { + const [open, setOpen] = useState(false); + const rootRef = useRef(null); + + useEffect(() => { + if (disabled) { + setOpen(false); + } + }, [disabled]); + + useEffect(() => { + if (!open) return undefined; + + const handlePointerDown = (event) => { + if (!rootRef.current?.contains(event.target)) { + setOpen(false); + } + }; + + const handleKeyDown = (event) => { + if (event.key === 'Escape') { + setOpen(false); + } + }; + + document.addEventListener('mousedown', handlePointerDown); + document.addEventListener('touchstart', handlePointerDown); + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('mousedown', handlePointerDown); + document.removeEventListener('touchstart', handlePointerDown); + document.removeEventListener('keydown', handleKeyDown); + }; + }, [open]); + + const selectedOption = options.find((option) => option.value === value); + const displayLabel = selectedOption + ? (renderValue ? renderValue(selectedOption) : selectedOption.label) + : placeholder; + + return ( +
+ + + {open && ( +
+
+ {options.length > 0 ? ( + options.map((option) => { + const selected = option.value === value; + return ( + + ); + }) + ) : ( +
+ {emptyLabel} +
+ )} +
+
+ )} +
+ ); +} + const ConfigExporter = ({ tokens = [] }) => { const { t } = useTranslation(); const { site } = useSite(); @@ -50,6 +189,23 @@ const ConfigExporter = ({ tokens = [] }) => { () => TOOLS.find((tool) => tool.id === selectedTool) || TOOLS[0], [selectedTool], ); + const tokenOptions = useMemo( + () => + tokens.map((token) => ({ + value: token.id, + label: `${token.name} (sk-${token.key.substring(0, 16)}...)`, + token, + })), + [tokens], + ); + const modelOptions = useMemo( + () => + availableModels.map((model) => ({ + value: model, + label: model, + })), + [availableModels], + ); useEffect(() => { if (tokens.length === 0) { @@ -449,17 +605,13 @@ print(message.content[0].text)`; {t('config.selectKey')} - +
@@ -479,28 +631,22 @@ print(message.content[0].text)`; {t('config.selectModel')} - + onChange={setSelectedModel} + options={modelOptions} + placeholder={ + loadingModels + ? t('config.loadingModels') + : t('config.selectModel') + } + disabled={loadingModels || modelOptions.length === 0} + emptyLabel={ + modelsError + ? t('tokens.loadSupportedModelsFailed') + : t('tokens.noSupportedModels') + } + />