feat: load banancing list

This commit is contained in:
ningyv
2025-02-11 18:27:14 +08:00
parent 95b5d848f7
commit fb023a039b
10 changed files with 857 additions and 116 deletions
@@ -160,6 +160,13 @@ const mockData = [
path: '/aiApis',
icon: 'ic:baseline-api',
access: 'system.settings.ai_api.view'
},
{
name: '负载均衡',
key: 'loadBalancing',
path: '/loadBalancing',
icon: 'ph:network-x',
access: 'system.settings.data_source.view'
}
]
},
@@ -219,6 +219,16 @@ const mockData = {
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'loadBalancing',
router: [
{
path: 'loadBalancing',
type: 'normal'
}
]
}
// {
// "driver": "apipark.remote.normal",
@@ -799,5 +799,20 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
}
]
}
],
[
'loadBalancing',
{
type: 'module',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/loadBalancing/loadBalancingLayout.tsx')),
key: 'loadBalancing',
children: [
{
path: 'list',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/loadBalancing/index.tsx')),
key: 'loadBalancingList'
}
]
}
]
])
@@ -79,7 +79,7 @@ const AiServiceInsideRouterCreate = () => {
timeout,
retry,
aiPrompt: { variables: variables, prompt: prompt },
aiModel: { id: defaultLlm?.id, provider: defaultLlm?.provider, config: defaultLlm?.config },
aiModel: { id: defaultLlm?.id, provider: defaultLlm?.provider, config: defaultLlm?.config, type: defaultLlm?.type },
disabled
}
return fetchData<BasicResponse<null>>('service/ai-router', {
@@ -237,13 +237,14 @@ const AiServiceInsideRouterCreate = () => {
}
const handlerSubmit: () => Promise<boolean> | undefined = () => {
return drawerAddFormRef.current?.save()?.then((res: { id: string; config: string }) => {
return drawerAddFormRef.current?.save()?.then((res: { id: string; config: string, type: string, provider: string }) => {
setDefaultLlm(
(prev) =>
({
...prev,
provider: res.provider,
id: res.id,
type: res.type,
config: res.config,
logo: llmList?.find((x: AiProviderLlmsItems) => x.id === res.id)?.logo
}) as AiProviderDefaultConfig & { config: string }
@@ -1,132 +1,217 @@
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 { AiProviderDefaultConfig, AiProviderLlmsItems } from "@core/pages/aiSetting/AiSettingList"
import { SimpleAiProviderItem } from "@core/pages/system/SystemConfig"
import { Form, message, Select, Tag } from "antd"
import { DefaultOptionType } from "antd/es/select"
import { forwardRef, useEffect, useImperativeHandle, useState } from "react"
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 { AiProviderDefaultConfig, AiProviderLlmsItems } from '@core/pages/aiSetting/AiSettingList'
import { LocalLlmType } from '@core/pages/loadBalancing/type'
import { SimpleAiProviderItem } from '@core/pages/system/SystemConfig'
import { Form, message, Select, Tag } from 'antd'
import { DefaultOptionType } from 'antd/es/select'
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
export type AiServiceRouterModelConfigHandle = {
save:()=>Promise<{id:string, config:string}>
save: () => Promise<{ id: string; config: string, type: string, provider: string }>
}
export type AiServiceRouterModelConfigProps = {
entity:AiServiceRouterModelConfigField
llmList:AiProviderLlmsItems[]
entity: AiServiceRouterModelConfigField
llmList: AiProviderLlmsItems[]
}
type AiServiceRouterModelConfigField = {
provider:string
id:string
config:string
provider: string
id: string
config: string
type: string
}
const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle, AiServiceRouterModelConfigProps>((props, ref)=>{
const [form] = Form.useForm();
const {entity} = props
const [providerList, setProviderList]= useState<DefaultOptionType[]>([])
const [llmList, setLlmList]= useState<DefaultOptionType[]>([])
const {fetchData} = useFetch()
useImperativeHandle(ref, ()=>({
save:form.validateFields
})
)
const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle, AiServiceRouterModelConfigProps>(
(props, ref) => {
const [form] = Form.useForm()
const { entity } = props
const [providerList, setProviderList] = useState<DefaultOptionType[]>([])
const [llmList, setLlmList] = useState<DefaultOptionType[]>([])
const [modelType, setModelType] = useState<'online' | 'local'>('online')
const { fetchData } = useFetch()
useImperativeHandle(ref, () => ({
save: form.validateFields
}))
const [modelTypeList] = useState([
{
label: $t('线上模型'),
value: 'online'
},
{
label: $t('本地模型'),
value: 'local'
}
])
useEffect(()=>{
/**
* 获取本地模型列表
* @param setDefaultValue
*/
const getLocalLlmList = (setDefaultValue?: boolean) => {
fetchData<LocalLlmType[]>('simple/ai/models/local/configured', {
method: 'GET',
eoTransformKeys: ['default_config']
}).then((response) => {
const models = response.data.models || []
setLlmList(models)
if (setDefaultValue && models.length) {
const id = models[0].id
form.setFieldsValue({
id,
config: models.find((x) => x.id === id)?.defaultConfig
})
}
})
}
/**
* 切换模型类型
* @param e
*/
const modelTypeChange = (e: string) => {
setModelType(e as 'online' | 'local')
setLlmList([])
form.setFieldsValue({
provider: '',
id: '',
config: '',
type: e
})
if (e === 'online') {
getProviderList(true)
} else {
getLocalLlmList(true)
}
}
useEffect(() => {
if (entity.type === 'online') {
getProviderList()
form.setFieldsValue(entity)
},[])
const getProviderList = ()=>{
setProviderList([])
fetchData<BasicResponse<{ providers: SimpleAiProviderItem[] }>>('simple/ai/providers',{method:'GET',eoTransformKeys:[]}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setProviderList(data.providers?.filter(x=>x.configured)?.map((x:SimpleAiProviderItem)=>{return {...x,
label: x.name, value:x.id
}}))
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const getLlmList = (provider:string)=>{
fetchData<BasicResponse<{llms:AiProviderLlmsItems[],provider:AiProviderDefaultConfig}>>('ai/provider/llms',{method:'GET',eoParams:{provider}, eoTransformKeys:['default_llm']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setLlmList(data.llms)
form.setFieldsValue({
id:data.provider.defaultLlm,
config:data.llms.find(x=>x.id===data.provider.defaultLlm)?.config})
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> console.error(errorInfo))
}
const handleChangeProvider = (provider:string)=>{
getLlmList(provider)
}
useEffect(()=>{
getLlmList(entity.provider)
},[])
} else {
getLocalLlmList()
}
form.setFieldsValue(entity)
}, [])
const getProviderList = (setDefaultValue?: boolean) => {
setProviderList([])
fetchData<BasicResponse<{ providers: SimpleAiProviderItem[] }>>('simple/ai/providers/configured', {
method: 'GET',
eoTransformKeys: []
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setProviderList(
data.providers
?.map((x: SimpleAiProviderItem) => {
return { ...x, label: x.name, value: x.id }
})
)
if (setDefaultValue && data.providers.length) {
const id = data.providers[0].id
form.setFieldValue('provider', id)
getLlmList(id)
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const getLlmList = (provider: string) => {
fetchData<BasicResponse<{ llms: AiProviderLlmsItems[]; provider: AiProviderDefaultConfig }>>('ai/provider/llms', {
method: 'GET',
eoParams: { provider },
eoTransformKeys: ['default_llm']
})
.then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setLlmList(data.llms)
form.setFieldsValue({
id: data.provider.defaultLlm,
config: data.llms.find((x) => x.id === data.provider.defaultLlm)?.config
})
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
.catch((errorInfo) => console.error(errorInfo))
}
const handleChangeProvider = (provider: string) => {
getLlmList(provider)
}
return (
<Form
layout='vertical'
labelAlign='left'
scrollToFirstError
form={form}
className="mx-auto flex flex-col h-full"
name="aiServiceInsideRouterModalConfig"
autoComplete="off"
>
<Form.Item<AiServiceRouterModelConfigField>
label={$t("模型供应商")}
name="provider"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={providerList}
onChange={(e)=>{
handleChangeProvider(e)
}}>
</Select>
</Form.Item>
<Form
layout="vertical"
labelAlign="left"
scrollToFirstError
form={form}
className="mx-auto flex flex-col h-full"
name="aiServiceInsideRouterModalConfig"
autoComplete="off"
>
<Form.Item<AiServiceRouterModelConfigField> label={$t('模型类型')} name="type" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={modelTypeList}
onChange={(e) => {
modelTypeChange(e)
}}
></Select>
</Form.Item>
{modelType === 'online' && (
<Form.Item<AiServiceRouterModelConfigField>
label={$t('模型供应商')}
name="provider"
rules={[{ required: true }]}
>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={providerList}
onChange={(e) => {
handleChangeProvider(e)
}}
></Select>
</Form.Item>
)}
<Form.Item<AiServiceRouterModelConfigField>
label={$t("模型")}
name="id"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={llmList?.map(x=>({
value:x.id,
label:<div className="flex items-center gap-[10px]">
<span>{x.id}</span>
{x?.scopes?.map(s=><Tag >{s?.toLocaleUpperCase()}</Tag>)}
</div>}))}
onChange={(e)=>{
form.setFieldValue('config',llmList.find(x=>x.id===e)?.config)
}}>
</Select>
</Form.Item>
<Form.Item<AiServiceRouterModelConfigField> label={$t('模型')} name="id" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={
llmList?.map((x) => ({
value: x.id,
label: (
<div className="flex items-center gap-[10px]" key={x.id}>
<span>{x.id}</span>
{modelType === 'online' && x?.scopes?.map((s: any) => <Tag>{s?.toLocaleUpperCase()}</Tag>)}
</div>
)
}))
}
onChange={(e) => {
form.setFieldValue('config', llmList.find((x) => x.id === e)?.config || llmList.find((x) => x.id === e)?.defaultConfig)
}}
></Select>
</Form.Item>
<Form.Item<AiServiceRouterModelConfigField>
label={$t("参数")}
name="config"
>
<Codebox editorTheme="vs-dark"
width="100%" height="300px" language='json' enableToolbar={false} />
</Form.Item>
</Form>
<Form.Item<AiServiceRouterModelConfigField> label={$t('参数')} name="config">
<Codebox editorTheme="vs-dark" width="100%" height="300px" language="json" enableToolbar={false} />
</Form.Item>
</Form>
)
})
}
)
export default AiServiceRouterModelConfig
export default AiServiceRouterModelConfig
@@ -272,7 +272,7 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
disabled={readOnly}
>
{modelMode === 'manual' && (
<Form.Item<ModelDetailData> label={$t('模型来源')} name="modelMode" rules={[{ required: true }]}>
<Form.Item<ModelDetailData> label={$t('模型供应商')} name="modelMode" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
@@ -0,0 +1,227 @@
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales/index.ts'
import { App, Form, Select, Tag } from 'antd'
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import { LoadBalancingHandle, LoadModelDetailData, LocalLlmType } from './type'
import { ApiResponse } from '../aiSetting/AIFlowChart'
import { AiProviderLlmsItems, ModelListData } from '../aiSetting/types'
import { DefaultOptionType } from 'antd/es/select'
const AddLoadBalancingModel = forwardRef<LoadBalancingHandle>((props, ref: any) => {
const [form] = Form.useForm()
const [modelProviderLoading, setModelProviderLoading] = useState(false)
const [modelProviderData, setModelProviderData] = useState<ModelListData[]>([])
const [llmList, setLlmList] = useState<DefaultOptionType[]>()
const [modelType, setModelType] = useState<'online' | 'local'>('online')
const { message } = App.useApp()
const [llmListLoading, setLlmListLoading] = useState<boolean>(false)
const { fetchData } = useFetch()
const [modelTypeList] = useState([
{
label: $t('线上模型'),
value: 'online'
},
{
label: $t('本地模型'),
value: 'local'
}
])
/**
* 获取 llm 列表
* @param id
*/
const getLlmList = (id?: string) => {
setLlmListLoading(true)
fetchData<BasicResponse<{ llms: AiProviderLlmsItems[] }>>(`ai/provider/llms`, {
method: 'GET',
eoParams: { provider: id },
eoTransformKeys: ['default_llm']
})
.then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setLlmList(data.llms)
form.setFieldValue('model', data.provider?.defaultLlm)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
.finally(() => {
setLlmListLoading(false)
})
}
/**
* 重置表单数据
* @param e
*/
const resetFormData = (e = 'online') => {
form.setFieldValue('type', e)
form.setFieldValue('model', '')
form.setFieldValue('provider', '')
setModelProviderData([])
setLlmList([])
setModelType(e as 'online' | 'local')
}
/**
* 切换模型类型
* @param e
*/
const modelTypeChange = (e: string) => {
resetFormData(e)
if (e === 'online') {
setModelProviderLoading(true)
fetchData<ApiResponse>('simple/ai/providers/configured', {
method: 'GET',
eoTransformKeys: ['default_llm']
})
.then((response) => {
const mockApiResponse: ApiResponse = response as ApiResponse
const providers = mockApiResponse.data.providers || []
setModelProviderData(providers)
if (providers.length) {
const id = providers[0].id
form.setFieldValue('provider', id)
getLlmList(id)
}
})
.finally(() => {
setModelProviderLoading(false)
})
} else {
setLlmListLoading(true)
fetchData<LocalLlmType[]>('simple/ai/models/local/configured', {
method: 'GET'
})
.then((response) => {
const models = response.data.models || []
setLlmList(models)
if (models.length) {
const id = models[0].id
form.setFieldValue('model', id)
}
})
.finally(() => {
setLlmListLoading(false)
})
}
}
/**
* 模型提供商变化
* @param e
*/
const modelProviderChange = (e: string) => {
form.setFieldValue('modelProvider', e)
getLlmList(e)
}
useEffect(() => {
modelTypeChange('online')
}, [])
/**
* 保存
*/
const save = () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then((values) => {
fetchData<ApiResponse>('ai/balance', {
method: 'POST',
eoBody: values
})
.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((error) => {
reject(error)
})
})
.catch((errorInfo) => {
reject(errorInfo)
})
})
}
useImperativeHandle(ref, () => ({
save
}))
return (
<Form
form={form}
layout="vertical"
labelAlign="left"
scrollToFirstError
className="flex flex-col mx-auto h-full"
name="aiServiceInsideRouterModalConfig"
autoComplete="off"
>
<Form.Item<LoadModelDetailData> label={$t('模型类型')} name="type" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={modelTypeList}
onChange={(e) => {
modelTypeChange(e)
}}
></Select>
</Form.Item>
{modelType === 'online' && (
<Form.Item<LoadModelDetailData> label={$t('模型供应商')} name="provider" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
loading={modelProviderLoading}
options={modelProviderData?.map((x) => ({
value: x.id,
label: (
<div className="flex items-center gap-[10px]">
<span>{x.name}</span>
</div>
)
}))}
onChange={(e) => {
modelProviderChange(e)
}}
></Select>
</Form.Item>
)}
<Form.Item label={$t('模型')} name="model" className="mt-[16px]" rules={[{ required: true }]}>
<Select
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)}
loading={llmListLoading}
options={
llmList?.map((x) => ({
value: x.id,
label: (
<div className="flex items-center gap-[10px]">
<span>{x.id}</span>
{ modelType === 'online' &&x?.scopes?.map((s: any) => <Tag key={s}>{s?.toLocaleUpperCase()}</Tag>)}
</div>
)
}))
}
onChange={(value) => {
form.setFieldValue('model', value)
}}
></Select>
</Form.Item>
</Form>
)
})
export default AddLoadBalancingModel
@@ -0,0 +1,349 @@
import { ActionType } from '@ant-design/pro-components'
import InsidePage from '@common/components/aoplatform/InsidePage'
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList'
import WithPermission from '@common/components/aoplatform/WithPermission'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales/index.ts'
import { App, Button, Typography } from 'antd'
import { useEffect, useRef, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { LoadBalancingHandle, LoadBalancingItems } from './type'
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission'
import AddLoadBalancingModel from './AddModel'
const LoadBalancingPage = () => {
const pageListRef = useRef<ActionType>(null)
const [searchParams] = useSearchParams()
const serviceId = searchParams.get('serviceId')
const [searchWord, setSearchWord] = useState<string>('')
const [columns, setColumns] = useState<PageProColumns<LoadBalancingItems>[]>([])
const { modal, message } = App.useApp()
const [apiKeys, setApiKeys] = useState<LoadBalancingItems[]>([])
const addModelRef = useRef<LoadBalancingHandle>()
const statusEnum: Record<string, { text: React.ReactNode }> = {
normal: { text: <Typography.Text type="success">{$t('正常')}</Typography.Text> },
abnormal: { text: <Typography.Text type="danger">{$t('异常')}</Typography.Text> }
}
/**
* 请求数据
*/
const { fetchData } = useFetch()
const addModel = () => {
modal.confirm({
title: $t('添加负载均衡'),
content: <AddLoadBalancingModel ref={addModelRef} />,
width: 600,
closable: true,
onOk: () => {
return addModelRef.current?.save().then((res) => {
if (res === true) {
pageListRef.current?.reload()
}
})
},
wrapClassName: 'ant-modal-without-footer',
okText: $t('确认'),
cancelText: $t('取消'),
icon: <></>
})
}
/**
* 获取列表数据
* @param dataType
* @returns
*/
const requestApis = (
params: LoadBalancingItems & {
pageSize: number
current: number
},
sort: Record<string, string>,
filter: Record<string, string>
) => {
let filters
if (filter) {
filters = []
if (filter.isStop) {
if (filter.isStop.indexOf('true') !== -1) {
filters.push('enable')
}
if (filter.isStop.indexOf('false') !== -1) {
filters.push('disable')
}
if (filter.publishStatus?.length > 0) {
filters = [...filters, ...filter.publishStatus]
}
}
}
return fetchData<BasicResponse<{ list: LoadBalancingItems[]; total: number }>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/list`,
{
method: 'GET',
eoParams: {
order: Object.keys(sort)?.[0],
sort: Object.keys(sort)?.length > 0 ? (Object.values(sort)?.[0] === 'descend' ? 'desc' : 'asc') : undefined,
filters: JSON.stringify(filters),
keyword: searchWord,
service: serviceId
},
eoTransformKeys: ['is_stop', 'is_delete', 'update_time', 'publish_status', 'processed_total']
}
)
.then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setApiKeys(response.data.list)
// 保存数据
return {
data: data.list,
total: data.total,
success: true
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return { data: [], success: false }
}
})
.catch(() => {
return { data: [], success: false }
})
}
/**
* 排序
* @param beforeIndex
* @param afterIndex
* @param newDataSource
*/
const handleDragSortEnd = async (beforeIndex: number, afterIndex: number, newDataSource: LoadBalancingItems[]) => {
try {
let targetId
let sortDirection
// Check if there's an item before afterIndex
if (afterIndex > 0) {
targetId = newDataSource[afterIndex - 1].id
sortDirection = 'after'
} else if (afterIndex < newDataSource.length - 1) {
// If no item before, use the item after
targetId = newDataSource[afterIndex + 1].id
sortDirection = 'before'
}
const response = await fetchData<BasicResponse<any>>('ai/balance/sort', {
method: 'PUT',
eoBody: {
origin: apiKeys[beforeIndex].id,
target: targetId,
sort: sortDirection
}
})
if (response.code === STATUS_CODE.SUCCESS) {
message.success($t('排序成功'))
pageListRef.current?.reload()
} else {
message.error(response.msg || RESPONSE_TIPS.error)
// Revert the UI if API call fails
pageListRef.current?.reload()
}
} catch (error) {
message.error(RESPONSE_TIPS.error)
// Revert the UI if API call fails
pageListRef.current?.reload()
}
}
/**
* 删除
* @param id
*/
const handleDelete = (id: string) => {
fetchData<BasicResponse<null>>('ai/balance', {
method: 'DELETE',
eoBody: {
id
}
})
.then((response) => {
const { code } = response
if (code === STATUS_CODE.SUCCESS) {
message.success($t('删除成功'))
pageListRef.current?.reload()
} else {
message.error(RESPONSE_TIPS.error)
}
})
.catch((error) => {
message.error(RESPONSE_TIPS.error)
})
}
/**
* 设置表格列
*/
const setTableColumns = () => {
setColumns([
{
title: '',
dataIndex: 'drag',
width: '40px'
},
{
title: $t('优先级'),
dataIndex: 'priority',
width: 80,
ellipsis: true,
key: 'priority'
},
{
title: $t('模型'),
dataIndex: ['provider', 'name'],
ellipsis: true,
width: 100,
key: 'provider',
render: (text: string, record: LoadBalancingItems) => (
<span>
{record.provider?.name} / {record.model?.name}
</span>
)
},
{
title: $t('类型'),
dataIndex: 'type',
width: 100,
ellipsis: true,
key: 'type',
render: (text: string, record: LoadBalancingItems) => (
<span>{record.type === 'online' ? $t('线上模型') : $t('本地模型')}</span>
)
},
{
title: $t('状态'),
dataIndex: 'state',
width: 120,
ellipsis: true,
key: 'state',
render: (text: string, record: LoadBalancingItems) => <span>{statusEnum[record.state]?.text || '-'}</span>
},
{
title: $t('API 数量'),
dataIndex: 'api_count',
ellipsis: true,
width: 80,
key: 'api_count',
render: (text: string, record: LoadBalancingItems) => (
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
<a
href={`/aiApis?modelId=${record.model?.id}`}
target="_blank"
className="key-link"
style={{
fontWeight: 500,
cursor: 'pointer',
pointerEvents: 'all',
textDecoration: 'none'
}}
>
{record.api_count || '-'}
</a>
</span>
)
},
{
title: $t('KEY 数量'),
dataIndex: 'key_count',
ellipsis: true,
width: 80,
key: 'key_count',
render: (text: string, record: LoadBalancingItems) => (
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
<a
href={`/keysetting?modelId=${record.model?.id}`}
target="_blank"
className="key-link"
style={{
fontWeight: 500,
cursor: 'pointer',
pointerEvents: 'all',
textDecoration: 'none'
}}
>
{record.key_count || '-'}
</a>
</span>
)
},
{
title: '',
key: 'option',
btnNums: 1,
width: 80,
fixed: 'right',
valueType: 'option',
render: (_: React.ReactNode, entity: any) => [
<TableBtnWithPermission
access="system.settings.ai_key_resource.manager"
key="delete"
btnType="delete"
onClick={() => handleDelete(entity.id as string)}
btnTitle={$t('删除')}
/>
]
}
])
}
useEffect(() => {
setTableColumns()
}, [])
return (
<>
<InsidePage
pageTitle={$t('负载均衡')}
description={$t(
'系统自动识别异常AI模型后,自动替换成以下优先级最高的可用模型。这将确保您的AI应用保持高可用性和最佳性能,从而防止任何单个LLM异常成为您的性能瓶颈。'
)}
showBorder={false}
scrollPage={false}
>
<div className="h-[calc(100%-1rem-36px)] pr-PAGE_INSIDE_X">
<PageList
ref={pageListRef}
rowKey="id"
afterNewBtn={[
<WithPermission key="removeFromDepPermission" access={``}>
<Button className="mr-btnbase" type="primary" key="removeFromDep" onClick={() => addModel()}>
{$t('添加模型')}
</Button>
</WithPermission>
]}
request={async (
params: any & {
pageSize: number
current: number
},
sort: Record<string, string>,
filter: Record<string, string>
) => requestApis(params, sort, filter)}
onSearchWordChange={(e) => {
setSearchWord(e.target.value)
}}
showPagination={true}
dragSortKey="drag"
onDragSortEnd={handleDragSortEnd}
searchPlaceholder={$t('请输入...')}
columns={columns}
/>
</div>
</InsidePage>
</>
)
}
export default LoadBalancingPage
@@ -0,0 +1,16 @@
import { useEffect } from 'react'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
export default function LoadBalancingLayout() {
const location = useLocation()
const pathName = location.pathname
const navigator = useNavigate()
useEffect(() => {
if (pathName === '/loadBalancing') {
const queryParams = new URLSearchParams(location.search).toString()
navigator(`/loadBalancing/list${queryParams ? `?${queryParams}` : ''}`)
}
}, [pathName])
return <Outlet></Outlet>
}
@@ -0,0 +1,31 @@
export interface LoadBalancingItems {
id: string
priority: string
provider: {
id: string
name: string
}
model: {
id: string
name: string
}
type: string
state: string
api_count: string
key_count: string
}
export interface LoadModelDetailData {
type: string
provider: string
model: string
}
export interface LocalLlmType {
id: string
name: string
defaultConfig: string
}
export type LoadBalancingHandle = {
save: () => Promise<boolean | string>
}