feat: add api keys

This commit is contained in:
scarqin
2024-12-25 20:23:47 +08:00
parent cdc9bb73bb
commit 02e5394924
3 changed files with 99 additions and 113 deletions
@@ -4,12 +4,14 @@ import { Select, Space, message } from 'antd'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
interface AIProvider {
export interface AIProvider {
id: string
name: string
logo: string
configured: boolean
getApikeyUrl: string
status: string
config: string
}
interface AIProviderResponse {
@@ -24,7 +26,7 @@ interface AIProviderResponse {
interface AIProviderSelectProps {
value?: string
onChange?: (value: string) => void
onChange?: (value: string, provider: AIProvider) => void
style?: React.CSSProperties
}
@@ -35,24 +37,33 @@ const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, st
const { fetchData } = useFetch()
useEffect(() => {
let isMounted = true
const fetchProviders = async () => {
setLoading(true)
if (isMounted) setLoading(true)
try {
const response = await fetchData<AIProviderResponse>('simple/ai/providers', { method: 'GET' })
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setProviders(data.providers)
isMounted && setProviders(data.providers)
if (!data.providers?.length) return
const selectedProvider: AIProvider = value ? providers.find((p) => p.id === value) : data.providers[0]
onChange?.(selectedProvider.id, selectedProvider)
} else {
message.error(msg || t('Failed to fetch AI providers'))
}
} catch (error) {
message.error(t('Failed to fetch AI providers'))
} finally {
setLoading(false)
isMounted && setLoading(false)
}
}
fetchProviders()
return () => {
isMounted = false
}
}, [])
return (
@@ -60,7 +71,10 @@ const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, st
<span>{t('AI 供应商')}:</span>
<Select
value={value}
onChange={onChange}
onChange={(selectedValue) => {
const selectedProvider = providers.find((p) => p.id === selectedValue)
onChange?.(selectedValue, selectedProvider as AIProvider)
}}
style={style}
loading={loading}
showSearch
@@ -1,4 +1,7 @@
import Icon from '@ant-design/icons'
import { Codebox } from '@common/components/postcat/api/Codebox'
import { $t } from '@common/locales'
import { AIProvider } from '@core/components/AIProviderSelect'
import { DatePicker, Form, Input, Modal, Switch } from 'antd'
import dayjs from 'dayjs'
import React, { useEffect, useState } from 'react'
@@ -8,50 +11,32 @@ interface ApiKeyModalProps {
visible: boolean
onCancel: () => void
onSave: (values: any) => void
providerName: string
provider?: AIProvider
mode: 'add' | 'edit'
initialValues?: Partial<APIKey>
defaultKeyNumber?: number
entity: APIKey | null
}
const { TextArea } = Input
const ApiKeyModal: React.FC<ApiKeyModalProps> = ({
visible,
onCancel,
onSave,
providerName,
mode,
initialValues,
defaultKeyNumber = 1
}) => {
const ApiKeyModal: React.FC<ApiKeyModalProps> = ({ visible, onCancel, onSave, provider, mode, entity }) => {
const [form] = Form.useForm()
const [neverExpire, setNeverExpire] = useState(true)
useEffect(() => {
if (visible) {
if (mode === 'add') {
form.setFieldsValue({
id: `KEY${defaultKeyNumber}`,
neverExpire: true,
expire_time: dayjs().add(7, 'days'),
name: {
openai_api_base: 'API Base',
openai_api_key: 'API Key'
}
})
} else if (initialValues) {
form.setFieldsValue({
id: initialValues.id,
name: initialValues.name,
expire_time: initialValues.expire_time ? dayjs(initialValues.expire_time) : undefined,
enabled: initialValues.enabled,
neverExpire: !initialValues.expire_time
})
setNeverExpire(!initialValues.expire_time)
}
try {
form.setFieldsValue({
name: entity?.name,
expire_time: entity?.expire_time ? dayjs(entity.expire_time) : undefined,
config: entity?.config ? JSON.stringify(JSON.parse(entity?.config), null, 2) : JSON.parse(provider?.config)
})
setNeverExpire(!entity?.expire_time)
} catch (e) {
console.error('Error setting form values:', e)
form.setFieldsValue({
name: entity?.name,
expire_time: undefined,
config: ''
})
}
}, [visible, mode, initialValues, defaultKeyNumber, form])
}, [])
const handleOk = async () => {
try {
@@ -74,57 +59,65 @@ const ApiKeyModal: React.FC<ApiKeyModalProps> = ({
}
}
const getProviderKeyUrl = (provider: string): string => {
const urls: Record<string, string> = {
openai: 'https://platform.openai.com/api-keys',
anthropic: 'https://console.anthropic.com/account/keys',
google: 'https://console.cloud.google.com/apis/credentials',
azure: 'https://portal.azure.com/#blade/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/OpenAI',
stability: 'https://platform.stability.ai/account/keys'
}
return urls[provider.toLowerCase()] || '#'
}
console.log(provider)
return (
<Modal
title={mode === 'add' ? $t(`添加 ${providerName} APIKey`) : $t('编辑 APIKey')}
title={mode === 'add' ? $t(`添加 ${provider?.name} APIKey`) : $t('编辑 APIKey')}
open={visible}
onCancel={onCancel}
onOk={handleOk}
destroyOnClose
width={600}
maskClosable={false}
footer={(_, { OkBtn, CancelBtn }) => (
<div className="flex justify-between items-center">
<a
target="_blank"
rel="noopener noreferrer"
href={provider?.getApikeyUrl}
className="flex items-center gap-[8px]"
>
<span>{$t('从 (0) 获取 API KEY', [provider?.name])}</span>
<Icon icon="ic:baseline-open-in-new" width={16} height={16} />
</a>
<div>
<CancelBtn />
<OkBtn />
</div>
</div>
)}
>
<Form form={form} layout="vertical">
<Form.Item name="id" label={$t('APIKey 名称')} rules={[{ required: true, message: $t('请输入 APIKey') }]}>
<Input disabled={mode === 'edit'} />
</Form.Item>
<Form.Item
name="name"
label={
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<span>{$t('API Key')}</span>
{mode === 'add' && (
<a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer">
{$t('从 OpenAI 获取 API Key')}
</a>
)}
</div>
}
rules={[{ required: true, message: $t('请输入 API Key') }]}
>
{mode === 'add' ? (
<TextArea
rows={4}
placeholder={JSON.stringify(
{
openai_api_base: 'API Base',
openai_api_key: 'API Key'
},
null,
2
)}
/>
) : (
<Input.Password />
)}
<Form.Item label={$t('API Key')} name="config">
<Codebox
editorTheme="vs-dark"
readOnly={false}
width="100%"
height="300px"
language="json"
enableToolbar={false}
/>
</Form.Item>
<Form.Item name="neverExpire" valuePropName="checked">
<Switch
checkedChildren={$t('永不过期')}
unCheckedChildren={$t('设置过期时间')}
onChange={handleNeverExpireChange}
/>
<Form.Item label={$t('过期时间')} name="neverExpire" valuePropName="checked">
<div className="flex items-center">
<Switch onChange={handleNeverExpireChange} />
<span className="ml-2">{neverExpire ? $t('永不过期') : $t('设置过期时间')}</span>
</div>
</Form.Item>
{!neverExpire && (
@@ -5,7 +5,7 @@ import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPe
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import AIProviderSelect from '@core/components/AIProviderSelect'
import AIProviderSelect, { AIProvider } from '@core/components/AIProviderSelect'
import { Divider, message, Space, Typography } from 'antd'
import React, { useEffect, useRef, useState } from 'react'
import ApiKeyModal from './components/ApiKeyModal'
@@ -13,8 +13,8 @@ import { APIKey } from './types'
const KeySettings: React.FC = () => {
const pageListRef = useRef<ActionType>(null)
const [selectedProvider, setSelectedProvider] = useState<string>('openai')
const [providerName, setProviderName] = useState<string>('')
const [selectedProvider, setSelectedProvider] = useState<string>()
const [provider, setProvider] = useState<AIProvider | undefined>()
const [modalVisible, setModalVisible] = useState(false)
const [modalMode, setModalMode] = useState<'add' | 'edit'>('add')
const [editingKey, setEditingKey] = useState<APIKey | null>(null)
@@ -24,28 +24,8 @@ const KeySettings: React.FC = () => {
useEffect(() => {
pageListRef.current?.reload()
fetchProviderName()
}, [selectedProvider])
const fetchProviderName = async () => {
try {
const response = await fetchData<{ code: number; data: { providers: { id: string; name: string }[] } }>(
'simple/ai/providers',
{ method: 'GET' }
)
if (response.code === STATUS_CODE.SUCCESS) {
const provider = response.data.providers.find((p) => p.id === selectedProvider)
if (provider) {
setProviderName(provider.name)
}
}
} catch (error) {
console.error('Failed to fetch provider name:', error)
}
}
useEffect(() => {}, [])
const handleEdit = (record: APIKey) => {
setEditingKey(record)
setModalMode('edit')
@@ -172,6 +152,7 @@ const KeySettings: React.FC = () => {
}
const requestApiKeys = async (params: any) => {
if (!selectedProvider) return
try {
const response = await fetchData<BasicResponse<{ data: APIKey[] }>>('ai/resource/keys', {
method: 'GET',
@@ -310,7 +291,13 @@ const KeySettings: React.FC = () => {
<>
{$t('支持单个 API 模型供应商下创建多个 APIKey APIKey 进行智能负载均衡')}
<div className="mt-4">
<AIProviderSelect value={selectedProvider} onChange={setSelectedProvider} />
<AIProviderSelect
value={selectedProvider}
onChange={(value: string, provider: AIProvider) => {
setSelectedProvider(value)
setProvider(provider)
}}
/>
</div>
</>
}
@@ -338,16 +325,8 @@ const KeySettings: React.FC = () => {
mode={modalMode}
onCancel={handleModalCancel}
onSave={handleSave}
providerName={providerName}
initialValues={
editingKey
? {
id: editingKey.id,
name: editingKey.name,
expire_time: editingKey.expire_time
}
: undefined
}
provider={provider}
entity={editingKey}
defaultKeyNumber={apiKeys.length + 1}
/>
</div>