mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
feat: local model list page
This commit is contained in:
@@ -129,7 +129,7 @@ const DEFAULT_HEADERS = {
|
||||
namespace: 'default'
|
||||
}
|
||||
|
||||
export type EoRequest = RequestInit & {
|
||||
type EoRequest = RequestInit & {
|
||||
eoParams?: { [k: string]: unknown }
|
||||
eoTransformKeys?: string[]
|
||||
eoApiPrefix?: string
|
||||
|
||||
@@ -57,7 +57,12 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
eoTransformKeys: ['default_config']
|
||||
}).then((response) => {
|
||||
const models = response.data.models || []
|
||||
setLlmList(models)
|
||||
setLlmList(
|
||||
models.map((x: any) => ({
|
||||
...x,
|
||||
config: x.defaultConfig
|
||||
}))
|
||||
)
|
||||
if (setDefaultValue && models.length) {
|
||||
const id = models[0].id
|
||||
form.setFieldsValue({
|
||||
@@ -144,10 +149,6 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
.catch((errorInfo) => console.error(errorInfo))
|
||||
}
|
||||
|
||||
const handleChangeProvider = (provider: string) => {
|
||||
getLlmList(provider)
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
layout="vertical"
|
||||
@@ -179,7 +180,7 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={providerList}
|
||||
onChange={(e) => {
|
||||
handleChangeProvider(e)
|
||||
getLlmList(e)
|
||||
}}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
@@ -201,7 +202,7 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
}))
|
||||
}
|
||||
onChange={(e) => {
|
||||
form.setFieldValue('config', llmList.find((x) => x.id === e)?.config || llmList.find((x) => x.id === e)?.defaultConfig)
|
||||
form.setFieldValue('config', llmList.find((x) => x.id === e)?.config)
|
||||
}}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
|
||||
@@ -12,7 +12,7 @@ export type AiSettingModalContentProps = {
|
||||
entity?: AISettingEntityItem
|
||||
readOnly: boolean
|
||||
modelMode?: 'auto' | 'manual'
|
||||
originEntity?: string
|
||||
source?: string
|
||||
/** 如果是手动选择 AI 模型,那么需要更新 footer 底部的内容,所以需要这个方法去更新外部的 footer */
|
||||
updateEntityData: (entity: AISettingEntityItem) => void
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export type AiSettingModalContentHandle = {
|
||||
const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingModalContentProps>((props, ref) => {
|
||||
const [form] = Form.useForm()
|
||||
const { message } = App.useApp()
|
||||
const { entity, readOnly, modelMode = 'auto', updateEntityData, originEntity } = props
|
||||
const { entity, readOnly, modelMode = 'auto', updateEntityData, source } = props
|
||||
const { fetchData } = useFetch()
|
||||
const [llmList, setLlmList] = useState<AiProviderLlmsItems[]>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
@@ -163,13 +163,12 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
// 如果是直接在 AI 模型配置,则获取默认模型列表和团队列表
|
||||
if (localEntity?.id) {
|
||||
getModelConfig(localEntity.id)
|
||||
setFormFieldsValue(localEntity)
|
||||
} else {
|
||||
getModelProviderList()
|
||||
originEntity && getTeamOptionList()
|
||||
source && getTeamOptionList()
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -187,7 +186,6 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
team: value.team,
|
||||
provider: localEntity?.id
|
||||
}
|
||||
console.log(finalValue)
|
||||
fetchData<BasicResponse<null>>('quick/service/ai', {
|
||||
method: 'POST',
|
||||
eoBody: finalValue
|
||||
@@ -306,7 +304,7 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
}))}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
{originEntity === 'guide' && (
|
||||
{source === 'guide' && (
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={teamList} onChange={(value) => {
|
||||
form.setFieldValue('team', value)
|
||||
@@ -323,7 +321,7 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
enableToolbar={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
{originEntity !== 'guide' && (
|
||||
{source !== 'guide' && (
|
||||
<Form.Item className="p-4 bg-white rounded-lg" label={$t('LLM 状态管理')}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
|
||||
@@ -113,7 +113,7 @@ const LocalModelList: React.FC = () => {
|
||||
|
||||
const handleEdit = (record: ModelListData) => {
|
||||
modal.confirm({
|
||||
title: $t('部署 AI 模型'),
|
||||
title: $t('模型设置'),
|
||||
content: <EditLocalModelModal ref={EditLocalModelModalRef} modelID={record.id} enable={record.state !== 'disabled'}/>,
|
||||
onOk: () => {
|
||||
return EditLocalModelModalRef.current?.save().then((res) => {
|
||||
@@ -132,7 +132,7 @@ const LocalModelList: React.FC = () => {
|
||||
|
||||
const handleAdd = () => {
|
||||
const modalInstance = modal.confirm({
|
||||
title: $t('部署 AI 模型'),
|
||||
title: $t('部署本地模型'),
|
||||
content: (
|
||||
<LocalAiDeploy
|
||||
ref={localAiDeployRef}
|
||||
@@ -159,15 +159,15 @@ const LocalModelList: React.FC = () => {
|
||||
|
||||
const handleDelete = async (id: string, apiCount: number) => {
|
||||
modal.confirm({
|
||||
title: $t('停止部署'),
|
||||
title: $t('删除模型'),
|
||||
content: `${$t('有')} ${apiCount} ${$t('个API使用当前模型,删除当前的模型配置后,该模型相关的API将会切换为使用负载均衡中优先级最高的可用模型。并且当前模型下的所有API KEY和相关数据将会被清空,是否确认删除当前模型?')}`,
|
||||
onOk: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
fetchData<BasicResponse<any>>('ai/provider', {
|
||||
fetchData<BasicResponse<any>>('model/local', {
|
||||
method: 'DELETE',
|
||||
eoParams: {
|
||||
provider: id
|
||||
model: id
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -206,7 +206,6 @@ const LocalModelList: React.FC = () => {
|
||||
keyword: searchWord,
|
||||
page: params.current
|
||||
},
|
||||
eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/',
|
||||
eoTransformKeys: ['can_delete', 'api_count']
|
||||
})
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const OnlineModelList: React.FC = () => {
|
||||
|
||||
const handleDelete = async (id: string, apiCount: number) => {
|
||||
modal.confirm({
|
||||
title: $t('停止部署'),
|
||||
title: $t('删除模型'),
|
||||
content: `${$t('有')} ${apiCount} ${$t('个API使用当前模型,删除当前的模型配置后,该模型相关的API将会切换为使用负载均衡中优先级最高的可用模型。并且当前模型下的所有API KEY和相关数据将会被清空,是否确认删除当前模型?')}`,
|
||||
onOk: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -41,7 +41,6 @@ const OnlineModelList: React.FC = () => {
|
||||
eoParams: {
|
||||
provider: id
|
||||
}
|
||||
// eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
|
||||
}).then((response) => {
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
message.success($t('删除成功'))
|
||||
@@ -79,7 +78,6 @@ const OnlineModelList: React.FC = () => {
|
||||
page: params.current
|
||||
},
|
||||
eoTransformKeys: ['default_llm', 'api_count', 'key_count', 'can_delete']
|
||||
// eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
|
||||
})
|
||||
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { App } from 'antd'
|
||||
import { Card } from 'antd'
|
||||
import { useRef } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import AiSettingModalContent, { AiSettingModalContentHandle } from '../aiSetting/AiSettingModal'
|
||||
import { checkAccess } from '@common/utils/permission'
|
||||
import LocalAiDeploy, { LocalAiDeployHandle } from './LocalAiDeploy'
|
||||
@@ -68,7 +67,7 @@ export const AIModelGuide = () => {
|
||||
ref={modalRef}
|
||||
modelMode="manual"
|
||||
updateEntityData={updateEntityData}
|
||||
originEntity="guide"
|
||||
source="guide"
|
||||
readOnly={!checkAccess('system.devops.ai_provider.edit', accessData)}
|
||||
/>
|
||||
),
|
||||
@@ -111,7 +110,7 @@ export const AIModelGuide = () => {
|
||||
*/
|
||||
const localModelCardClick = async () => {
|
||||
const modalInstance = modal.confirm({
|
||||
title: $t('部署 AI 模型'),
|
||||
title: $t('部署本地模型'),
|
||||
content: <LocalAiDeploy ref={localAiDeployRef} onClose={() => {
|
||||
modalInstance.destroy()
|
||||
dumpServerPage()
|
||||
|
||||
@@ -10,7 +10,7 @@ import useDeployLocalModel from './deployModelUtil'
|
||||
|
||||
const { Dragger } = Upload
|
||||
export type RestAIDeployHandle = {
|
||||
deployRestAIServer: () => Promise<boolean | string>
|
||||
deployRestAIServer: () => Promise<boolean | string>
|
||||
}
|
||||
const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any) => {
|
||||
const [form] = Form.useForm()
|
||||
@@ -38,36 +38,36 @@ const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any)
|
||||
getTeamList()
|
||||
}, [])
|
||||
|
||||
/**
|
||||
/**
|
||||
* 部署 rest 服务
|
||||
* @param file
|
||||
* @returns
|
||||
*/
|
||||
const deployRestServer = async (file: File) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('type', file.type)
|
||||
formData.append('team', form.getFieldValue('team'))
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>('quick/service/rest', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).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(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const deployRestServer = async (file: File) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('type', file.type)
|
||||
formData.append('team', form.getFieldValue('team'))
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>('quick/service/rest', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).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(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署本地AI
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
const deployRestAIServer = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -86,39 +86,35 @@ const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any)
|
||||
}))
|
||||
return (
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="partitionInsideCert"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
name="key"
|
||||
className="mb-0 bg-transparent p-0 border-none rounded-none"
|
||||
rules={[{ required: true }]}
|
||||
<Form
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="partitionInsideCert"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Dragger {...uploadProps}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<Icon className="text-[#ccc]" icon="tdesign:upload" width="50" height="50" />
|
||||
</p>
|
||||
<p className="ant-upload-text">{$t('选择 OpenAPI 文件 (.json / .yaml)')}</p>
|
||||
</Dragger>
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('team', value)
|
||||
}}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
<Form.Item name="key" className="mb-0 bg-transparent p-0 border-none rounded-none" rules={[{ required: true }]}>
|
||||
<Dragger {...uploadProps}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<Icon className="text-[#ccc]" icon="tdesign:upload" width="50" height="50" />
|
||||
</p>
|
||||
<p className="ant-upload-text">{$t('选择 OpenAPI 文件 (.json / .yaml)')}</p>
|
||||
</Dragger>
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('team', value)
|
||||
}}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -9,27 +9,23 @@ import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
const useDeployLocalModel = () => {
|
||||
const { fetchData } = useFetch()
|
||||
const { checkPermission } = useGlobalContext()
|
||||
const deployLocalModel = (value: { modelID: string; team?: number }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<null>>('model/local/deploy/start', {
|
||||
const deployLocalModel = async (value: { modelID: string; team?: number }) => {
|
||||
const response = await fetchData<BasicResponse<null>>(
|
||||
'model/local/deploy/start',
|
||||
{
|
||||
method: 'POST',
|
||||
eoBody: {
|
||||
model: value.modelID,
|
||||
team: value?.team
|
||||
}
|
||||
})
|
||||
.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(false)
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
)
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取 team 选项列表
|
||||
|
||||
@@ -57,42 +57,15 @@ const LoadBalancingPage = () => {
|
||||
* @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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const requestApis = () => {
|
||||
return fetchData<BasicResponse<{ list: LoadBalancingItems[]; total: number }>>(
|
||||
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/list`,
|
||||
`ai/balances`,
|
||||
{
|
||||
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
|
||||
keyword: searchWord
|
||||
},
|
||||
eoTransformKeys: ['is_stop', 'is_delete', 'update_time', 'publish_status', 'processed_total']
|
||||
eoTransformKeys: ['api_count', 'key_count']
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
@@ -234,10 +207,10 @@ const LoadBalancingPage = () => {
|
||||
},
|
||||
{
|
||||
title: $t('Apis'),
|
||||
dataIndex: 'api_count',
|
||||
dataIndex: 'apiCount',
|
||||
ellipsis: true,
|
||||
width: 80,
|
||||
key: 'api_count',
|
||||
key: 'apiCount',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
|
||||
<a
|
||||
@@ -251,17 +224,17 @@ const LoadBalancingPage = () => {
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
>
|
||||
{record.api_count || '0'}
|
||||
{record.apiCount || '0'}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('Keys'),
|
||||
dataIndex: 'key_count',
|
||||
dataIndex: 'keyCount',
|
||||
ellipsis: true,
|
||||
width: 80,
|
||||
key: 'key_count',
|
||||
key: 'keyCount',
|
||||
render: (dom: React.ReactNode, record: LoadBalancingItems) => (
|
||||
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
|
||||
<a
|
||||
@@ -275,7 +248,7 @@ const LoadBalancingPage = () => {
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
>
|
||||
{record.key_count || '0'}
|
||||
{record.keyCount || '0'}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
@@ -289,7 +262,7 @@ const LoadBalancingPage = () => {
|
||||
valueType: 'option',
|
||||
render: (_: React.ReactNode, entity: any) => [
|
||||
<TableBtnWithPermission
|
||||
access="system.settings.ai_key_resource.manager"
|
||||
access=""
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
onClick={() => handleDelete(entity.id as string)}
|
||||
@@ -324,14 +297,7 @@ const LoadBalancingPage = () => {
|
||||
</Button>
|
||||
</WithPermission>
|
||||
]}
|
||||
request={async (
|
||||
params: any & {
|
||||
pageSize: number
|
||||
current: number
|
||||
},
|
||||
sort: Record<string, string>,
|
||||
filter: Record<string, string>
|
||||
) => requestApis(params, sort, filter)}
|
||||
request={() => requestApis()}
|
||||
onSearchWordChange={(e) => {
|
||||
setSearchWord(e.target.value)
|
||||
}}
|
||||
|
||||
@@ -11,8 +11,8 @@ export interface LoadBalancingItems {
|
||||
}
|
||||
type: string
|
||||
state: string
|
||||
api_count: string
|
||||
key_count: string
|
||||
apiCount: string
|
||||
keyCount: string
|
||||
}
|
||||
|
||||
export interface LoadModelDetailData {
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ export const LogsFooter = (props: any) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<any>>('model/local/cancel_deploy', {
|
||||
method: 'POST',
|
||||
eoBody: { recordId: record.id }
|
||||
eoBody: { model: record.id }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
@@ -47,7 +47,7 @@ export const LogsFooter = (props: any) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<any>>('model/local', {
|
||||
method: 'DELETE',
|
||||
eoBody: { recordId: record.id }
|
||||
eoBody: { model: record.id }
|
||||
})
|
||||
.then((response: BasicResponse<any>) => {
|
||||
const { code, msg } = response
|
||||
|
||||
@@ -60,12 +60,10 @@ export const ServiceDeployment = (props: { record: SystemTableListItem }) => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(
|
||||
'http://localhost:3000/stream',
|
||||
// 'model/local/deploy',
|
||||
'model/local/deploy',
|
||||
{
|
||||
method: 'POST',
|
||||
eoBody: { recordId: record.id },
|
||||
eoApiPrefix: '',
|
||||
headers: {
|
||||
'Content-Type': 'event-stream'
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user