mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-12 18:11:34 +08:00
feat: feature/1.5-Data Integration
This commit is contained in:
@@ -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,24 @@
|
||||
"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:"
|
||||
}
|
||||
|
||||
@@ -872,5 +872,24 @@
|
||||
"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をすぐに公開して皆さんに利用してもらえます:"
|
||||
}
|
||||
|
||||
@@ -803,5 +803,24 @@
|
||||
"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供大家使用:"
|
||||
}
|
||||
|
||||
@@ -872,5 +872,24 @@
|
||||
"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 供大家使用:"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
eoParams: { 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,19 +114,94 @@ 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: '正常' },
|
||||
deploying: { text: '部署中', className: 'text-[#2196f3] cursor-pointer' },
|
||||
error: { text: '模型异常', className: 'text-[#ff4d4f]' },
|
||||
disabled: { text: '停用' },
|
||||
deploying_error: { text: '部署失败', className: 'text-[#ff4d4f] cursor-pointer' }
|
||||
normal: { text: $t('正常') },
|
||||
deploying: { text: $t('部署中'), className: 'text-[#2196f3] cursor-pointer' },
|
||||
error: { text: $t('模型异常'), className: 'text-[#ff4d4f]' },
|
||||
disabled: { text: $t('停用') },
|
||||
deploying_error: { text: $t('部署失败'), 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} />
|
||||
},
|
||||
@@ -326,6 +408,7 @@ const LocalModelList: React.FC = () => {
|
||||
{
|
||||
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
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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]">
|
||||
@@ -180,6 +181,7 @@ export default function Guide() {
|
||||
Star
|
||||
</span>
|
||||
</p>
|
||||
<p>{$t('⚡您可快速通过以下方式开放API供大家使用:')}</p>
|
||||
</div>
|
||||
}
|
||||
showBorder={false}
|
||||
|
||||
@@ -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"
|
||||
@@ -135,27 +135,6 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
getLocalModelList(value)
|
||||
}}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('模型')} name="model" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
className="w-INPUT_NORMAL"
|
||||
filterOption={(input, option) => (option?.searchText ?? '').includes(input.toLowerCase())}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={tagList.map((provider) => ({
|
||||
label: (
|
||||
<div className="relative">
|
||||
<span>{provider.name}</span>
|
||||
{provider.size && <span className="absolute right-[10px] text-[#999]">{provider.size}</span>}
|
||||
</div>
|
||||
),
|
||||
value: provider.id,
|
||||
searchText: provider.name.toLowerCase()
|
||||
}))}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('model', value)
|
||||
}}
|
||||
></Select>
|
||||
<div className="mt-[10px] mb-[5px]">
|
||||
<Icon className="align-text-top" icon="noto-v1:fire" width="17" height="17" />
|
||||
{$t('热点模型')}
|
||||
@@ -179,6 +158,27 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
: null}
|
||||
</div>
|
||||
</Form.Item>
|
||||
<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())}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={tagList.map((provider) => ({
|
||||
label: (
|
||||
<div className="relative">
|
||||
<span>{provider.name}</span>
|
||||
{provider.size && <span className="absolute right-[10px] text-[#999]">{provider.size}</span>}
|
||||
</div>
|
||||
),
|
||||
value: provider.id,
|
||||
searchText: provider.name.toLowerCase()
|
||||
}))}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('model', value)
|
||||
}}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
className="w-INPUT_NORMAL"
|
||||
|
||||
@@ -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异常成为您的性能瓶颈。'
|
||||
)}
|
||||
|
||||
@@ -498,7 +498,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}
|
||||
>
|
||||
|
||||
@@ -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