mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Merge remote-tracking branch 'origin/feature/1.5-cx' into feature/1.5-local-model
This commit is contained in:
@@ -25,10 +25,11 @@ interface AIProviderResponse {
|
||||
interface AIProviderSelectProps {
|
||||
value?: string
|
||||
onChange?: (value: string, provider: AIProvider) => void
|
||||
style?: React.CSSProperties
|
||||
style?: React.CSSProperties,
|
||||
source?: 'ai_api' | 'ai_keys'
|
||||
}
|
||||
|
||||
const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, style = { width: 200 } }) => {
|
||||
const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, source = 'ai', style = { width: 200 } }) => {
|
||||
const { t } = useTranslation()
|
||||
const [providers, setProviders] = useState<AIProvider[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -40,7 +41,7 @@ const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, st
|
||||
if (isMounted) setLoading(true)
|
||||
try {
|
||||
const endpoint = 'simple/ai/providers/configured'
|
||||
const response = await fetchData<AIProviderResponse>(endpoint, { method: 'GET', eoParams: { all: true} })
|
||||
const response = await fetchData<AIProviderResponse>(endpoint, { method: 'GET', ...(source === 'ai_api' ? { eoParams: { all: true } } : {}) })
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const providers = data.providers.map((val) => ({
|
||||
|
||||
@@ -752,6 +752,9 @@ p{
|
||||
.custom-steps .ant-steps-item-content {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.custom-steps .ant-steps-item-content .ant-steps-item-description {
|
||||
width: 138px !important;
|
||||
}
|
||||
|
||||
|
||||
.ant-modal-body .pr-PAGE_INSIDE_X{
|
||||
|
||||
@@ -255,6 +255,7 @@ const ApiSettings: React.FC = () => {
|
||||
<div className="flex gap-2 items-center">
|
||||
<AIProviderSelect
|
||||
value={selectedProvider}
|
||||
source="ai_api"
|
||||
onChange={(value, option) => {
|
||||
setSelectedProvider(value)
|
||||
setProvider(option)
|
||||
|
||||
@@ -151,7 +151,7 @@ const AiServiceInsideRouterCreate = () => {
|
||||
type: aiModel?.type
|
||||
}) as AiProviderDefaultConfig & { config: string }
|
||||
)
|
||||
aiModel?.type !== 'local' && getDefaultModelConfig(aiModel?.provider)
|
||||
aiModel?.type !== 'local' && getDefaultModelConfig(aiModel?.provider, false)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
@@ -160,7 +160,7 @@ const AiServiceInsideRouterCreate = () => {
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
const getDefaultModelConfig = (provider?: string) => {
|
||||
const getDefaultModelConfig = (provider?: string, resetDefaultLlm = true) => {
|
||||
fetchData<BasicResponse<{ llms: AiProviderLlmsItems[]; provider: AiProviderDefaultConfig }>>('ai/provider/llms', {
|
||||
method: 'GET',
|
||||
eoParams: { provider: provider ?? aiServiceInfo?.provider?.id },
|
||||
@@ -170,19 +170,21 @@ const AiServiceInsideRouterCreate = () => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setLlmList(data.llms)
|
||||
setDefaultLlm((prev) => {
|
||||
const llmSetting = data.llms?.find(
|
||||
(x: AiProviderLlmsItems) => x.id === (prev?.id ?? data.provider.defaultLlm)
|
||||
)
|
||||
return {
|
||||
...prev,
|
||||
defaultLlm: data.provider.defaultLlm,
|
||||
provider: data.provider.id,
|
||||
name: data.provider.name,
|
||||
config: llmSetting?.config || '',
|
||||
...(llmSetting ?? {})
|
||||
} as AiProviderDefaultConfig & { config: string }
|
||||
})
|
||||
if (resetDefaultLlm) {
|
||||
setDefaultLlm((prev) => {
|
||||
const llmSetting = data.llms?.find(
|
||||
(x: AiProviderLlmsItems) => x.id === (prev?.id ?? data.provider.defaultLlm)
|
||||
)
|
||||
return {
|
||||
...prev,
|
||||
defaultLlm: data.provider.defaultLlm,
|
||||
provider: data.provider.id,
|
||||
name: data.provider.name,
|
||||
config: llmSetting?.config || '',
|
||||
...(llmSetting ?? {})
|
||||
} as AiProviderDefaultConfig & { config: string }
|
||||
})
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
setModelType(entity.type as 'online' | 'local')
|
||||
if (entity.type === 'online') {
|
||||
getProviderList()
|
||||
getLlmList(entity.provider)
|
||||
getLlmList(entity.provider, false)
|
||||
} else {
|
||||
getLocalLlmList()
|
||||
}
|
||||
@@ -129,7 +129,7 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
})
|
||||
}
|
||||
|
||||
const getLlmList = (provider: string) => {
|
||||
const getLlmList = (provider: string, setDefaultValue = true) => {
|
||||
fetchData<BasicResponse<{ llms: AiProviderLlmsItems[]; provider: AiProviderDefaultConfig }>>('ai/provider/llms', {
|
||||
method: 'GET',
|
||||
eoParams: { provider },
|
||||
@@ -139,10 +139,12 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
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
|
||||
})
|
||||
if (setDefaultValue && data.llms.length) {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ const LocalModelList: React.FC = () => {
|
||||
normal: { text: '正常' },
|
||||
deploying: { text: '部署中', className: 'text-[#2196f3] cursor-pointer' },
|
||||
error: { text: '模型异常', className: 'text-[#ff4d4f]' },
|
||||
disabled: { text: '停用', className: 'text-[#999]' },
|
||||
disabled: { text: '停用' },
|
||||
deploying_error: { text: '部署失败', className: 'text-[#ff4d4f] cursor-pointer' }
|
||||
})
|
||||
|
||||
@@ -318,7 +318,7 @@ const LocalModelList: React.FC = () => {
|
||||
render: (dom: React.ReactNode, record: ModelListData) => (
|
||||
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
|
||||
<a
|
||||
href={`/aiApis?modelId=${record?.id}`}
|
||||
href={`/aiApis?modelId=${record?.provider}`}
|
||||
target="_blank"
|
||||
className="key-link"
|
||||
style={{
|
||||
|
||||
@@ -156,6 +156,7 @@ const OnlineModelList: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: $t('默认模型'),
|
||||
ellipsis: true,
|
||||
dataIndex: 'defaultLlm'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ModelListData {
|
||||
name: string
|
||||
logo: string
|
||||
defaultLlm: string | undefined
|
||||
provider?: string
|
||||
modelMode?: string
|
||||
status: ModelStatus
|
||||
state?: ModelDeployStatus
|
||||
|
||||
@@ -57,9 +57,12 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
* @returns
|
||||
*/
|
||||
const deployPopularModel = async (id: string) => {
|
||||
await deployLocalModel({
|
||||
const response = await deployLocalModel({
|
||||
modelID: id
|
||||
})
|
||||
if (response.code !== STATUS_CODE.SUCCESS) {
|
||||
return
|
||||
}
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
@@ -84,10 +87,13 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
form
|
||||
.validateFields()
|
||||
.then(async (value) => {
|
||||
await deployLocalModel({
|
||||
const response = await deployLocalModel({
|
||||
modelID: value.model,
|
||||
team: value.team
|
||||
})
|
||||
if (response.code !== STATUS_CODE.SUCCESS) {
|
||||
return
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
|
||||
@@ -23,6 +23,7 @@ const useDeployLocalModel = () => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return response
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import AddLoadBalancingModel from './AddModel'
|
||||
const LoadBalancingPage = () => {
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
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>()
|
||||
@@ -150,125 +149,120 @@ const LoadBalancingPage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 设置表格列
|
||||
*/
|
||||
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: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span>
|
||||
{record.provider?.name} / {record.model?.name}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('类型'),
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
key: 'type',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span>{record.type === 'online' ? $t('线上模型') : $t('本地模型')}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('状态'),
|
||||
dataIndex: 'state',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
key: 'state',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span>{statusEnum[record.state]?.text || '-'}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('Apis'),
|
||||
dataIndex: 'apiCount',
|
||||
ellipsis: true,
|
||||
width: 80,
|
||||
key: 'apiCount',
|
||||
render: (dom: React.ReactNode, 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.apiCount || '0'}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('Keys'),
|
||||
dataIndex: 'keyCount',
|
||||
ellipsis: true,
|
||||
width: 80,
|
||||
key: 'keyCount',
|
||||
render: (dom: React.ReactNode, 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.keyCount || '0'}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
key: 'option',
|
||||
btnNums: 1,
|
||||
width: 80,
|
||||
fixed: 'right',
|
||||
valueType: 'option',
|
||||
render: (_: React.ReactNode, entity: any) => [
|
||||
<TableBtnWithPermission
|
||||
access="system.settings.ai_balance.delete"
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
onClick={() => handleDelete(entity.id as string)}
|
||||
btnTitle={$t('删除')}
|
||||
/>
|
||||
]
|
||||
}
|
||||
])
|
||||
}
|
||||
useEffect(() => {
|
||||
setTableColumns()
|
||||
}, [])
|
||||
const columns: PageProColumns<LoadBalancingItems>[] = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'drag',
|
||||
width: '40px'
|
||||
},
|
||||
{
|
||||
title: $t('优先级'),
|
||||
dataIndex: 'priority',
|
||||
width: 80,
|
||||
ellipsis: true,
|
||||
key: 'priority'
|
||||
},
|
||||
{
|
||||
title: $t('模型'),
|
||||
dataIndex: ['provider', 'name'],
|
||||
ellipsis: true,
|
||||
key: 'provider',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span>
|
||||
{record.provider?.name} / {record.model?.name}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('类型'),
|
||||
dataIndex: 'type',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
key: 'type',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span>{record.type === 'online' ? $t('线上模型') : $t('本地模型')}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('状态'),
|
||||
dataIndex: 'state',
|
||||
width: 80,
|
||||
ellipsis: true,
|
||||
key: 'state',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span>{statusEnum[record.state]?.text || '-'}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('Apis'),
|
||||
dataIndex: 'apiCount',
|
||||
ellipsis: true,
|
||||
width: 80,
|
||||
key: 'apiCount',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
|
||||
<a
|
||||
href={`/aiApis?modelId=${record.provider?.id}`}
|
||||
target="_blank"
|
||||
className="key-link"
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
pointerEvents: 'all',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
>
|
||||
{record.apiCount || '0'}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('Keys'),
|
||||
dataIndex: 'keyCount',
|
||||
ellipsis: true,
|
||||
width: 80,
|
||||
key: 'keyCount',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
|
||||
<a
|
||||
href={`/keysetting?modelId=${record.provider?.id}`}
|
||||
target="_blank"
|
||||
className="key-link"
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
pointerEvents: 'all',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
>
|
||||
{record.keyCount || '0'}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
key: 'option',
|
||||
btnNums: 1,
|
||||
width: 50,
|
||||
fixed: 'right',
|
||||
valueType: 'option',
|
||||
render: (_: React.ReactNode, entity: any) => [
|
||||
<TableBtnWithPermission
|
||||
access="system.settings.ai_balance.delete"
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
onClick={() => handleDelete(entity.id as string)}
|
||||
btnTitle={$t('删除')}
|
||||
/>
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user