feat: delete model

This commit is contained in:
scarqin
2025-02-12 00:33:16 +08:00
parent eeb2fbcad6
commit 9cb09905f9
4 changed files with 120 additions and 116 deletions
@@ -1,14 +1,13 @@
import { QuestionCircleOutlined } from '@ant-design/icons'
import { Codebox } from '@common/components/postcat/api/Codebox'
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import { App, Form, InputNumber, Select, Switch, Tag, Tooltip } from 'antd'
import { App, Form, Select, Switch, Tag } from 'antd'
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import { AiProviderLlmsItems, ModelDetailData } from './types'
export type AiSettingModalContentProps = {
entity: ModelDetailData & { defaultLlm: string }
entity?: { id: string | undefined; defaultLlm: string | undefined }
readOnly: boolean
}
@@ -23,12 +22,36 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
const { fetchData } = useFetch()
const [llmList, setLlmList] = useState<AiProviderLlmsItems[]>()
const [loading, setLoading] = useState<boolean>(false)
const [enableState, setEnableState] = useState<boolean>(entity.status === 'enabled')
const [enableState, setEnableState] = useState<boolean>(entity?.status === 'enabled' ?? true)
const [providers, setProviders] = useState<Array<{ id: string; name: string }>>([])
const [selectedProvider, setSelectedProvider] = useState<string>(entity?.id || '')
const getUnconfiguredProviders = () => {
if (entity) return // Skip if editing existing provider
fetchData<BasicResponse<{ providers: Array<{ id: string; name: string }> }>>('ai/providers/unconfigured', {
method: 'GET'
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setProviders(data.providers)
if (data.providers.length > 0) {
setSelectedProvider(data.providers[0].id)
form.setFieldValue('provider', data.providers[0].id)
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const getLlmList = () => {
if (!selectedProvider) return
setLoading(true)
fetchData<BasicResponse<{ llms: AiProviderLlmsItems[] }>>(`ai/provider/llms`, {
method: 'GET',
eoParams: { provider: entity.id }
eoParams: { provider: selectedProvider }
})
.then((response) => {
const { code, data, msg } = response
@@ -42,56 +65,79 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
setLoading(false)
})
}
const initData = async () => {
if (entity?.id) {
message.loading($t(RESPONSE_TIPS.loading))
const { code, data, msg } = await fetchData<BasicResponse<{ provider: ModelDetailData }>>('ai/provider/config', {
method: 'GET',
eoParams: { provider: entity.id },
eoTransformKeys: ['get_apikey_url', 'default_llm']
})
message.destroy()
if (code !== STATUS_CODE.SUCCESS) {
message.error(msg || $t(RESPONSE_TIPS.error))
return
}
const provider = data.provider
form.setFieldsValue({
defaultLlm: provider.defaultLlm,
config: provider.config ? JSON.stringify(JSON.parse(provider.config), null, 2) : '',
enable: provider.status === 'enabled'
})
return
}
form.setFieldsValue({
defaultLlm: entity?.defaultLlm,
config: '',
enable: true
})
}
useEffect(() => {
getUnconfiguredProviders()
}, [])
useEffect(() => {
getLlmList()
try {
form.setFieldsValue({
defaultLlm: entity.defaultLlm,
config: entity!.config ? JSON.stringify(JSON.parse(entity!.config), null, 2) : '',
priority: entity.priority || 1,
enable: entity.status === 'enabled'
})
} catch (e) {
form.setFieldsValue({
defaultLlm: entity.defaultLlm,
config: '',
priority: 1,
enable: true
})
}
}, [])
}, [selectedProvider])
useEffect(() => {
initData()
}, [entity])
const save: () => Promise<boolean | string> = () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then((value) => {
const finalValue = {
...value,
priority: Math.max(1, value.priority)
}
return new Promise(async (resolve, reject) => {
try {
form
.validateFields()
.then((value) => {
const finalValue = {
...value,
priority: Math.max(1, value.priority)
}
fetchData<BasicResponse<null>>('ai/provider/config', {
method: 'PUT',
eoParams: { provider: entity?.id },
eoBody: finalValue,
eoTransformKeys: ['defaultLlm']
// eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
})
.then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
fetchData<BasicResponse<null>>('ai/provider/config', {
method: entity ? 'PUT' : 'POST',
eoParams: { provider: selectedProvider },
eoBody: finalValue,
eoTransformKeys: ['defaultLlm']
})
.catch((errorInfo) => reject(errorInfo))
})
.catch((errorInfo) => reject(errorInfo))
.then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
})
.catch((errorInfo) => reject(errorInfo))
})
.catch((errorInfo) => reject(errorInfo))
} catch (error) {
reject(error)
}
})
}
@@ -117,6 +163,15 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
autoComplete="off"
disabled={readOnly}
>
{!entity && (
<Form.Item label={$t('供应商')} name="provider" rules={[{ required: true, message: $t('请选择供应商') }]}>
<Select
placeholder={$t('请选择供应商')}
onChange={(value) => setSelectedProvider(value)}
options={providers.map((p) => ({ label: p.name, value: p.id }))}
/>
</Form.Item>
)}
<Form.Item<ModelDetailData> label={$t('默认模型')} name="defaultLlm" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
@@ -134,34 +189,6 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
></Select>
</Form.Item>
<Form.Item<ModelDetailData>
label={
<span className="flex items-center">
{$t('负载优先级')}
<Tooltip
title={$t('负载优先级决定在原供应商异常或停用后,优先使用哪一个供应商。优先级数字越小,优先级越高。')}
>
<QuestionCircleOutlined className="ml-1 text-gray-500" />
</Tooltip>
</span>
}
name="priority"
rules={[
{ required: true },
{
validator: async (_, value) => {
if (value <= 0) {
throw new Error($t('优先级必须大于 0'))
}
return Promise.resolve()
}
}
]}
initialValue={1}
>
<InputNumber className="w-INPUT_NORMAL" min={1} placeholder={$t('请输入优先级')} />
</Form.Item>
<Form.Item<ModelDetailData> label={$t('API Key(默认 Key')} name="config">
<Codebox
editorTheme="vs-dark"
@@ -172,8 +199,7 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
enableToolbar={false}
/>
</Form.Item>
{entity.configured && (
{entity?.id && (
<Form.Item className="p-4 bg-white rounded-lg" label={$t('LLM 状态管理')}>
<div className="flex justify-between items-center">
<div>
@@ -27,11 +27,10 @@ const OnlineModelList: React.FC = () => {
const handleDelete = async (id: string) => {
try {
const response = await fetchData<BasicResponse<any>>('ai/resource/key', {
const response = await fetchData<BasicResponse<any>>('ai/provider', {
method: 'DELETE',
eoParams: {
id: id,
branchID: 0
provider: id
}
// eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
})
@@ -61,7 +60,6 @@ const OnlineModelList: React.FC = () => {
})
if (response.code === STATUS_CODE.SUCCESS) {
console.log(response)
setTotal(response.data.total)
return {
data: response.data.providers,
@@ -128,8 +126,8 @@ const OnlineModelList: React.FC = () => {
dataIndex: 'status',
ellipsis: true,
valueType: 'select',
filters: true,
onFilter: true,
// filters: true,
// onFilter: true,
valueEnum: statusEnum,
render: (dom: React.ReactNode, entity: ModelListData) => statusEnum[entity.status]?.text || entity.status
},
@@ -160,7 +158,6 @@ const OnlineModelList: React.FC = () => {
showPagination={true}
searchPlaceholder={$t('请输入名称搜索')}
columns={columns}
dragSortKey="drag"
addNewBtnTitle={$t('添加模型')}
onAddNewBtnClick={handleAdd}
/>
@@ -1,13 +1,11 @@
import Icon from '@ant-design/icons'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import { checkAccess } from '@common/utils/permission'
import { App } from 'antd'
import { createContext, useContext, useRef } from 'react'
import AiSettingModalContent, { AiSettingModalContentHandle } from '../AiSettingModal'
import { AiSettingListItem, ModelDetailData } from '../types'
import { AiSettingListItem } from '../types'
interface AiSettingContextType {
openConfigModal: (entity?: AiSettingListItem) => Promise<void>
@@ -16,30 +14,17 @@ interface AiSettingContextType {
const AiSettingContext = createContext<AiSettingContextType | undefined>(undefined)
export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { modal, message } = App.useApp()
const { fetchData } = useFetch()
const { modal } = App.useApp()
const { aiConfigFlushed, setAiConfigFlushed, accessData } = useGlobalContext()
const modalRef = useRef<AiSettingModalContentHandle>()
const openConfigModal = async (entity: AiSettingListItem) => {
message.loading($t(RESPONSE_TIPS.loading))
const { code, data, msg } = await fetchData<BasicResponse<{ provider: ModelDetailData }>>('ai/provider/config', {
method: 'GET',
eoParams: { provider: entity!.id },
eoTransformKeys: ['get_apikey_url']
})
message.destroy()
if (code !== STATUS_CODE.SUCCESS) {
message.error(msg || $t(RESPONSE_TIPS.error))
return
}
const openConfigModal = async (entity?: AiSettingListItem) => {
modal.confirm({
title: $t('模型配置'),
content: (
<AiSettingModalContent
ref={modalRef}
entity={{ ...data.provider, defaultLlm: entity.defaultLlm }}
entity={{ id: entity?.id, defaultLlm: entity?.defaultLlm }}
readOnly={!checkAccess('system.devops.ai_provider.edit', accessData)}
/>
),
@@ -58,10 +43,10 @@ export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ chi
<a
target="_blank"
rel="noopener noreferrer"
href={data.provider.getApikeyUrl}
// href={data.provider.getApikeyUrl}
className="flex items-center gap-[8px]"
>
<span>{$t('从 (0) 获取 API KEY', [data.provider.name])}</span>
{/* <span>{$t('从 (0) 获取 API KEY', [data.provider.name])}</span> */}
<Icon icon="ic:baseline-open-in-new" width={16} height={16} />
</a>
<div>
@@ -1,10 +1,10 @@
export type ModelStatus = 'enabled' | 'abnormal'|'disabled'
export type KeyStatus ='normal' | 'abnormal'|'disabled'
export type ModelStatus = 'enabled' | 'abnormal' | 'disabled'
export type KeyStatus = 'normal' | 'abnormal' | 'disabled'
export interface KeyData {
id: string
name: string
status: KeyStatus,
status: KeyStatus
}
export interface ModelListData {
@@ -17,16 +17,14 @@ export interface ModelListData {
key_count: number
keys: KeyData[]
}
export interface ModelDetailData extends ModelListData{
enable:boolean
config: string,
priority?: number
export interface ModelDetailData extends ModelListData {
enable: boolean
config: string
getApikeyUrl: string
status: ModelStatus
configured: boolean
}
export type AiSettingListItem = {
name: string
id: string
@@ -53,5 +51,3 @@ export type AiProviderDefaultConfig = {
defaultLlm: string
scopes: string[]
}