mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
@@ -43,7 +43,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-joyride": "^2.8.2",
|
||||
"react-router-dom": "^6.20.0",
|
||||
"react-router-dom": "6.20.0",
|
||||
"swagger-ui-react": "^5.17.14",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"uuid": "^9.0.1",
|
||||
|
||||
@@ -241,6 +241,9 @@ function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
headerTitleRender={() => (
|
||||
<div className="w-[192px] flex items-center">
|
||||
<img className="h-[20px] cursor-pointer " src={Logo} onClick={() => navigator(mainPage)} />
|
||||
<a className="align-text-top" href="https://github.com/APIParkLab/APIPark" target="_blank" className="ml-[5px] h-[25px] relative">
|
||||
<img src="https://img.shields.io/github/stars/APIParkLab/APIPark?style=social" className='absolute top-[6px]' width={75} alt="" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
logo={Logo}
|
||||
@@ -280,9 +283,9 @@ function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
collapsedButtonRender={false}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${
|
||||
className={`w-full h-calc-100vh-minus-navbar ${
|
||||
currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden'
|
||||
}`}
|
||||
} ${currentUrl.startsWith('/guide/page') ? '' : 'pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T'}`}
|
||||
>
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,7 @@ class InsidePageProps {
|
||||
headerClassName?: string = ''
|
||||
/** 整个页面滚动 */
|
||||
scrollPage?: boolean = true
|
||||
customPadding?: boolean
|
||||
customBtn?: ReactNode
|
||||
}
|
||||
|
||||
@@ -41,6 +42,7 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
contentClassName = '',
|
||||
headerClassName = '',
|
||||
scrollPage = true,
|
||||
customPadding = false,
|
||||
customBtn
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
@@ -57,7 +59,7 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
{!pageTitle && !description && !backUrl && !customBtn ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="mb-[30px]">
|
||||
<div className={customPadding ? '' : 'mb-[30px]'}>
|
||||
{backUrl && (
|
||||
<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}>
|
||||
|
||||
@@ -41,6 +41,7 @@ interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<Acti
|
||||
beforeSearchNode?: React.ReactNode[]
|
||||
onSearchWordChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
afterNewBtn?: React.ReactNode[]
|
||||
beforeNewBtn?: React.ReactNode[]
|
||||
dragSortKey?: string
|
||||
onDragSortEnd?: (beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
|
||||
tableTitle?: string
|
||||
@@ -56,7 +57,8 @@ interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<Acti
|
||||
delayLoading?: boolean
|
||||
noScroll?: boolean
|
||||
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
|
||||
manualReloadTable?: () => void
|
||||
manualReloadTable?: () => void,
|
||||
customEmptyRender?: () => React.ReactNode
|
||||
}
|
||||
|
||||
const PageList = <T extends Record<string, unknown>>(
|
||||
@@ -80,6 +82,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
onSearchWordChange,
|
||||
manualReloadTable,
|
||||
afterNewBtn,
|
||||
beforeNewBtn,
|
||||
dragSortKey,
|
||||
onDragSortEnd,
|
||||
tableTitle,
|
||||
@@ -94,7 +97,8 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
tableTitleClass,
|
||||
delayLoading = true,
|
||||
besidesTableHeight,
|
||||
noScroll
|
||||
noScroll,
|
||||
customEmptyRender
|
||||
} = props
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight)
|
||||
@@ -190,6 +194,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
const headerTitle = () => {
|
||||
return (
|
||||
<>
|
||||
{beforeNewBtn ? (beforeNewBtn as React.ReactNode[]) : undefined}
|
||||
{tableTitle ? (
|
||||
<span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span>
|
||||
) : addNewBtnTitle ? (
|
||||
@@ -345,6 +350,11 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
)
|
||||
onChange?.(pagination, filters, sorter, extra)
|
||||
}}
|
||||
locale={{
|
||||
emptyText: customEmptyRender ? customEmptyRender?.() : undefined
|
||||
}}
|
||||
style={customEmptyRender ? { height: '100%' } : undefined}
|
||||
bodyStyle={customEmptyRender ? { height: '100%' } : undefined}
|
||||
rowKey={primaryKey}
|
||||
dataSource={dataSource}
|
||||
search={false}
|
||||
|
||||
@@ -148,7 +148,7 @@ const mockData = [
|
||||
access: 'system.settings.ai_provider.view'
|
||||
},
|
||||
{
|
||||
name: 'APIKey 资源池',
|
||||
name: 'API Key 负载',
|
||||
key: 'aiKeys',
|
||||
path: '/keysetting',
|
||||
icon: 'ic:baseline-key',
|
||||
@@ -162,7 +162,7 @@ const mockData = [
|
||||
access: 'system.settings.ai_api.view'
|
||||
},
|
||||
{
|
||||
name: '负载均衡',
|
||||
name: '模型灾备',
|
||||
key: 'loadBalancing',
|
||||
path: '/loadBalancing',
|
||||
icon: 'ph:network-x',
|
||||
|
||||
@@ -850,5 +850,25 @@
|
||||
"Kbe98ba9e": "Private Service",
|
||||
"K24540de": "Stop",
|
||||
"Kd85b3f64": "Continue Waiting",
|
||||
"K1400a1fc": "As a prefix for all APIs within the service, such as host/{service_name}/{api_path}. This has a significant impact, so modify with caution"
|
||||
"K1400a1fc": "As a prefix for all APIs within the service, such as host/{service_name}/{api_path}. This has a significant impact, so modify with caution",
|
||||
"Kb8185132": "OR",
|
||||
"K83829a3b": "Model Exception",
|
||||
"Kb92fb02b": "Deployment Failed",
|
||||
"Kcf8e3b18": "Tags",
|
||||
"K9ae48909": "API Key Balancing",
|
||||
"K437724fc": "Supports creating multiple API Keys for intelligent load balancing under a single API model provider",
|
||||
"K7d17707e": "Model Fallback",
|
||||
"Kc007db4a": "Only .png, .jpg, .jpeg, .svg image files are supported",
|
||||
"Kacf10c44": "Ollama Endpoint",
|
||||
"K8d4f5b44": "Input example: https://www.apipark.com",
|
||||
"K481442d3": "Configure Ollama Service",
|
||||
"Kf9b341e3": "How to deploy Ollama?",
|
||||
"K8632bef2": "Model deployment service not configured",
|
||||
"Kbbd8ce81": "Configure Service",
|
||||
"K39a8d392": "Import OpenAPI documents to publish existing system APIs to APIPark.",
|
||||
"Ka742e079": "Add API Key for public cloud AI models to call public cloud AI models via APIPark.",
|
||||
"K8097d6be": "is an open-source AI Gateway and API Portal that unifies access to OpenAI, DeepSeek, and other AI models. With enterprise-grade security features and real-time monitoring, it helps teams safely manage and share their AI APIs through a unified gateway.",
|
||||
"Kf1ce5b3": "✨ We'd love your support on Github! Leave us a star or share your feedback. ",
|
||||
"K3af90490": "⚡ You can quickly open the API for everyone to use via the following methods:",
|
||||
"K6b99dce8": "address"
|
||||
}
|
||||
|
||||
@@ -872,5 +872,25 @@
|
||||
"Kbe98ba9e": "プライベートサービス",
|
||||
"K24540de": "停止",
|
||||
"Kd85b3f64": "引き続き待機",
|
||||
"K1400a1fc": "サービス内のすべてのAPIのプレフィックスとして使用されます。例えば host/{service_name}/{api_path} のように、大きな影響を与えるため、慎重に変更してください。"
|
||||
"K1400a1fc": "サービス内のすべてのAPIのプレフィックスとして使用されます。例えば host/{service_name}/{api_path} のように、大きな影響を与えるため、慎重に変更してください。",
|
||||
"Kb8185132": "または",
|
||||
"K83829a3b": "モデル異常",
|
||||
"Kb92fb02b": "デプロイ失敗",
|
||||
"Kcf8e3b18": "タグ",
|
||||
"K9ae48909": "APIキーのペイロード",
|
||||
"K437724fc": "1つのAPIモデルプロバイダーで複数のAPIキーを作成し、インテリジェント負荷分散をサポート",
|
||||
"K7d17707e": "モデルフォールバック",
|
||||
"Kc007db4a": "画像ファイルは .png, .jpg, .jpeg, .svg 形式のみサポートされています",
|
||||
"Kacf10c44": "Ollama エンドポイント",
|
||||
"K8d4f5b44": "入力例:https://www.apipark.com",
|
||||
"K481442d3": "Ollama サービスの設定",
|
||||
"Kf9b341e3": "Ollamaのデプロイ方法は?",
|
||||
"K8632bef2": "モデルデプロイサービスが設定されていません",
|
||||
"Kbbd8ce81": "サービスの設定",
|
||||
"K39a8d392": "OpenAPIドキュメントをインポートし、既存のシステムのAPIをAPIParkに公開します。",
|
||||
"Ka742e079": "パブリッククラウドAIモデルのAPIキーを追加し、APIParkを介してパブリッククラウドのAIモデルを統一的に呼び出します。",
|
||||
"K8097d6be": "OpenAIやDeepSeekなどのさまざまなAIモデルに迅速にアクセスできるオープンソースのワンストップAIゲートウェイおよびAPIポータルです。統一されたリクエスト形式を使用して、モデルの切り替えによるビジネスへの影響を回避し、企業レベルのAPIセキュリティ(認証/レート制限/センシティブワードフィルタリング)とリアルタイムの使用量監視を提供します。チーム内でのAPI共有やコラボレーションをサポートし、インターフェースのサブスクリプション認証を管理してAPIのセキュリティを確保します。",
|
||||
"Kf1ce5b3": "✨ Githubでスターを付けていただくか、製品フィードバックをお寄せください。",
|
||||
"K3af90490": "⚡ 以下の方法で、APIをすぐに公開して皆さんに利用してもらえます:",
|
||||
"K6b99dce8": "Ollama アドレス"
|
||||
}
|
||||
|
||||
@@ -803,5 +803,25 @@
|
||||
"Kbe98ba9e": "私有服务",
|
||||
"K24540de": "停止",
|
||||
"Kd85b3f64": "继续等待",
|
||||
"K1400a1fc": "作为服务内所有API的前缀,比如host/{service_name}/{api_path},影响较大,谨慎修改"
|
||||
"K1400a1fc": "作为服务内所有API的前缀,比如host/{service_name}/{api_path},影响较大,谨慎修改",
|
||||
"Kb8185132": "或",
|
||||
"K83829a3b": "模型异常",
|
||||
"Kb92fb02b": "部署失败",
|
||||
"Kcf8e3b18": "Tags",
|
||||
"K9ae48909": "API Key 负载",
|
||||
"K437724fc": "支持单个 API 模型供应商下创建多个 API Key 进行智能负载均衡",
|
||||
"K7d17707e": "模型灾备",
|
||||
"Kc007db4a": "仅支持 .png .jpg .jpeg .svg 格式的图片文件",
|
||||
"Kacf10c44": "Ollama 端点",
|
||||
"K8d4f5b44": "输入例如:https://www.apipark.com",
|
||||
"K481442d3": "配置 Ollama 服务",
|
||||
"Kf9b341e3": "如何部署 Ollama?",
|
||||
"K8632bef2": "模型部署服务未配置",
|
||||
"Kbbd8ce81": "配置服务",
|
||||
"K39a8d392": "导入OpenAPI文档,将现有系统的API发布到APIPark。",
|
||||
"Ka742e079": "添加公有云AI模型的 API Key,通过APIPark 统一调用公有云的AI模型。",
|
||||
"K8097d6be": "是开源的一站式 AI 网关与 API 门户,可快速接入 OpenAI/DeepSeek 等各类 AI 模型,通过统一请求格式避免模型切换对业务造成影响,提供企业级 API 安全防护(鉴权/限流/敏感词过滤)与实时用量监控,支持团队内 API 共享协作,管理接口订阅授权并保证您的API安全。",
|
||||
"Kf1ce5b3": "✨ 欢迎在 Github 为我们 Star 或提供产品反馈意见。",
|
||||
"K3af90490": "⚡您可快速通过以下方式开放API供大家使用:",
|
||||
"K6b99dce8": "Ollama 地址"
|
||||
}
|
||||
|
||||
@@ -872,5 +872,25 @@
|
||||
"Kbe98ba9e": "私有服務",
|
||||
"K24540de": "停止",
|
||||
"Kd85b3f64": "繼續等待",
|
||||
"K1400a1fc": "作為服務內所有 API 的前綴,例如 host/{service_name}/{api_path},這會產生較大的影響,請謹慎修改"
|
||||
"K1400a1fc": "作為服務內所有 API 的前綴,例如 host/{service_name}/{api_path},這會產生較大的影響,請謹慎修改",
|
||||
"Kb8185132": "或",
|
||||
"K83829a3b": "模型異常",
|
||||
"Kb92fb02b": "部署失敗",
|
||||
"Kcf8e3b18": "標籤",
|
||||
"K9ae48909": "API Key 負載",
|
||||
"K437724fc": "支持在單個 API 模型供應商下創建多個 API Key 進行智能負載均衡",
|
||||
"K7d17707e": "模型災備",
|
||||
"Kc007db4a": "僅支持 .png, .jpg, .jpeg, .svg 格式的圖片文件",
|
||||
"Kacf10c44": "Ollama 端點",
|
||||
"K8d4f5b44": "輸入範例:https://www.apipark.com",
|
||||
"K481442d3": "配置 Ollama 服務",
|
||||
"Kf9b341e3": "如何部署 Ollama?",
|
||||
"K8632bef2": "模型部署服務未配置",
|
||||
"Kbbd8ce81": "配置服務",
|
||||
"K39a8d392": "導入 OpenAPI 文件,將現有系統的 API 發佈到 APIPark。",
|
||||
"Ka742e079": "添加公有雲 AI 模型的 API Key,通過 APIPark 統一調用公有雲的 AI 模型。",
|
||||
"K8097d6be": "是一個開源的一站式 AI 閘道和 API 入口網站,可快速接入 OpenAI/DeepSeek 等各類 AI 模型,通過統一的請求格式避免模型切換對業務造成影響,提供企業級 API 安全防護(鑑權/限流/敏感詞過濾)與實時用量監控,支持團隊內 API 共享協作,管理介面訂閱授權並保證您的 API 安全。",
|
||||
"Kf1ce5b3": "✨ 歡迎在 Github 為我們 Star 或提供產品反饋意見。",
|
||||
"K3af90490": "⚡ 您可以快速通過以下方式開放 API 供大家使用:",
|
||||
"K6b99dce8": "Ollama 地址"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export type AiServiceConfigFieldType = {
|
||||
name?: string;
|
||||
id?: string;
|
||||
provider?:string
|
||||
model?:string
|
||||
prefix?:string;
|
||||
logo?:string;
|
||||
logoFile?:UploadFile;
|
||||
|
||||
@@ -996,6 +996,13 @@ p{
|
||||
min-width:unset !important;
|
||||
}
|
||||
|
||||
.local-model-list .ant-pro-table .ant-table-body {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.local-model-list .ant-pro-table td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.table-border {
|
||||
.ant-table:not(.ant-table-bordered){
|
||||
border:1px solid var(--border-color) !important;
|
||||
|
||||
@@ -189,7 +189,7 @@ const AiServiceInsideRouterCreate = () => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setLlmList(data.models)
|
||||
const localId = id || aiServiceInfo?.id
|
||||
const localId = id || aiServiceInfo?.model
|
||||
|
||||
if (replaceDefaultLlm) {
|
||||
setDefaultLlm((prev) => {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react'
|
||||
import { App, Divider, Form, Space, Switch, Tag, Input } from 'antd'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { $t } from '@common/locales'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
|
||||
export type ConfigureOllamaServiceHandle = {
|
||||
save: () => Promise<boolean | string>
|
||||
}
|
||||
|
||||
const ConfigureOllamaService = forwardRef<ConfigureOllamaServiceHandle, any>((props, ref) => {
|
||||
const { address = '' } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { message } = App.useApp()
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ address })
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* 保存
|
||||
* @returns
|
||||
*/
|
||||
const save: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
fetchData<BasicResponse<null>>('model/local/source/ollama', {
|
||||
method: 'PUT',
|
||||
eoBody: { address: value.address }
|
||||
})
|
||||
.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
return (
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="partitionInsideCert"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
name="address"
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
className="p-4 bg-white rounded-lg"
|
||||
label={$t('Ollama 地址')}
|
||||
>
|
||||
<Input
|
||||
placeholder={$t('输入例如:https://www.apipark.com')}
|
||||
value={address}
|
||||
onChange={(e) => form.setFieldValue('address', e.target.value)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
)
|
||||
})
|
||||
|
||||
export default ConfigureOllamaService
|
||||
@@ -4,13 +4,15 @@ import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPe
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import { App, Divider, Form, Space, Switch, Tag } from 'antd'
|
||||
import { App, Divider, Form, Space, Switch, Tag, Button } from 'antd'
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { ModelListData } from './types'
|
||||
import LocalAiDeploy, { LocalAiDeployHandle } from '../guide/LocalAiDeploy'
|
||||
import { ServiceDeployment } from '../system/serviceDeployment/ServiceDeployment'
|
||||
import { LogsFooter } from '../system/serviceDeployment/ServiceDeployMentFooter'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import ConfigureOllamaService, { ConfigureOllamaServiceHandle } from './ConfigureOllamaService'
|
||||
type EditLocalModelModalHandle = {
|
||||
save: () => Promise<boolean | string>
|
||||
}
|
||||
@@ -18,20 +20,21 @@ type EditLocalModelModalProps = {
|
||||
enable: boolean
|
||||
modelID?: string
|
||||
}
|
||||
const EditLocalModelModal = forwardRef<EditLocalModelModalHandle, EditLocalModelModalProps>((props: EditLocalModelModalProps, ref) => {
|
||||
const { enable, modelID } = props
|
||||
const { fetchData } = useFetch()
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
const [currentStatus, setCurrentStatus] = useState<boolean>(enable)
|
||||
const EditLocalModelModal = forwardRef<EditLocalModelModalHandle, EditLocalModelModalProps>(
|
||||
(props: EditLocalModelModalProps, ref) => {
|
||||
const { enable, modelID } = props
|
||||
const { fetchData } = useFetch()
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
const [currentStatus, setCurrentStatus] = useState<boolean>(enable)
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ enable })
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ enable })
|
||||
}, [])
|
||||
/**
|
||||
* 保存
|
||||
* @returns
|
||||
*/
|
||||
* 保存
|
||||
* @returns
|
||||
*/
|
||||
const save: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
@@ -41,22 +44,23 @@ const EditLocalModelModal = forwardRef<EditLocalModelModalHandle, EditLocalModel
|
||||
const finalValue = {
|
||||
disable: !value.enable
|
||||
}
|
||||
|
||||
fetchData<BasicResponse<null>>('model/local/info', {
|
||||
method: 'PUT',
|
||||
eoParams: { model: modelID },
|
||||
eoBody: finalValue,
|
||||
})
|
||||
.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))
|
||||
|
||||
fetchData<BasicResponse<null>>('model/local/info', {
|
||||
method: 'PUT',
|
||||
eoParams: { model: modelID },
|
||||
eoBody: finalValue
|
||||
})
|
||||
.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) {
|
||||
@@ -68,40 +72,41 @@ const EditLocalModelModal = forwardRef<EditLocalModelModalHandle, EditLocalModel
|
||||
save
|
||||
}))
|
||||
|
||||
return (
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="partitionInsideCert"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item className="p-4 bg-white rounded-lg" label={$t('LLM 状态管理')}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<span className="text-gray-600">{$t('当前调用状态:')}</span>
|
||||
{currentStatus && <Tag color="success">{$t('正常')}</Tag>}
|
||||
{!currentStatus && <Tag color="warning">{$t('停用')}</Tag>}
|
||||
</div>
|
||||
<Form.Item name="enable" valuePropName="checked" noStyle>
|
||||
<Switch
|
||||
checkedChildren={$t('启用')}
|
||||
unCheckedChildren={$t('停用')}
|
||||
onChange={(checked) => {
|
||||
form.setFieldsValue({ enable: checked })
|
||||
setCurrentStatus(checked)
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="partitionInsideCert"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item className="p-4 bg-white rounded-lg" label={$t('LLM 状态管理')}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<span className="text-gray-600">{$t('当前调用状态:')}</span>
|
||||
{currentStatus && <Tag color="success">{$t('正常')}</Tag>}
|
||||
{!currentStatus && <Tag color="warning">{$t('停用')}</Tag>}
|
||||
</div>
|
||||
<Form.Item name="enable" valuePropName="checked" noStyle>
|
||||
<Switch
|
||||
checkedChildren={$t('启用')}
|
||||
unCheckedChildren={$t('停用')}
|
||||
onChange={(checked) => {
|
||||
form.setFieldsValue({ enable: checked })
|
||||
setCurrentStatus(checked)
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const LocalModelList: React.FC = () => {
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
@@ -109,6 +114,7 @@ const LocalModelList: React.FC = () => {
|
||||
const { fetchData } = useFetch()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const localAiDeployRef = useRef<LocalAiDeployHandle>()
|
||||
const ConfigureOllamaServiceRef = useRef<ConfigureOllamaServiceHandle>()
|
||||
const EditLocalModelModalRef = useRef<EditLocalModelModalHandle>()
|
||||
const [stateColumnMap] = useState<{ [k: string]: { text: string; className?: string } }>({
|
||||
normal: { text: '正常' },
|
||||
@@ -118,10 +124,84 @@ const LocalModelList: React.FC = () => {
|
||||
deploying_error: { text: '部署失败', className: 'text-[#ff4d4f] cursor-pointer' }
|
||||
})
|
||||
|
||||
const [ollamaAddress, setOllamaAddress] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
getOllamaData()
|
||||
}, [])
|
||||
|
||||
const configureService = (address?: string) => {
|
||||
modal.confirm({
|
||||
title: $t('配置 Ollama 服务'),
|
||||
content: (
|
||||
<ConfigureOllamaService ref={ConfigureOllamaServiceRef} address={address}></ConfigureOllamaService>
|
||||
),
|
||||
onOk: () => {
|
||||
return ConfigureOllamaServiceRef.current?.save().then((res) => {
|
||||
if (res === true) {
|
||||
getOllamaData()
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
})
|
||||
},
|
||||
footer: (_, { OkBtn, CancelBtn }) => {
|
||||
return (
|
||||
<div className="flex justify-between items-center">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://ollama.com/download/linux"
|
||||
className="flex items-center gap-[8px]"
|
||||
>
|
||||
<span>{$t('如何部署 Ollama?')}</span>
|
||||
</a>
|
||||
<div>
|
||||
<CancelBtn />
|
||||
<OkBtn />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
const customEmptyRender = () => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Icon className="align-sub mr-[5px]" icon="ph:hard-drives-light" width="50" height="50" />
|
||||
<div>{$t('模型部署服务未配置')}</div>
|
||||
<Button type="primary" className="mt-[10px]" onClick={() => configureService()}>
|
||||
{$t('配置服务')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const getOllamaData = async () => {
|
||||
const response = await fetchData<BasicResponse<{ data: any[] }>>('model/local/source/ollama', {
|
||||
method: 'GET'
|
||||
})
|
||||
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
setOllamaAddress(response.data?.config?.address || '')
|
||||
} else {
|
||||
message.error(response.msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (record: ModelListData) => {
|
||||
modal.confirm({
|
||||
title: $t('模型设置'),
|
||||
content: <EditLocalModelModal ref={EditLocalModelModalRef} modelID={record.id} enable={record.state !== 'disabled'}/>,
|
||||
content: (
|
||||
<EditLocalModelModal ref={EditLocalModelModalRef} modelID={record.id} enable={record.state !== 'disabled'} />
|
||||
),
|
||||
onOk: () => {
|
||||
return EditLocalModelModalRef.current?.save().then((res) => {
|
||||
if (res === true) {
|
||||
@@ -243,7 +323,7 @@ const LocalModelList: React.FC = () => {
|
||||
{
|
||||
title: '',
|
||||
key: 'option',
|
||||
btnNums: 4,
|
||||
btnNums: 2,
|
||||
fixed: 'right',
|
||||
valueType: 'option',
|
||||
render: (_: React.ReactNode, entity: ModelListData) => [
|
||||
@@ -283,7 +363,9 @@ const LocalModelList: React.FC = () => {
|
||||
}
|
||||
const modalInstance = modal.confirm({
|
||||
title: $t('部署过程'),
|
||||
content: <ServiceDeployment record={record} closeModal={closeModal} updateFooter={updateFooter} cancelCb={cancel} />,
|
||||
content: (
|
||||
<ServiceDeployment record={record} closeModal={closeModal} updateFooter={updateFooter} cancelCb={cancel} />
|
||||
),
|
||||
footer: () => {
|
||||
return <LogsFooter record={record} closeModal={closeModal} />
|
||||
},
|
||||
@@ -319,13 +401,14 @@ const LocalModelList: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{stateColumnMap[entity?.state as string]?.text || '-'}
|
||||
{$t(stateColumnMap[entity?.state as string]?.text || '-')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: $t('Apis'),
|
||||
dataIndex: 'apiCount',
|
||||
width: 100,
|
||||
render: (dom: React.ReactNode, record: ModelListData) => (
|
||||
<span className="[&>.key-link]:text-[#2196f3] cursor-pointer">
|
||||
<a
|
||||
@@ -351,11 +434,16 @@ const LocalModelList: React.FC = () => {
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
rowKey="id"
|
||||
tableClass="local-model-list"
|
||||
customEmptyRender={customEmptyRender}
|
||||
request={requestList}
|
||||
onSearchWordChange={(e) => {
|
||||
setSearchWord(e.target.value)
|
||||
pageListRef.current?.reload()
|
||||
}}
|
||||
beforeNewBtn={
|
||||
[<Button className="mr-btnbase" key="removeFromDep" onClick={() => configureService(ollamaAddress)}>{$t('配置服务')}</Button>]
|
||||
}
|
||||
showPagination={true}
|
||||
searchPlaceholder={$t('请输入名称搜索')}
|
||||
columns={columns}
|
||||
|
||||
@@ -6,16 +6,18 @@ import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { App } from 'antd'
|
||||
import { Card } from 'antd'
|
||||
import { useRef } from 'react'
|
||||
import { useEffect, useRef, useState } 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'
|
||||
import useDeployLocalModel from './deployModelUtil'
|
||||
import RestAIDeploy, { RestAIDeployHandle } from './RestAIDeploy'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
|
||||
export const AIModelGuide = () => {
|
||||
const { modal } = App.useApp()
|
||||
const { message, modal } = App.useApp()
|
||||
const entityData = useRef<any>(null)
|
||||
const navigateTo = useNavigate()
|
||||
const { accessData } = useGlobalContext()
|
||||
@@ -23,6 +25,8 @@ export const AIModelGuide = () => {
|
||||
const localAiDeployRef = useRef<LocalAiDeployHandle>()
|
||||
const restAiDeployRef = useRef<RestAIDeployHandle>()
|
||||
const { deployLocalModel } = useDeployLocalModel()
|
||||
const { fetchData } = useFetch()
|
||||
const [ollamaAddress, setOllamaAddress] = useState<string>('')
|
||||
|
||||
const dumpServerPage = () => {
|
||||
navigateTo('/service/list')
|
||||
@@ -105,10 +109,31 @@ export const AIModelGuide = () => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const getOllamaData = async () => {
|
||||
const response = await fetchData<BasicResponse<{ data: any[] }>>('model/local/source/ollama', {
|
||||
method: 'GET'
|
||||
})
|
||||
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
setOllamaAddress(response.data?.config?.address || '')
|
||||
} else {
|
||||
message.error(response.msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getOllamaData()
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* 本地部署 AI 并生成 API
|
||||
*/
|
||||
const localModelCardClick = async () => {
|
||||
if (!ollamaAddress) {
|
||||
navigateTo('/aisetting?status=unconfigure')
|
||||
return
|
||||
}
|
||||
const modalInstance = modal.confirm({
|
||||
title: $t('部署本地模型'),
|
||||
content: <LocalAiDeploy ref={localAiDeployRef} onClose={() => {
|
||||
@@ -131,6 +156,10 @@ export const AIModelGuide = () => {
|
||||
}
|
||||
const deployDeepSeek = async (e: any) => {
|
||||
e.stopPropagation()
|
||||
if (!ollamaAddress) {
|
||||
navigateTo('/aisetting?status=unconfigure')
|
||||
return
|
||||
}
|
||||
await deployLocalModel({
|
||||
modelID: 'deepseek-r1'
|
||||
})
|
||||
@@ -141,13 +170,13 @@ export const AIModelGuide = () => {
|
||||
{
|
||||
imgSrc: restAPIPic,
|
||||
title: $t('添加 Rest 服务'),
|
||||
description: $t('支持批量添加现有 API 文档以实现统一的外部访问。'),
|
||||
description: $t('导入OpenAPI文档,将现有系统的API发布到APIPark。'),
|
||||
click: restCardClick
|
||||
},
|
||||
{
|
||||
imgSrc: onlineAIPic,
|
||||
title: $t('添加在线 AI API'),
|
||||
description: $t('快速调用 AI 模型的云服务 API,方便管理提示词和统一计费。'),
|
||||
description: $t('添加公有云AI模型的 API Key,通过APIPark 统一调用公有云的AI模型。'),
|
||||
click: aiCardClick
|
||||
},
|
||||
{
|
||||
@@ -164,7 +193,9 @@ export const AIModelGuide = () => {
|
||||
}
|
||||
]
|
||||
return (
|
||||
<div className="mb-[30px] pt-[15px] flex justify-between space-x-4">
|
||||
<>
|
||||
<p>{$t('⚡您可快速通过以下方式开放API供大家使用:')}</p>
|
||||
<div className="mb-[30px] pt-[25px] flex justify-between space-x-4">
|
||||
{cardList.map((item, itemIndex) => (
|
||||
<Card
|
||||
key={itemIndex}
|
||||
@@ -182,5 +213,6 @@ export const AIModelGuide = () => {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,12 +151,13 @@ export default function Guide() {
|
||||
description={
|
||||
<div className="flex flex-col gap-[8px]">
|
||||
<p>
|
||||
<span className="font-bold">🦄 APIPark </span>
|
||||
{$t(
|
||||
'你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审核流程等诸多好处。'
|
||||
'是开源的一站式 AI 网关与 API 门户,可快速接入 OpenAI/DeepSeek 等各类 AI 模型,通过统一请求格式避免模型切换对业务造成影响,提供企业级 API 安全防护(鉴权/限流/敏感词过滤)与实时用量监控,支持团队内 API 共享协作,管理接口订阅授权并保证您的API安全。'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{$t('如果你喜欢我们的产品,欢迎给我们 Star 或提供产品反馈意见。')}
|
||||
{$t('✨ 欢迎在 Github 为我们 Star 或提供产品反馈意见。')}
|
||||
<span className="font-bold">
|
||||
{$t('点击这里')}
|
||||
<span className="align-middle leading-[16px]">
|
||||
@@ -184,7 +185,9 @@ export default function Guide() {
|
||||
}
|
||||
showBorder={false}
|
||||
scrollPage={false}
|
||||
contentClassName=" w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B"
|
||||
customPadding={true}
|
||||
headerClassName="pt-[30px] pl-[40px]"
|
||||
contentClassName=" w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B pl-[40px]"
|
||||
>
|
||||
<AIModelGuide></AIModelGuide>
|
||||
<div className="flex flex-col gap-[15px]">
|
||||
|
||||
@@ -114,7 +114,7 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
name="partitionInsideCert"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item label={$t('模型供应商')} name="provider" rules={[{ required: true }]}>
|
||||
<Form.Item label={$t('模型')} name="provider" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
className="w-INPUT_NORMAL"
|
||||
@@ -158,8 +158,8 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
: null}
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('默认模型')} name="model" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
<Form.Item label={$t('Tags')} name="model" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
className="w-INPUT_NORMAL"
|
||||
filterOption={(input, option) => (option?.searchText ?? '').includes(input.toLowerCase())}
|
||||
@@ -168,7 +168,7 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
label: (
|
||||
<div className="relative">
|
||||
<span>{provider.name}</span>
|
||||
{ provider.size && <span className="absolute right-[10px] text-[#999]">{provider.size}</span> }
|
||||
{provider.size && <span className="absolute right-[10px] text-[#999]">{provider.size}</span>}
|
||||
</div>
|
||||
),
|
||||
value: provider.id,
|
||||
|
||||
@@ -338,10 +338,10 @@ const KeySettings: React.FC = () => {
|
||||
return (
|
||||
<InsidePage
|
||||
className="overflow-y-auto gap-4 pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X"
|
||||
pageTitle={$t('APIKey 资源池')}
|
||||
pageTitle={$t('API Key 负载')}
|
||||
description={
|
||||
<>
|
||||
{$t('支持单个 API 模型供应商下创建多个 APIKey APIKey 进行智能负载均衡')}
|
||||
{$t('支持单个 API 模型供应商下创建多个 API Key 进行智能负载均衡')}
|
||||
<div className="mt-4">
|
||||
<AIProviderSelect
|
||||
value={selectedProvider}
|
||||
|
||||
@@ -28,7 +28,7 @@ const LoadBalancingPage = () => {
|
||||
const { fetchData } = useFetch()
|
||||
const addModel = () => {
|
||||
modal.confirm({
|
||||
title: $t('添加负载均衡'),
|
||||
title: $t('添加模型'),
|
||||
content: <AddLoadBalancingModel ref={addModelRef} />,
|
||||
width: 600,
|
||||
closable: true,
|
||||
@@ -267,7 +267,7 @@ const LoadBalancingPage = () => {
|
||||
return (
|
||||
<>
|
||||
<InsidePage
|
||||
pageTitle={$t('负载均衡')}
|
||||
pageTitle={$t('模型灾备')}
|
||||
description={$t(
|
||||
'系统自动识别异常AI模型后,自动替换成以下优先级最高的可用模型。这将确保您的AI应用保持高可用性和最佳性能,从而防止任何单个LLM异常成为您的性能瓶颈。'
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import {
|
||||
BasicResponse,
|
||||
DELETE_TIPS,
|
||||
PLACEHOLDER,
|
||||
RESPONSE_TIPS,
|
||||
STATUS_CODE
|
||||
} from '@common/const/const.tsx'
|
||||
import { BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem, MemberItem, SimpleTeamItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
@@ -50,15 +44,10 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
const [tagOptionList, setTagOptionList] = useState<DefaultOptionType[]>([])
|
||||
const [serviceClassifyOptionList, setServiceClassifyOptionList] = useState<DefaultOptionType[]>()
|
||||
const [uploadLoading, setUploadLoading] = useState<boolean>(false)
|
||||
const {
|
||||
checkPermission,
|
||||
accessInit,
|
||||
getGlobalAccessData,
|
||||
state,
|
||||
aiConfigFlushed,
|
||||
setAiConfigFlushed
|
||||
} = useGlobalContext()
|
||||
const { checkPermission, accessInit, getGlobalAccessData, state, aiConfigFlushed, setAiConfigFlushed } =
|
||||
useGlobalContext()
|
||||
const [providerOptionList, setProviderOptionList] = useState<DefaultOptionType[]>()
|
||||
const [modelList, setModelList] = useState<DefaultOptionType[]>()
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
|
||||
@@ -77,18 +66,80 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
fetchData<BasicResponse<{ providers: SimpleAiProviderItem[] }>>('simple/ai/providers/configured', {
|
||||
method: 'GET',
|
||||
eoTransformKeys: [],
|
||||
eoParams: { all: true}
|
||||
}).then(response => {
|
||||
eoParams: { all: true }
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const configuredProvider = data.providers
|
||||
?.filter(x => x.configured)
|
||||
?.filter((x) => x.configured)
|
||||
?.map((x: SimpleAiProviderItem) => {
|
||||
return { ...x, label: x.name, value: x.id }
|
||||
})
|
||||
setProviderOptionList(configuredProvider)
|
||||
if (!serviceId && configuredProvider.length > 0) {
|
||||
form.setFieldsValue({ provider: configuredProvider[0]?.id })
|
||||
if (configuredProvider[0]?.type === 'local') {
|
||||
getLocalModelList()
|
||||
} else {
|
||||
getOnlineModelList(configuredProvider[0]?.id)
|
||||
}
|
||||
}
|
||||
if (serviceId && configuredProvider.length > 0) {
|
||||
const providerID = form.getFieldValue('provider')
|
||||
const provider = configuredProvider?.find((item: any) => item.id === providerID)
|
||||
if (provider?.type === 'local') {
|
||||
getLocalModelList(false)
|
||||
} else {
|
||||
getOnlineModelList(provider?.id, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const modelProviderChange = (id: string) => {
|
||||
const provider = providerOptionList?.find((item) => item.id === id)
|
||||
if (provider?.type === 'local') {
|
||||
getLocalModelList()
|
||||
} else {
|
||||
getOnlineModelList(provider?.id)
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalModelList = (setDefaultLlm = true) => {
|
||||
fetchData<BasicResponse<{ providers: any[] }>>('simple/ai/models/local/configured', {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const localModelList = data.models?.map((x: any) => {
|
||||
return { ...x, label: x.name, value: x.id }
|
||||
})
|
||||
setModelList(localModelList)
|
||||
if (setDefaultLlm && localModelList.length > 0) {
|
||||
form.setFieldsValue({ model: localModelList[0]?.id })
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
const getOnlineModelList = (id: string, setDefaultLlm = true) => {
|
||||
fetchData<BasicResponse<{ providers: any[] }>>('ai/provider/llms', {
|
||||
method: 'GET',
|
||||
eoParams: { provider: id },
|
||||
eoTransformKeys: ['default_llm']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const localModelList = data.llms?.map((x: any) => {
|
||||
return { ...x, label: x.id, value: x.id }
|
||||
})
|
||||
setModelList(localModelList)
|
||||
if (setDefaultLlm && localModelList.length > 0) {
|
||||
form.setFieldsValue({ model: localModelList[0]?.id })
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
@@ -137,9 +188,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
}
|
||||
|
||||
const uploadButton = (
|
||||
<div>
|
||||
{uploadLoading ? <LoadingOutlined /> : <Icon icon="ic:baseline-add" width="24" height="24" />}
|
||||
</div>
|
||||
<div>{uploadLoading ? <LoadingOutlined /> : <Icon icon="ic:baseline-add" width="24" height="24" />}</div>
|
||||
)
|
||||
|
||||
const getTagAndServiceClassifyList = () => {
|
||||
@@ -147,7 +196,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
setServiceClassifyOptionList([])
|
||||
fetchData<BasicResponse<{ catalogues: CategorizesType[]; tags: EntityItem[] }>>('catalogues', {
|
||||
method: 'GET'
|
||||
}).then(response => {
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTagOptionList(
|
||||
@@ -175,7 +224,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
method: 'GET',
|
||||
eoParams: { team: teamId, service: serviceId },
|
||||
eoTransformKeys: ['team_id', 'service_type', 'approval_type', 'service_kind']
|
||||
}).then(response => {
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTimeout(() => {
|
||||
@@ -196,6 +245,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
})
|
||||
setImageBase64(data.service.logo)
|
||||
setShowClassify(data.service.serviceType === 'public')
|
||||
getProviderOptionList()
|
||||
}, 0)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
@@ -204,21 +254,19 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
}
|
||||
|
||||
const onFinish: () => Promise<boolean | string> = () => {
|
||||
return form.validateFields().then(value => {
|
||||
return form.validateFields().then((value) => {
|
||||
return fetchData<BasicResponse<{ service: { id: string } }>>(
|
||||
serviceId === undefined ? 'team/service' : 'service/info',
|
||||
{
|
||||
method: serviceId === undefined ? 'POST' : 'PUT',
|
||||
eoParams: {
|
||||
...(serviceId === undefined
|
||||
? { team: value.team }
|
||||
: { service: serviceId, team: teamId })
|
||||
...(serviceId === undefined ? { team: value.team } : { service: serviceId, team: teamId })
|
||||
},
|
||||
eoBody: { ...value, prefix: value.prefix?.trim() },
|
||||
eoTransformKeys: ['serviceType', 'approvalType', 'serviceKind']
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
@@ -229,7 +277,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch(errorInfo => {
|
||||
.catch((errorInfo) => {
|
||||
return Promise.reject(errorInfo)
|
||||
})
|
||||
})
|
||||
@@ -241,7 +289,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(
|
||||
!checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams',
|
||||
{ method: 'GET', eoTransformKeys: [] }
|
||||
).then(response => {
|
||||
).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTeamOptionList(
|
||||
@@ -262,7 +310,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
fetchData<BasicResponse<null>>('team/service', {
|
||||
method: 'DELETE',
|
||||
eoParams: { team: teamId, service: serviceId }
|
||||
}).then(response => {
|
||||
}).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
@@ -285,7 +333,6 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
getTeamOptionList()
|
||||
})
|
||||
}
|
||||
getProviderOptionList()
|
||||
getTagAndServiceClassifyList()
|
||||
if (serviceId !== undefined) {
|
||||
setOnEdit(true)
|
||||
@@ -299,6 +346,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
}
|
||||
])
|
||||
} else {
|
||||
getProviderOptionList()
|
||||
setOnEdit(false)
|
||||
const id = uuidv4()
|
||||
form.setFieldValue('id', id)
|
||||
@@ -333,7 +381,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
// const serviceTypeOptions = useMemo(()=>SERVICE_KIND_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language]);
|
||||
// const visualizationOptions = useMemo(()=>SERVICE_VISUALIZATION_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language])
|
||||
const approvalOptions = useMemo(
|
||||
() => SERVICE_APPROVAL_OPTIONS.map(x => ({ ...x, label: $t(x.label) })),
|
||||
() => SERVICE_APPROVAL_OPTIONS.map((x) => ({ ...x, label: $t(x.label) })),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
@@ -364,21 +412,13 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
name="id"
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={onEdit}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
/>
|
||||
<Input className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
{!onEdit && (
|
||||
<Form.Item<SystemConfigFieldType>
|
||||
label={$t('服务类型')}
|
||||
name="serviceKind"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Form.Item<SystemConfigFieldType> label={$t('服务类型')} name="serviceKind" rules={[{ required: true }]}>
|
||||
<Radio.Group
|
||||
disabled={onEdit}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setShowAI(e.target.value === 'ai')
|
||||
}}
|
||||
>
|
||||
@@ -398,39 +438,40 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
</Form.Item>
|
||||
)}
|
||||
{showAI && (
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t('默认 AI 供应商')}
|
||||
name="provider"
|
||||
rules={[{ required: true }]}
|
||||
extra={
|
||||
serviceId
|
||||
? $t('创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API')
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{providerOptionList && providerOptionList.length > 0 ? (
|
||||
<Select
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={providerOptionList}
|
||||
></Select>
|
||||
) : (
|
||||
<p>
|
||||
{$t('未配置任何 AI 模型供应商,')}
|
||||
<a href="/aisetting" target="_blank" onClick={() => setAiConfigFlushed(false)}>
|
||||
{$t('立即配置')}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</Form.Item>
|
||||
<>
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t('默认 AI 供应商')}
|
||||
name="provider"
|
||||
rules={[{ required: true }]}
|
||||
extra={serviceId ? $t('创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API') : ''}
|
||||
>
|
||||
{providerOptionList && providerOptionList.length > 0 ? (
|
||||
<Select
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={providerOptionList}
|
||||
onChange={(e) => {
|
||||
modelProviderChange(e)
|
||||
}}
|
||||
></Select>
|
||||
) : (
|
||||
<p>
|
||||
{$t('未配置任何 AI 模型供应商,')}
|
||||
<a href="/aisetting" target="_blank" onClick={() => setAiConfigFlushed(false)}>
|
||||
{$t('立即配置')}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item<AiServiceConfigFieldType> label={$t('默认模型')} name="model" rules={[{ required: true }]}>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={modelList}></Select>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Form.Item<SystemConfigFieldType>
|
||||
label={$t('API 调用前缀')}
|
||||
name="prefix"
|
||||
extra={$t(
|
||||
'作为服务内所有API的前缀,比如host/{service_name}/{api_path},影响较大,谨慎修改'
|
||||
)}
|
||||
extra={$t('作为服务内所有API的前缀,比如host/{service_name}/{api_path},影响较大,谨慎修改')}
|
||||
rules={[
|
||||
{ required: true, whitespace: true },
|
||||
{
|
||||
@@ -438,18 +479,10 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={onEdit ? '' : '/'}
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
/>
|
||||
<Input prefix={onEdit ? '' : '/'} className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
{!onEdit && (
|
||||
<Form.Item<SystemConfigFieldType>
|
||||
label={$t('所属团队')}
|
||||
name="team"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Form.Item<SystemConfigFieldType> label={$t('所属团队')} name="team" rules={[{ required: true }]}>
|
||||
<Select
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={onEdit}
|
||||
@@ -459,11 +492,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item<SystemConfigFieldType>
|
||||
label={$t('订阅审核')}
|
||||
name="approvalType"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Form.Item<SystemConfigFieldType> label={$t('订阅审核')} name="approvalType" rules={[{ required: true }]}>
|
||||
<Radio.Group className="flex flex-col" options={approvalOptions} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -498,7 +527,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
<Form.Item<SystemConfigFieldType>
|
||||
label={$t('图标')}
|
||||
name="logoFile"
|
||||
extra={$t('仅支持 .png .jpg .jpeg .svg 格式的图片文件, 大于 1KB 的文件将被压缩')}
|
||||
extra={$t('仅支持 .png .jpg .jpeg .svg 格式的图片文件')}
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={normFile}
|
||||
>
|
||||
@@ -515,11 +544,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
style={{ marginTop: 8 }}
|
||||
>
|
||||
{imageBase64 ? (
|
||||
<img
|
||||
src={imageBase64}
|
||||
alt="Logo"
|
||||
style={{ maxWidth: '200px', width: '68px', height: '68px' }}
|
||||
/>
|
||||
<img src={imageBase64} alt="Logo" style={{ maxWidth: '200px', width: '68px', height: '68px' }} />
|
||||
) : (
|
||||
uploadButton
|
||||
)}
|
||||
|
||||
@@ -68,12 +68,12 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => {
|
||||
<div className="my-[10px]">{$t('可通过以下 URL 或 下载 Json 文件,导入 API 文档数据到 Agent 平台中。')}</div>
|
||||
<div className="flex w-full items-center gap-[30px]">
|
||||
<Space.Compact className="w-[700px]">
|
||||
<Input className="truncate" disabled title={url} value={url} />
|
||||
<Input className="truncate" readOnly title={url} value={url} />
|
||||
<Button type="primary" onClick={copyURL}>
|
||||
{$t('复制 URL')}
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<span className="text-[14px] font-bold">OR</span>
|
||||
<span className="text-[14px] font-bold">{$t('或')}</span>
|
||||
<Button href={`/api/v1/export/openapi/${serviceId}`} target="_blank">
|
||||
{$t('下载 Json 文件')}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user