diff --git a/frontend/packages/common/src/assets/localAI.svg b/frontend/packages/common/src/assets/localAI.svg new file mode 100644 index 00000000..ea2ac931 --- /dev/null +++ b/frontend/packages/common/src/assets/localAI.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/packages/common/src/assets/onlineAI.svg b/frontend/packages/common/src/assets/onlineAI.svg new file mode 100644 index 00000000..008a606f --- /dev/null +++ b/frontend/packages/common/src/assets/onlineAI.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/packages/common/src/assets/restAPI.svg b/frontend/packages/common/src/assets/restAPI.svg new file mode 100644 index 00000000..680b98f3 --- /dev/null +++ b/frontend/packages/common/src/assets/restAPI.svg @@ -0,0 +1,14 @@ + + + + + + REST + diff --git a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx index 931bf7f0..471d78b5 100644 --- a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx +++ b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx @@ -182,7 +182,7 @@ function BasicLayout({ project = 'core' }: { project: string }) { , ...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[]) || []) ] - }, [pluginSlotHub.getSlot('basicLayoutAfterBtns')]) + }, [state.language, pluginSlotHub.getSlot('basicLayoutAfterBtns')]) return (
[] = [ dataIndex: ['team', 'name'], ellipsis: true }, + { + title: '状态', + width: 140, + dataIndex: 'update_time', + // dataIndex: 'state', + ellipsis: true + }, { title: 'API 数量', dataIndex: 'apiNum', diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx index a81a4c7d..f0df517d 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx @@ -4,31 +4,50 @@ import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/ import { useFetch } from '@common/hooks/http' import { $t } from '@common/locales' import { App, Form, InputNumber, Select, Switch, Tag, Tooltip } from 'antd' -import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' -import { AiProviderLlmsItems, ModelDetailData } from './types' +import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react' +import { AiProviderLlmsItems, ModelDetailData, AiSettingListItem } from './types' +import { MemberItem, SimpleTeamItem } from '@common/const/type' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' export type AiSettingModalContentProps = { entity: ModelDetailData & { defaultLlm: string } readOnly: boolean + modelMode?: 'auto' | 'manual' + /** 如果是手动选择 AI 模型,那么需要更新 footer 底部的内容,所以需要这个方法去更新外部的 footer */ + updateEntityData: (entity: ModelDetailData & { defaultLlm: string }) => void } export type AiSettingModalContentHandle = { save: () => Promise + deployAIServer: () => Promise } const AiSettingModalContent = forwardRef((props, ref) => { const [form] = Form.useForm() const { message } = App.useApp() - const { entity, readOnly } = props + const { entity, readOnly, modelMode = 'auto', updateEntityData } = props const { fetchData } = useFetch() const [llmList, setLlmList] = useState() const [loading, setLoading] = useState(false) - const [enableState, setEnableState] = useState(entity.status === 'enabled') - const getLlmList = () => { + // AI 模型配置 + const [localEntity, setLocalEntity] = useState(entity) + const [teamList, setTeamList] = useState([]) + // AI 模型提供商列表 + const modelProviderListRef = useRef([]) + // 模型模式加载 + const [modelModeLoading, setModelModeLoading] = useState(false) + const [enableState, setEnableState] = useState(localEntity?.status === 'enabled') + const { checkPermission } = useGlobalContext() + + /** + * 获取 llm 列表 + * @param id + */ + const getLlmList = (id?: string) => { setLoading(true) fetchData>(`ai/provider/llms`, { method: 'GET', - eoParams: { provider: entity.id } + eoParams: { provider: id || localEntity.id } }) .then((response) => { const { code, data, msg } = response @@ -43,25 +62,159 @@ const AiSettingModalContent = forwardRef { - getLlmList() + /** + * 获取团队选项列表 + * @returns + */ + const getTeamOptionList = async (): any[] => { + const response = await fetchData>( + !checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams', + { method: 'GET', eoTransformKeys: [] } + ) + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const teamOptionList = data.teams?.map((x: MemberItem) => { + return { ...x, label: x.name, value: x.id } + }) + setTeamList(teamOptionList) + if (form.getFieldValue('team') === undefined && data.teams?.length) { + form.setFieldValue('team', data.teams[0].id) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return [] + } + } + + /** + * 获取未配置模型提供者列表 + */ + const getModelProviderList = () => { + setModelModeLoading(true) + fetchData>(`ai/providers/unconfigured`, { + method: 'GET', + eoTransformKeys: ['default_llm', 'default_llm_logo'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const providers = data.providers || [] + modelProviderListRef.current = providers + if (providers.length) { + const id = providers[0].id + form.setFieldValue('modelMode', id) + getModelConfig(id) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setModelModeLoading(false) + }) + } + + /** + * 获取模型配置 + * @param id + */ + const getModelConfig = (id: string) => { + getLlmList(id) + fetchData>(`ai/provider/config`, { + method: 'GET', + eoParams: { provider: id }, + eoTransformKeys: ['get_apikey_url'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const modelEntity = { + ...data.provider, + defaultLlm: modelProviderListRef.current.find((x) => x.id === id)?.defaultLlm + } + setLocalEntity(modelEntity) + setFormFieldsValue(modelEntity) + updateEntityData(modelEntity) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setModelModeLoading(false) + }) + } + + /** + * 设置表单字段值 + * @param fieldsValue + */ + const setFormFieldsValue = (fieldsValue: any) => { try { form.setFieldsValue({ - defaultLlm: entity.defaultLlm, - config: entity!.config ? JSON.stringify(JSON.parse(entity!.config), null, 2) : '', - priority: entity.priority || 1, - enable: entity.status === 'enabled' + defaultLlm: fieldsValue.defaultLlm, + config: fieldsValue!.config ? JSON.stringify(JSON.parse(fieldsValue!.config), null, 2) : '', + priority: fieldsValue.priority || 1, + enable: fieldsValue.status === 'enabled' }) } catch (e) { form.setFieldsValue({ - defaultLlm: entity.defaultLlm, + defaultLlm: localEntity.defaultLlm, config: '', priority: 1, enable: true }) } + } + useEffect(() => { + // 如果是直接在 AI 模型配置,则获取默认模型列表和团队列表 + if (modelMode === 'auto') { + getLlmList() + setFormFieldsValue(localEntity) + } else { + getModelProviderList() + getTeamOptionList() + } }, []) + /** + * 部署 AI 服务 + */ + const deployAIServer: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + const finalValue = { + config: value.config, + model: value.defaultLlm, + team: value.team, + provider: localEntity?.id + } + console.log(finalValue) + fetchData>('quick/service/ai', { + method: 'POST', + 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)) + }) + } + + /** + * 保存 + * @returns + */ const save: () => Promise = () => { return new Promise((resolve, reject) => { form @@ -74,7 +227,7 @@ const AiSettingModalContent = forwardRef>('ai/provider/config', { method: 'PUT', - eoParams: { provider: entity?.id }, + eoParams: { provider: localEntity?.id }, eoBody: finalValue, eoTransformKeys: ['defaultLlm'] // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' @@ -103,7 +256,8 @@ const AiSettingModalContent = forwardRef ({ - save + save, + deployAIServer })) return ( @@ -117,6 +271,26 @@ const AiSettingModalContent = forwardRef + {modelMode === 'manual' && ( + label={$t('模型来源')} name="modelMode" rules={[{ required: true }]}> + + + )} label={$t('默认模型')} name="defaultLlm" rules={[{ required: true }]}> - - - label={ - - {$t('负载优先级')} - - - - - } - name="priority" - rules={[ - { required: true }, - { - validator: async (_, value) => { - if (value <= 0) { - throw new Error($t('优先级必须大于 0')) - } - return Promise.resolve() - } + {modelMode === 'auto' && ( + + label={ + + {$t('负载优先级')} + + + + } - ]} - initialValue={1} - > - - + name="priority" + rules={[ + { required: true }, + { + validator: async (_, value) => { + if (value <= 0) { + throw new Error($t('优先级必须大于 0')) + } + return Promise.resolve() + } + } + ]} + initialValue={1} + > + + + )} + {modelMode === 'manual' && ( + + + + )} label={$t('API Key(默认 Key)')} name="config"> - {entity.configured && ( + {localEntity?.configured && (
{$t('当前调用状态:')} - {entity.status === 'enabled' && {$t('正常')}} - {entity.status === 'disabled' && {$t('停用')}} - {entity.status === 'abnormal' && {$t('异常')}} + {localEntity.status === 'enabled' && {$t('正常')}} + {localEntity.status === 'disabled' && {$t('停用')}} + {localEntity.status === 'abnormal' && {$t('异常')}}
- {(entity.status === 'enabled' && !enableState) || (entity.status !== 'enabled' && enableState) ? ( + {(localEntity.status === 'enabled' && !enableState) || (localEntity.status !== 'enabled' && enableState) ? (
* {getTooltipText(enableState)}
) : null}
diff --git a/frontend/packages/core/src/pages/aiSetting/types.ts b/frontend/packages/core/src/pages/aiSetting/types.ts index 6ba2ea7a..6a8a4598 100644 --- a/frontend/packages/core/src/pages/aiSetting/types.ts +++ b/frontend/packages/core/src/pages/aiSetting/types.ts @@ -12,6 +12,7 @@ export interface ModelListData { name: string logo: string defaultLlm: string + modelMode?: string status: ModelStatus api_count: number key_count: number diff --git a/frontend/packages/core/src/pages/guide/AIModelGuide.tsx b/frontend/packages/core/src/pages/guide/AIModelGuide.tsx new file mode 100644 index 00000000..53c2288b --- /dev/null +++ b/frontend/packages/core/src/pages/guide/AIModelGuide.tsx @@ -0,0 +1,427 @@ +import restAPIPic from '@common/assets/restAPI.svg' +import onlineAIPic from '@common/assets/onlineAI.svg' +import localAIPic from '@common/assets/localAI.svg' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { $t } from '@common/locales' +import { Icon } from '@iconify/react/dist/iconify.js' +import { App, Upload, UploadProps, Form, message, Select } from 'antd' +import { Card } from 'antd' +import { useRef, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { LocalModelItem, MemberItem, SimpleTeamItem } from '@common/const/type' +import AiSettingModalContent, { AiSettingModalContentHandle } from '../aiSetting/AiSettingModal' +import { checkAccess } from '@common/utils/permission' +const { Dragger } = Upload +export const AIModelGuide = () => { + const { modal } = App.useApp() + const [, forceUpdate] = useState(null) + const [form] = Form.useForm() + const entityData = useRef(null) + const { fetchData } = useFetch() + const navigateTo = useNavigate() + const { checkPermission, accessData } = useGlobalContext() + const modalRef = useRef() + + /** + * 获取 team 选项列表 + * @returns + */ + const getTeamOptionList = async (): any[] => { + const response = await fetchData>( + !checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams', + { method: 'GET', eoTransformKeys: [] } + ) + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const teamOptionList = data.teams?.map((x: MemberItem) => { + return { ...x, label: x.name, value: x.id } + }) + if (form.getFieldValue('team') === undefined && data.teams?.length) { + form.setFieldValue('team', data.teams[0].id) + } + return teamOptionList + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return [] + } + } + + /** + * 部署 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>('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 deployPopularModel = async (id: string, modalInstance: any) => { + await deployLocalModel({ + modelID: id, + team: form.getFieldValue('team') + }) + modalInstance.destroy() + navigateTo(`/service/list`) + } + + /** + * 部署本地模型 + * @param value + * @returns + */ + const deployLocalModel = (value: { modelID: string; team?: number }) => { + return new Promise((resolve, reject) => { + const finalValue = { + model: value.modelID, + team: value?.team + } + console.log(finalValue) + fetchData>('model/local/deploy', { + method: 'POST', + 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(false) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + /** + * 获取本地模型列表 + * @returns 本地模型列表 + */ + const getLocalModelList = async (): any[] => { + const response = await fetchData>( + 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/model/local/can_deploy', + // 'model/local/can_deploy' + { method: 'GET', custom: true, eoTransformKeys: ['is_popular'] } + ) + // TODO_数据模拟 + if (response.ok) { + const datas = await response.json() + const { code, data, msg } = datas + if (code === STATUS_CODE.SUCCESS) { + const modelList = data.models?.map((x: LocalModelItem) => { + return { ...x, label: x.name, value: x.id } + }) + return modelList + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return [] + } + } else { + console.error('HTTP error', response.status) + } + // const { code, data, msg } = response + // if (code === STATUS_CODE.SUCCESS) { + // const modelList = data.models?.map((x: LocalModelItem) => { + // return { ...x, label: x.name, value: x.id } + // }) + // console.log('modelList===', modelList); + + // return modelList + // } else { + // message.error(msg || $t(RESPONSE_TIPS.error)) + // return [] + // } + } + + /** + * rest 服务卡片点击事件 + */ + const restCardClick = async () => { + form.resetFields() + const teamList = await getTeamOptionList() + const props: UploadProps = { + name: 'file', + multiple: false, + maxCount: 1, + beforeUpload: (file) => { + form.setFieldsValue({ key: file }) + forceUpdate({}) + return false + } + } + + modal.confirm({ + title: $t('添加 Rest 服务'), + content: ( + +
+ + +

+ +

+

{$t('选择 OpenAPI 文件 (.json / .yaml)')}

+
+
+ + + +
+
+ ), + onOk: () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then(async (value) => { + await deployRestServer(value.key.file) + resolve(true) + navigateTo(`/service/list`) + }) + .catch((errorInfo) => reject(errorInfo)) + }) + }, + width: 600, + okText: $t('确认'), + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + /** + * AI 模型配置弹窗 + */ + const aiCardClick = () => { + // 更新弹窗 + const updateEntityData = (data: any) => { + entityData.current = data + // 更新弹窗 + modalInstance.update({}) + } + const modalInstance = modal.confirm({ + title: $t('模型配置'), + content: ( + + ), + onOk: () => { + return modalRef.current?.deployAIServer().then((res) => { + if (res === true) { + navigateTo(`/service/list`) + } + }) + }, + width: 600, + okText: $t('确认'), + footer: (_, { OkBtn, CancelBtn }) => { + return ( +
+ + {$t('从 (0) 获取 API KEY', [entityData.current?.name])} + + +
+ + {checkAccess('system.devops.ai_provider.edit', accessData) ? : null} +
+
+ ) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + /** + * 本地部署 AI 并生成 API + */ + const localModelCardClick = async () => { + form.resetFields() + const teamList = await getTeamOptionList() + const modelList = await getLocalModelList() + const modalInstance = modal.confirm({ + title: $t('部署 AI 模型'), + content: ( + +
+ + +
+ + {$t('热点模型')} +
+
+ {modelList.length && + modelList + .filter((item) => item.is_popular) + .map((item) => ( + { + deployPopularModel(item.id, modalInstance) + }} + > + {item.name} + + ))} +
+
+ + + +
+
+ ), + onOk: () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then(async (value) => { + await deployLocalModel(value) + resolve(true) + navigateTo(`/service/list`) + }) + .catch((errorInfo) => reject(errorInfo)) + }) + }, + width: 600, + okText: $t('确认'), + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + const deployDeepSeek = (e: any) => { + e.stopPropagation() + deployLocalModel({ + modelID: 'deepseek-r1' + }) + } + + const cardList = [ + { + imgSrc: restAPIPic, + title: $t('添加 Rest 服务'), + description: $t('支持批量添加现有 API 文档以实现统一的外部访问。'), + click: restCardClick + }, + { + imgSrc: onlineAIPic, + title: $t('添加在线 AI API'), + description: $t('快速调用 AI 模型的云服务 API,方便管理提示词和统一计费。'), + click: aiCardClick + }, + { + imgSrc: localAIPic, + title: $t('本地部署 AI 并生成 API'), + description: $t('快速在本地部署开源模型并自动生成 API。'), + click: localModelCardClick, + bottomRender: ( + + + {$t('部署')} Deepseek-R1 + + ) + } + ] + return ( +
+ {cardList.map((item, itemIndex) => ( + + +

{item.title}

+

{item.description}

+ {item.bottomRender ? item.bottomRender : null} +
+ ))} +
+ ) +} diff --git a/frontend/packages/core/src/pages/guide/Guide.tsx b/frontend/packages/core/src/pages/guide/Guide.tsx index 9e79f2e8..ed8a085c 100644 --- a/frontend/packages/core/src/pages/guide/Guide.tsx +++ b/frontend/packages/core/src/pages/guide/Guide.tsx @@ -1,232 +1,321 @@ -import InsidePage from "@common/components/aoplatform/InsidePage" -import { useGlobalContext } from "@common/contexts/GlobalStateContext" -import { $t } from "@common/locales" -import { Icon } from "@iconify/react/dist/iconify.js" -import { Button, Card, Collapse } from "antd" -import { Dispatch, SetStateAction, useEffect, useState } from "react" -import { useLocation, useNavigate } from "react-router-dom" +import InsidePage from '@common/components/aoplatform/InsidePage' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { $t } from '@common/locales' +import { Icon } from '@iconify/react/dist/iconify.js' +import { Button, Card, Collapse } from 'antd' +import { Dispatch, SetStateAction, useEffect, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' +import { AIModelGuide } from './AIModelGuide' -export default function Guide(){ - const [showGuide, setShowGuide] = useState(localStorage.getItem('showGuide') !== 'false' ) - const [showAdvancedGuide, setShowAdvancedGuide] = useState(localStorage.getItem('showAdvancedGuide') !== 'false' ) - const [, forceUpdate] = useState(null); - const {state} = useGlobalContext() - const location = useLocation() - const currentUrl = location.pathname - const navigator = useNavigate() - const guideSections = [ +export default function Guide() { + const [showGuide, setShowGuide] = useState(localStorage.getItem('showGuide') !== 'false') + const [showAdvancedGuide, setShowAdvancedGuide] = useState(localStorage.getItem('showAdvancedGuide') !== 'false') + const [, forceUpdate] = useState(null) + const { state } = useGlobalContext() + const location = useLocation() + const currentUrl = location.pathname + const navigator = useNavigate() + const guideSections = [ + { + title: $t('快速接入 AI'), + items: [ { - title: $t('快速接入 AI'), - items: [ - { - title: $t("配置你的 AI 模型"), - description: $t('通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。'), - link: 'https://docs.apipark.com/docs/system_setting/ai_model_providers' - }, - { - title: $t("创建 AI 服务和 API"), - description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'), - link: 'https://docs.apipark.com/docs/services/ai_services' - }, - { - title: $t("创建调用 Token"), - description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'), - link: 'https://docs.apipark.com/docs/consumers' - }, - { - title: $t("调用"), - description: $t('现在你可以通过 Token 来调用这些 API。'), - link: 'https://docs.apipark.com/docs/call_api' - } - ] + title: $t('配置你的 AI 模型'), + description: $t('通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。'), + link: 'https://docs.apipark.com/docs/system_setting/ai_model_providers' }, { - title: $t('快速接入 REST API'), - items: [ - { - title: $t("创建 REST 服务和 API"), - description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'), - link: 'https://docs.apipark.com/docs/services/rest_services' - }, - { - title: $t("创建调用 Token"), - description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'), - link: 'https://docs.apipark.com/docs/consumers' - }, - { - title: $t("调用"), - description: $t('现在你可以通过 Token 来调用这些 API。'), - link: 'https://docs.apipark.com/docs/call_api' - } - ] + title: $t('创建 AI 服务和 API'), + description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'), + link: 'https://docs.apipark.com/docs/services/ai_services' }, { - title: $t('仪表盘'), - items: [ - { - title: $t("统计 API 调用情况"), - description: $t('仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。'), - link: 'https://docs.apipark.com/docs/analysis' - } - ] + title: $t('创建调用 Token'), + description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'), + link: 'https://docs.apipark.com/docs/consumers' + }, + { + title: $t('调用'), + description: $t('现在你可以通过 Token 来调用这些 API。'), + link: 'https://docs.apipark.com/docs/call_api' } - ]; - const advanceGuideSections = [ + ] + }, + { + title: $t('快速接入 REST API'), + items: [ { - title: $t('核心功能'), - items: [ - { - title: $t("账号与角色"), - description: $t('邀请你的团队成员加入 APIPark,共同管理和调用 API。'), - link: 'https://docs.apipark.com/docs/system_setting/account_role' - }, - { - title: $t("团队"), - description: $t('团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。'), - link: 'https://docs.apipark.com/docs/teams' - }, - { - title: $t("服务"), - description: $t('服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。'), - link: 'https://docs.apipark.com/docs/category/-%E6%9C%8D%E5%8A%A1' - } - ] + title: $t('创建 REST 服务和 API'), + description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'), + link: 'https://docs.apipark.com/docs/services/rest_services' }, { - title: $t('权限管理'), - items: [ - { - title: $t("订阅服务"), - description: $t('如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。'), - link: 'https://docs.apipark.com/docs/developer_portal' - }, - { - title: $t("审核订阅申请"), - description: $t('提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。'), - link: 'https://docs.apipark.com/docs/services/review_consumers' - } - ] + title: $t('创建调用 Token'), + description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'), + link: 'https://docs.apipark.com/docs/consumers' }, { - title: $t('集成'), - items: [ - { - title: $t("日志"), - description: $t('APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。'), - link: 'https://docs.apipark.com/docs/system_setting/log/' - } - ] + title: $t('调用'), + description: $t('现在你可以通过 Token 来调用这些 API。'), + link: 'https://docs.apipark.com/docs/call_api' } - ]; - useEffect(()=>{ - localStorage.setItem('showGuide', showGuide.toString()) - },[showGuide]) - useEffect(()=>{ - localStorage.setItem('showAdvancedGuide', showAdvancedGuide.toString()) - },[showAdvancedGuide]) - - useEffect(()=>{ - if(currentUrl === '/guide'){ - setTimeout(()=>{ - navigator('/guide/page') - },0) + ] + }, + { + title: $t('仪表盘'), + items: [ + { + title: $t('统计 API 调用情况'), + description: $t('仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。'), + link: 'https://docs.apipark.com/docs/analysis' } - },[]) - useEffect(()=>{forceUpdate({})},[state.language]) - return ( - - 👋 - {$t('Hello!欢迎使用 APIPark')} - -
} - description={
-

{$t("你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审核流程等诸多好处。")}

-

{$t("如果你喜欢我们的产品,欢迎给我们 Star 或提供产品反馈意见。")}

-
} - showBorder={false} - scrollPage={false} - contentClassName=" w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B" - > -
- {showGuide && - -

- 🚀{`${$t('快速入门')}`}

-

{$t("我们提供了一些任务来帮你快速了解 APIPark")}

, - children: }]} - />} - {showAdvancedGuide && - -

- 🏍️{`${$t('进阶教程')}`}

-

{$t("了解 APIPark 如何更好地管理 API 和 AI")}

, - children: }]} - />} - - - ) + ] + } + ] + const advanceGuideSections = [ + { + title: $t('核心功能'), + items: [ + { + title: $t('账号与角色'), + description: $t('邀请你的团队成员加入 APIPark,共同管理和调用 API。'), + link: 'https://docs.apipark.com/docs/system_setting/account_role' + }, + { + title: $t('团队'), + description: $t( + '团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。' + ), + link: 'https://docs.apipark.com/docs/teams' + }, + { + title: $t('服务'), + description: $t('服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。'), + link: 'https://docs.apipark.com/docs/category/-%E6%9C%8D%E5%8A%A1' + } + ] + }, + { + title: $t('权限管理'), + items: [ + { + title: $t('订阅服务'), + description: $t( + '如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。' + ), + link: 'https://docs.apipark.com/docs/developer_portal' + }, + { + title: $t('审核订阅申请'), + description: $t('提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。'), + link: 'https://docs.apipark.com/docs/services/review_consumers' + } + ] + }, + { + title: $t('集成'), + items: [ + { + title: $t('日志'), + description: $t('APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。'), + link: 'https://docs.apipark.com/docs/system_setting/log/' + } + ] + } + ] + useEffect(() => { + localStorage.setItem('showGuide', showGuide.toString()) + }, [showGuide]) + useEffect(() => { + localStorage.setItem('showAdvancedGuide', showAdvancedGuide.toString()) + }, [showAdvancedGuide]) + + useEffect(() => { + if (currentUrl === '/guide') { + setTimeout(() => { + navigator('/guide/page') + }, 0) + } + }, []) + useEffect(() => { + forceUpdate({}) + }, [state.language]) + return ( + + 👋 + {$t('Hello!欢迎使用 APIPark')} + + } + description={ +
+

+ {$t( + '你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审核流程等诸多好处。' + )} +

+

+ {$t('如果你喜欢我们的产品,欢迎给我们 Star 或提供产品反馈意见。')} + {$t('点击这里')} + +   + +   + + + + + +   + +   + + {$t('点击')} +   + + + + Star +

+
+ } + showBorder={false} + scrollPage={false} + contentClassName=" w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B" + > + +
+ {showGuide && ( + +

+ 🚀 + {`${$t('快速入门')}`}{' '} +

+

{$t('我们提供了一些任务来帮你快速了解 APIPark')}

+
+ ), + children: + } + ]} + /> + )} + {showAdvancedGuide && ( + +

+ 🏍️ + {`${$t('进阶教程')}`}{' '} +

+

{$t('了解 APIPark 如何更好地管理 API 和 AI')}

+ + ), + children: ( + + ) + } + ]} + /> + )} + +
+ ) } -const QuickGuideContent = ({changeGuideShow,guideSections}:{changeGuideShow:Dispatch>,guideSections: { - title: string; +const QuickGuideContent = ({ + changeGuideShow, + guideSections +}: { + changeGuideShow: Dispatch> + guideSections: { + title: string items: { - title: string; - description: string; - link: string; - }[]; -}[]})=>{ - - - return (<> -
- {guideSections.map((section, index) => ( -
-

- - {section.title} -

-
-
- {section.items.map((item, itemIndex) => ( - { window.open(item.link, '_blank') }} - > - {item.description} - - ))} -
-
-
- ))} -

- -

- - -
-

-
- ) -} \ No newline at end of file + title: string + description: string + link: string + }[] + }[] +}) => { + return ( + <> +
+ {guideSections.map((section, index) => ( +
+

+ + {section.title} +

+
+
+ {section.items.map((item, itemIndex) => ( + { + window.open(item.link, '_blank') + }} + > + {item.description} + + ))} +
+
+
+ ))} +
+ +
+ + +
+
+
+ + ) +} diff --git a/frontend/packages/core/src/pages/system/SystemConfig.tsx b/frontend/packages/core/src/pages/system/SystemConfig.tsx index 55a833da..b449c81c 100644 --- a/frontend/packages/core/src/pages/system/SystemConfig.tsx +++ b/frontend/packages/core/src/pages/system/SystemConfig.tsx @@ -440,7 +440,6 @@ const SystemConfig = forwardRef((_, ref) => { diff --git a/frontend/packages/core/src/pages/system/SystemList.tsx b/frontend/packages/core/src/pages/system/SystemList.tsx index e397ab58..5c08f906 100644 --- a/frontend/packages/core/src/pages/system/SystemList.tsx +++ b/frontend/packages/core/src/pages/system/SystemList.tsx @@ -14,6 +14,7 @@ import { useNavigate } from 'react-router-dom' import { SERVICE_KIND_OPTIONS, SYSTEM_TABLE_COLUMNS } from '../../const/system/const.tsx' import { SystemConfigHandle, SystemTableListItem } from '../../const/system/type.ts' import SystemConfig from './SystemConfig.tsx' +import { ServiceDeployment } from './serviceDeployment/ServiceDeployment.tsx' const SystemList: FC = () => { const navigate = useNavigate() @@ -23,7 +24,7 @@ const SystemList: FC = () => { const { fetchData } = useFetch() const [tableListDataSource, setTableListDataSource] = useState([]) const [tableHttpReload, setTableHttpReload] = useState(true) - const { message } = App.useApp() + const { message, modal } = App.useApp() const pageListRef = useRef(null) const [memberValueEnum, setMemberValueEnum] = useState<{ [k: string]: { text: string } }>({}) const [open, setOpen] = useState(false) @@ -128,7 +129,22 @@ const SystemList: FC = () => { const onClose = () => { setOpen(false) } + const openLogsModal = (record: any) => { + console.log('record', record) + modal.confirm({ + title: $t('部署过程'), + content: , + onOk: () => { + console.log('ok') + }, + width: 600, + okText: $t('确认'), + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } const columns = useMemo(() => { const res = SYSTEM_TABLE_COLUMNS.map((x) => { const dataIndex = x.dataIndex as string[] @@ -145,6 +161,21 @@ const SystemList: FC = () => { ;(x.valueEnum as any)[option.value] = { text: $t(option.label) } }) } + if ((x.dataIndex as string) === 'update_time') { + x.render = (text: any, record: any) => ( + .ant-typography]:text-[#2196f3]' : ''}`} + onClick={(e) => { + if (record.can_delete) { + e?.stopPropagation(); + openLogsModal(record) + } + }} + > + {text} + + ) + } return { ...x, title: typeof x.title === 'string' ? $t(x.title as string) : x.title } }) diff --git a/frontend/packages/core/src/pages/system/serviceDeployment/ServiceDeployment.tsx b/frontend/packages/core/src/pages/system/serviceDeployment/ServiceDeployment.tsx new file mode 100644 index 00000000..7f75a6e9 --- /dev/null +++ b/frontend/packages/core/src/pages/system/serviceDeployment/ServiceDeployment.tsx @@ -0,0 +1,44 @@ +import { SystemTableListItem } from '@core/const/system/type' +import type { StepsProps } from 'antd' +import { Popover, Steps } from 'antd' +import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons' + +const customDot: StepsProps['progressDot'] = (dot, { status, index }) => ( + + step {index} status: {status} + + } + > + {dot} + +) + +export const ServiceDeployment = (props: { record: SystemTableListItem }) => { + const { record } = props + console.log('record', record) + + const items = [ + { + title: 'Download', + description: '4.7 GB / 4.7 GB' + }, + { + title: 'Deploy', + }, + { + title: 'Initializing', + } + ] + return ( +
+ {/* */} + +
+ ) +}