From b42da9f1540cd21a231a4f8c94a2671f32bac63f Mon Sep 17 00:00:00 2001 From: FunJim Date: Sun, 9 Mar 2025 14:02:21 +0800 Subject: [PATCH] feat: Enhance API Key Management in Provider Settings - Add single key checking functionality - Implement ability to remove individual API keys - Improve UI with remove and check buttons for each key - Disable actions during checking to prevent conflicts - Add styled remove icon for key deletion --- .../ProviderSettings/ApiCheckPopup.tsx | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx index 033e32a5..3224efcd 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx @@ -1,4 +1,4 @@ -import { CheckCircleFilled, CloseCircleFilled, LoadingOutlined } from '@ant-design/icons' +import { CheckCircleFilled, CloseCircleFilled, LoadingOutlined, MinusCircleOutlined } from '@ant-design/icons' import Scrollbar from '@renderer/components/Scrollbar' import { TopView } from '@renderer/components/TopView' import { checkApi } from '@renderer/services/ApiService' @@ -8,6 +8,7 @@ import { maskApiKey } from '@renderer/utils/api' import { Button, List, Modal, Space, Spin, Typography } from 'antd' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import styled from 'styled-components' interface ShowParams { title: string @@ -34,6 +35,7 @@ const PopupContainer: React.FC = ({ title, provider, model, apiKeys, reso }) const { t } = useTranslation() const [isChecking, setIsChecking] = useState(false) + const [isCheckingSingle, setIsCheckingSingle] = useState(false) const checkAllKeys = async () => { setIsChecking(true) @@ -54,10 +56,37 @@ const PopupContainer: React.FC = ({ title, provider, model, apiKeys, reso } } + const checkSingleKey = async (keyIndex: number) => { + if (isChecking || keyStatuses[keyIndex].checking) { + return + } + + setIsCheckingSingle(true) + setKeyStatuses((prev) => prev.map((status, idx) => (idx === keyIndex ? { ...status, checking: true } : status))) + + try { + const { valid } = await checkApi({ ...provider, apiKey: keyStatuses[keyIndex].key }, model) + + setKeyStatuses((prev) => + prev.map((status, idx) => (idx === keyIndex ? { ...status, checking: false, isValid: valid } : status)) + ) + } catch (error) { + setKeyStatuses((prev) => + prev.map((status, idx) => (idx === keyIndex ? { ...status, checking: false, isValid: false } : status)) + ) + } finally { + setIsCheckingSingle(false) + } + } + const removeInvalidKeys = () => { setKeyStatuses((prev) => prev.filter((status) => status.isValid !== false)) } + const removeKey = (keyIndex: number) => { + setKeyStatuses((prev) => prev.filter((_, idx) => idx !== keyIndex)) + } + const onOk = () => { const allKeys = keyStatuses.map((status) => status.key) resolve({ validKeys: allKeys }) @@ -84,12 +113,12 @@ const PopupContainer: React.FC = ({ title, provider, model, apiKeys, reso footer={ - - + !isChecking && !isCheckingSingle && removeKey(index)} + style={{ + cursor: isChecking || isCheckingSingle ? 'not-allowed' : 'pointer', + opacity: isChecking || isCheckingSingle ? 0.5 : 1 + }} + /> @@ -146,3 +185,13 @@ export default class ApiCheckPopup { }) } } + +const RemoveIcon = styled(MinusCircleOutlined)` + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + color: var(--color-error); + cursor: pointer; + transition: all 0.2s ease-in-out; +`