mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
feat: add modal
This commit is contained in:
@@ -11,7 +11,7 @@ export interface AIProvider {
|
||||
configured: boolean
|
||||
getApikeyUrl: string
|
||||
status: string
|
||||
config: string
|
||||
default_config: string
|
||||
}
|
||||
|
||||
interface AIProviderResponse {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Codebox } from '@common/components/postcat/api/Codebox'
|
||||
import { $t } from '@common/locales'
|
||||
import { AIProvider } from '@core/components/AIProviderSelect'
|
||||
import { DatePicker, Form, Input, Switch } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { EditAPIKey } from '../types'
|
||||
|
||||
interface ApiKeyContentProps {
|
||||
provider?: AIProvider
|
||||
entity: EditAPIKey
|
||||
}
|
||||
|
||||
const ApiKeyContent: React.FC<ApiKeyContentProps> = ({ provider, entity }) => {
|
||||
const [form] = Form.useForm()
|
||||
const [neverExpire, setNeverExpire] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
form.setFieldsValue({
|
||||
name: entity.name,
|
||||
expire_time: entity.expire_time === '0' ? 0 : dayjs(entity.expire_time),
|
||||
config: entity.config
|
||||
})
|
||||
} catch (e) {
|
||||
form.setFieldsValue({
|
||||
name: entity.name,
|
||||
expire_time: undefined,
|
||||
config: ''
|
||||
})
|
||||
}
|
||||
// setNeverExpire(entity.expire_time === '0')
|
||||
}, [])
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
// onSave({
|
||||
// ...values,
|
||||
// expire_time: neverExpire ? null : values.expire_time.format('YYYY-MM-DD HH:mm:ss')
|
||||
// })
|
||||
} catch (error) {
|
||||
console.error('Validation failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNeverExpireChange = (checked: boolean) => {
|
||||
console.log(checked)
|
||||
setNeverExpire(checked)
|
||||
if (!checked) {
|
||||
form.setFieldsValue({
|
||||
expire_time: dayjs().add(7, 'days')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="name" label={$t('名称')} rules={[{ required: true, message: $t('请输入 APIKey') }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('API Key')} name="config" rules={[{ required: true, message: $t('请填写 APIKey') }]}>
|
||||
<Codebox
|
||||
editorTheme="vs-dark"
|
||||
readOnly={false}
|
||||
width="100%"
|
||||
height="300px"
|
||||
language="json"
|
||||
enableToolbar={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<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:{neverExpire}
|
||||
{!neverExpire && (
|
||||
<Form.Item
|
||||
name="expire_time"
|
||||
label={$t('过期时间')}
|
||||
rules={[{ required: true, message: $t('请选择过期时间') }]}
|
||||
>
|
||||
<DatePicker style={{ width: '100%' }} showTime />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export default ApiKeyContent
|
||||
@@ -1,143 +0,0 @@
|
||||
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'
|
||||
import { APIKey } from '../types'
|
||||
|
||||
interface ApiKeyModalProps {
|
||||
visible: boolean
|
||||
onCancel: () => void
|
||||
onSave: (values: any) => void
|
||||
provider?: AIProvider
|
||||
mode: 'add' | 'edit'
|
||||
entity: APIKey | null
|
||||
}
|
||||
|
||||
const ApiKeyModal: React.FC<ApiKeyModalProps> = ({ visible, onCancel, onSave, provider, mode, entity }) => {
|
||||
const [form] = Form.useForm()
|
||||
const [neverExpire, setNeverExpire] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
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: ''
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
onSave({
|
||||
...values,
|
||||
expire_time: neverExpire ? null : values.expire_time.format('YYYY-MM-DD HH:mm:ss')
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Validation failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNeverExpireChange = (checked: boolean) => {
|
||||
setNeverExpire(checked)
|
||||
if (!checked) {
|
||||
form.setFieldsValue({
|
||||
expire_time: dayjs().add(7, 'days')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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(`添加 ${provider?.name} APIKey`) : $t('编辑 APIKey')}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={handleOk}
|
||||
destroyOnClose
|
||||
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 label={$t('API Key')} name="config">
|
||||
<Codebox
|
||||
editorTheme="vs-dark"
|
||||
readOnly={false}
|
||||
width="100%"
|
||||
height="300px"
|
||||
language="json"
|
||||
enableToolbar={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<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 && (
|
||||
<Form.Item
|
||||
name="expire_time"
|
||||
label={$t('过期时间')}
|
||||
rules={[{ required: true, message: $t('请选择过期时间') }]}
|
||||
>
|
||||
<DatePicker style={{ width: '100%' }} showTime />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{mode === 'edit' && (
|
||||
<Form.Item name="enabled" label={$t('启用状态')} valuePropName="checked">
|
||||
<Switch checkedChildren={$t('是')} unCheckedChildren={$t('否')} />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ApiKeyModal
|
||||
@@ -1,3 +1,4 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import { ActionType } from '@ant-design/pro-components'
|
||||
import InsidePage from '@common/components/aoplatform/InsidePage'
|
||||
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList'
|
||||
@@ -6,71 +7,92 @@ import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import AIProviderSelect, { AIProvider } from '@core/components/AIProviderSelect'
|
||||
import { Divider, message, Space, Typography } from 'antd'
|
||||
import { App, Divider, Space, Typography } from 'antd'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import ApiKeyModal from './components/ApiKeyModal'
|
||||
import { APIKey } from './types'
|
||||
import ApiKeyContent from './components/ApiKeyContent'
|
||||
import { APIKey, EditAPIKey } from './types'
|
||||
|
||||
const KeySettings: React.FC = () => {
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const { modal, message } = App.useApp()
|
||||
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)
|
||||
const [apiKeys, setApiKeys] = useState<APIKey[]>([])
|
||||
const { fetchData } = useFetch()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
|
||||
const [total, setTotal] = useState<number>(0)
|
||||
const modalRef = useRef<any>()
|
||||
useEffect(() => {
|
||||
pageListRef.current?.reload()
|
||||
}, [selectedProvider])
|
||||
|
||||
const handleEdit = (record: APIKey) => {
|
||||
setEditingKey(record)
|
||||
setModalMode('edit')
|
||||
setModalVisible(true)
|
||||
openModal(record)
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
setModalMode('add')
|
||||
setModalVisible(true)
|
||||
openModal()
|
||||
}
|
||||
|
||||
const handleModalCancel = () => {
|
||||
setModalVisible(false)
|
||||
setEditingKey(null)
|
||||
}
|
||||
|
||||
const handleSave = (values: any) => {
|
||||
if (modalMode === 'edit' && editingKey) {
|
||||
const newKeys: APIKey[] = apiKeys.map((key) =>
|
||||
key.id === editingKey.id
|
||||
? {
|
||||
...key,
|
||||
key: values.name,
|
||||
expire_time: values.expire_time,
|
||||
status: values.enabled ? 'normal' : 'disabled'
|
||||
}
|
||||
: key
|
||||
)
|
||||
setApiKeys(newKeys)
|
||||
} else {
|
||||
// Handle add case
|
||||
const newKey: APIKey = {
|
||||
id: String(Date.now()),
|
||||
name: values.name,
|
||||
status: 'normal',
|
||||
expire_time: values.expire_time,
|
||||
priority: apiKeys.length + 1,
|
||||
can_delete: true,
|
||||
use_token: 0,
|
||||
update_time: ''
|
||||
const openModal = async (entity?: EditAPIKey) => {
|
||||
if (!provider) return
|
||||
const mode = entity ? 'edit' : 'add'
|
||||
if (mode === 'edit') {
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
const { code, data, msg } = await fetchData<BasicResponse<{ info: EditAPIKey }>>('ai/resource/key', {
|
||||
method: 'GET',
|
||||
eoParams: { provider: selectedProvider, id: entity!.id }
|
||||
})
|
||||
message.destroy()
|
||||
if (code !== STATUS_CODE.SUCCESS) {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return
|
||||
}
|
||||
setApiKeys([...apiKeys, newKey])
|
||||
entity = data?.info
|
||||
} else {
|
||||
provider.default_config = '{"apikey":"******"}'
|
||||
entity = {
|
||||
name: `key${total}`,
|
||||
config: provider.default_config,
|
||||
expire_time: '0'
|
||||
} as EditAPIKey
|
||||
}
|
||||
setModalVisible(false)
|
||||
setEditingKey(null)
|
||||
const newEntity = entity as EditAPIKey
|
||||
|
||||
modal.confirm({
|
||||
title: mode === 'add' ? $t(`添加 ${provider?.name} APIKey`) : $t('编辑 APIKey'),
|
||||
content: <ApiKeyContent ref={modalRef} entity={newEntity} provider={provider} />,
|
||||
onOk: () => {
|
||||
return modalRef.current?.save().then((res) => {
|
||||
// if (res === true) setAiConfigFlushed(true)
|
||||
// getAiSettingList()
|
||||
})
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
footer: (_, { OkBtn, CancelBtn }) => {
|
||||
return (
|
||||
<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 />
|
||||
{/* {checkAccess('system.devops.ai_provider.edit', accessData) ? <OkBtn /> : null} */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
@@ -166,6 +188,7 @@ const KeySettings: React.FC = () => {
|
||||
})
|
||||
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
setTotal(response.data.total)
|
||||
return {
|
||||
data: response.data.keys,
|
||||
success: true,
|
||||
@@ -320,15 +343,6 @@ const KeySettings: React.FC = () => {
|
||||
addNewBtnTitle={$t('添加 APIKey')}
|
||||
onAddNewBtnClick={handleAdd}
|
||||
/>
|
||||
<ApiKeyModal
|
||||
visible={modalVisible}
|
||||
mode={modalMode}
|
||||
onCancel={handleModalCancel}
|
||||
onSave={handleSave}
|
||||
provider={provider}
|
||||
entity={editingKey}
|
||||
defaultKeyNumber={apiKeys.length + 1}
|
||||
/>
|
||||
</div>
|
||||
</InsidePage>
|
||||
)
|
||||
|
||||
@@ -9,4 +9,18 @@ export interface APIKey extends Record<string, unknown> {
|
||||
can_delete: boolean
|
||||
priority: number
|
||||
}
|
||||
|
||||
|
||||
export interface APIKey extends EditAPIKey {
|
||||
status: 'normal' | 'exceeded' | 'expired' | 'disabled' | 'error'
|
||||
use_token: number
|
||||
update_time: string
|
||||
can_delete: boolean
|
||||
priority: number
|
||||
}
|
||||
|
||||
export interface EditAPIKey {
|
||||
id?: string
|
||||
name: string
|
||||
config: string
|
||||
expire_time: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user