feat: add modal

This commit is contained in:
scarqin
2024-12-26 14:20:06 +08:00
parent 8128a7acac
commit a368af0598
5 changed files with 175 additions and 198 deletions
@@ -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
}