From c7b16e0ea9b96cd043d2dbcd4c9753f3b61e47a0 Mon Sep 17 00:00:00 2001 From: scarqin Date: Mon, 30 Dec 2024 15:59:43 +0800 Subject: [PATCH] feat: ai model --- .../packages/core/src/pages/aiApis/index.tsx | 80 +--------- .../core/src/pages/aiSetting/AIFlowChart.tsx | 10 +- .../src/pages/aiSetting/AIUnconfigure.tsx | 35 ++--- .../src/pages/aiSetting/AiSettingList.tsx | 137 ++++++------------ .../src/pages/aiSetting/AiSettingModal.tsx | 37 ++++- .../aiSetting/contexts/AiSettingContext.tsx | 89 ++++++++++++ .../core/src/pages/aiSetting/types.ts | 4 +- 7 files changed, 189 insertions(+), 203 deletions(-) create mode 100644 frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx diff --git a/frontend/packages/core/src/pages/aiApis/index.tsx b/frontend/packages/core/src/pages/aiApis/index.tsx index e7880e53..bf485cca 100644 --- a/frontend/packages/core/src/pages/aiApis/index.tsx +++ b/frontend/packages/core/src/pages/aiApis/index.tsx @@ -1,4 +1,3 @@ -import Icon from '@ant-design/icons' import { ActionType } from '@ant-design/pro-components' import InsidePage from '@common/components/aoplatform/InsidePage' import PageList, { PageProColumns } from '@common/components/aoplatform/PageList' @@ -7,14 +6,12 @@ import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' import { useGlobalContext } from '@common/contexts/GlobalStateContext' import { useFetch } from '@common/hooks/http' import { $t } from '@common/locales' -import { checkAccess } from '@common/utils/permission' import AIProviderSelect, { AIProvider } from '@core/components/AIProviderSelect' import { App, Divider, Space, Typography } from 'antd' import dayjs from 'dayjs' import React, { useEffect, useRef, useState } from 'react' import { useSearchParams } from 'react-router-dom' -import ApiKeyContent from './components/ApiKeyContent' -import { APIKey, EditAPIKey } from './types' +import { APIKey } from './types' const KeySettings: React.FC = () => { const pageListRef = useRef(null) @@ -33,80 +30,9 @@ const KeySettings: React.FC = () => { pageListRef.current?.reload() }, [selectedProvider]) - const handleEdit = (record: APIKey) => { - openModal(record) - } + const handleEdit = (record: APIKey) => {} - const handleAdd = () => { - openModal() - } - - const openModal = async (entity?: EditAPIKey) => { - if (!provider) return - const mode = entity ? 'edit' : 'add' - if (mode === 'edit') { - message.loading($t(RESPONSE_TIPS.loading)) - const { code, data, msg } = await fetchData>('ai/resource/key', { - method: 'GET', - eoParams: { provider: selectedProvider, id: entity!.id } - // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' - }) - message.destroy() - if (code !== STATUS_CODE.SUCCESS) { - message.error(msg || $t(RESPONSE_TIPS.error)) - return - } - entity = data?.info - } else { - entity = { - name: `key${total}`, - config: provider.default_config, - expire_time: 0 - } as EditAPIKey - } - const newEntity = entity as EditAPIKey - - modal.confirm({ - title: mode === 'add' ? $t(`添加 ${provider?.name} APIKey`) : $t('编辑 APIKey'), - content: , - onOk: () => { - return new Promise((resolve, reject) => { - modalRef.current?.handleOk().then((res: boolean) => { - if (res === true) { - pageListRef.current?.reload() - resolve(res) - return - } - reject() - }) - }) - }, - width: 600, - okText: $t('确认'), - footer: (_, { OkBtn, CancelBtn }) => { - return ( -
- - {$t('从 (0) 获取 API KEY', [provider.name])} - - -
- - {checkAccess('system.settings.ai_key_resource.manager', accessData) ? : null} -
-
- ) - }, - cancelText: $t('取消'), - closable: true, - icon: <> - }) - } + const handleAdd = () => {} const handleDelete = async (id: string) => { try { diff --git a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx index 834057dd..92a0063d 100644 --- a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx @@ -20,8 +20,9 @@ import { KeyStatusNode } from './components/KeyStatusNode' import { ModelCardNode } from './components/ModelCardNode' import { ServiceCardNode } from './components/NodeComponents' import { LAYOUT } from './constants' +import { useAiSetting } from './contexts/AiSettingContext' import './styles.css' -import { AiSettingListItem, ModelData } from './types' +import { ModelData } from './types' export type ApiResponse = BasicResponse<{ backup: { @@ -61,17 +62,16 @@ const edgeTypes: EdgeTypes = { custom: CustomEdge } -const AIFlowChart = ({ openModal }: { openModal: (entity: AiSettingListItem) => Promise }) => { +const AIFlowChart = () => { const [modelData, setModelData] = useState([]) const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) const { fetchData } = useFetch() + const { openConfigModal } = useAiSetting() useEffect(() => { - // Mock API call - replace with actual API call fetchData('ai/providers/configured', { method: 'GET' - // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' }).then((response) => { const mockApiResponse: ApiResponse = response as ApiResponse setModelData(mockApiResponse.data.providers) @@ -105,7 +105,7 @@ const AIFlowChart = ({ openModal }: { openModal: (entity: AiSettingListItem) => defaultLlm: model.default_llm, logo: model.logo, id: model.id, - openModal + openModal: openConfigModal } })), ...modelData.map((model) => ({ diff --git a/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx b/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx index 4e924444..2d6245d5 100644 --- a/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx @@ -4,7 +4,8 @@ import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' import { useFetch } from '@common/hooks/http' import { $t } from '@common/locales' import { App, Button, Card, Empty, Spin, Tag } from 'antd' -import { FC, memo, useEffect, useState } from 'react' +import { memo, useEffect, useState } from 'react' +import { useAiSetting } from './contexts/AiSettingContext' import { AiSettingListItem } from './types' const CardBox = memo( @@ -92,26 +93,23 @@ const ModelCardArea = ({ ) } -interface AIUnConfigureProps { - openModal: (entity: AiSettingListItem) => Promise -} -const AIUnConfigure: FC = ({ openModal }) => { - const { message } = App.useApp() +const AIUnConfigure = () => { + const [modelData, setModelData] = useState([]) const { fetchData } = useFetch() - const [aiSettingList, setAiSettingList] = useState([]) + const { openConfigModal } = useAiSetting() const [loading, setLoading] = useState(false) - const getAiSettingList = () => { + useEffect(() => { setLoading(true) - return fetchData[] }>>( + fetchData[] }>>( `ai/providers/unconfigured`, { method: 'GET', eoTransformKeys: ['default_llm', 'default_llm_logo'] } ) .then((response) => { const { code, data, msg } = response if (code === STATUS_CODE.SUCCESS) { - setAiSettingList( + setModelData( data.providers?.map((x: AiSettingListItem) => ({ ...x, name: $t(x.name), @@ -120,14 +118,11 @@ const AIUnConfigure: FC = ({ openModal }) => { })) ) } else { + const { message } = App.useApp() message.error(msg || $t(RESPONSE_TIPS.error)) } }) .finally(() => setLoading(false)) - } - - useEffect(() => { - getAiSettingList() }, []) return ( @@ -135,13 +130,16 @@ const AIUnConfigure: FC = ({ openModal }) => { className="h-full" wrapperClassName="h-full pr-PAGE_INSIDE_X" indicator={} - spinning={loading} + spinning={modelData.length === 0} > - {aiSettingList && aiSettingList.length > 0 ? ( + {modelData && modelData.length > 0 ? (
- {aiSettingList.filter((item) => !item.configured).length > 0 && ( + {modelData.filter((item) => !item.configured).length > 0 && ( <> - !item.configured) || []} /> + !item.configured) || []} + /> )}
@@ -151,5 +149,4 @@ const AIUnConfigure: FC = ({ openModal }) => { ) } - export default AIUnConfigure diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx index 7d670d0a..20ab6863 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx @@ -1,105 +1,50 @@ import InsidePage from '@common/components/aoplatform/InsidePage' -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' -import { useGlobalContext } from '@common/contexts/GlobalStateContext' -import { useFetch } from '@common/hooks/http' import { $t } from '@common/locales' -import { checkAccess } from '@common/utils/permission' -import { Icon } from '@iconify/react/dist/iconify.js' -import { App, Tabs } from 'antd' -import { useRef } from 'react' +import { Tabs } from 'antd' import AIFlowChart from './AIFlowChart' -import AiSettingModalContent, { AiSettingModalContentHandle } from './AiSettingModal' import AIUnConfigure from './AIUnconfigure' -import { AiProviderConfig, AiSettingListItem } from './types' - -const AiSettingList = () => { - const { modal, message } = App.useApp() - const { fetchData } = useFetch() - const modalRef = useRef() - const { setAiConfigFlushed, accessData } = useGlobalContext() - - const openModal = async (entity: AiSettingListItem) => { - console.log(entity) - message.loading($t(RESPONSE_TIPS.loading)) - const { code, data, msg } = await fetchData>('ai/provider/config', { - method: 'GET', - eoParams: { provider: entity!.id }, - eoTransformKeys: ['get_apikey_url'] - }) - message.destroy() - if (code !== STATUS_CODE.SUCCESS) { - message.error(msg || $t(RESPONSE_TIPS.error)) - return - } - modal.confirm({ - title: $t('模型配置'), - content: ( - - ), - onOk: () => { - return modalRef.current?.save().then((res) => { - if (res === true) setAiConfigFlushed(true) - }) - }, - width: 600, - okText: $t('确认'), - footer: (_, { OkBtn, CancelBtn }) => { - return ( -
- - {$t('从 (0) 获取 API KEY', [data.provider.name])} - - -
- - {checkAccess('system.devops.ai_provider.edit', accessData) ? : null} -
-
- ) - }, - cancelText: $t('取消'), - closable: true, - icon: <> - }) - } +import { AiSettingProvider } from './contexts/AiSettingContext' +const AiSettingContent = () => { return ( - <> - -
- - }, - { - key: 'config', - label: $t('未设置'), - children:
{}
- } - ]} - /> -
-
- + +
+ + }, + { + key: 'config', + label: $t('未设置'), + children: ( +
+ +
+ ) + } + ]} + /> +
+
) } + +const AiSettingList = () => { + return ( + + + + ) +} + export default AiSettingList diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx index 26092df0..b9e8486c 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx @@ -2,7 +2,7 @@ import { Codebox } from '@common/components/postcat/api/Codebox' import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' import { useFetch } from '@common/hooks/http' import { $t } from '@common/locales' -import { App, Form, Select, Tag } from 'antd' +import { App, Form, InputNumber, Select, Tag } from 'antd' import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' import { AiProviderConfig, AiProviderLlmsItems } from './AiSettingList' @@ -18,6 +18,7 @@ export type AiSettingModalContentHandle = { type AiSettingModalContentField = { config: string defaultLlm: string + priority: number } const AiSettingModalContent = forwardRef((props, ref) => { @@ -52,12 +53,14 @@ const AiSettingModalContent = forwardRef { + const finalValue = { + ...value, + priority: Math.max(1, value.priority) + } + fetchData>('ai/provider/config', { method: 'PUT', eoParams: { provider: entity?.id }, - eoBody: value, + eoBody: finalValue, eoTransformKeys: ['defaultLlm'] }) .then((response) => { @@ -103,7 +111,7 @@ const AiSettingModalContent = forwardRef - label={$t('模型')} name="defaultLlm" 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() + } + } + ]} + initialValue={1} + > + + + label={$t('参数')} name="config"> Promise +} + +const AiSettingContext = createContext(undefined) + +export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const { modal, message } = App.useApp() + const { fetchData } = useFetch() + const { setAiConfigFlushed, accessData } = useGlobalContext() + const modalRef = useRef() + + const openConfigModal = async (entity: AiSettingListItem) => { + message.loading($t(RESPONSE_TIPS.loading)) + const { code, data, msg } = await fetchData>('ai/provider/config', { + method: 'GET', + eoParams: { provider: entity!.id }, + eoTransformKeys: ['get_apikey_url'] + }) + message.destroy() + if (code !== STATUS_CODE.SUCCESS) { + message.error(msg || $t(RESPONSE_TIPS.error)) + return + } + + modal.confirm({ + title: $t('模型配置'), + content: ( + + ), + onOk: () => { + return modalRef.current?.save().then((res) => { + if (res === true) { + setAiConfigFlushed(true) + } + }) + }, + width: 600, + okText: $t('确认'), + footer: (_, { OkBtn, CancelBtn }) => { + return ( +
+ + {$t('从 (0) 获取 API KEY', [data.provider.name])} + + +
+ + {checkAccess('system.devops.ai_provider.edit', accessData) ? : null} +
+
+ ) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + return {children} +} + +export const useAiSetting = () => { + const context = useContext(AiSettingContext) + if (!context) { + throw new Error('useAiSetting must be used within an AiSettingProvider') + } + return context +} diff --git a/frontend/packages/core/src/pages/aiSetting/types.ts b/frontend/packages/core/src/pages/aiSetting/types.ts index 43d46536..eebcc66a 100644 --- a/frontend/packages/core/src/pages/aiSetting/types.ts +++ b/frontend/packages/core/src/pages/aiSetting/types.ts @@ -27,6 +27,7 @@ export type AiSettingListItem = { defaultLlmLogo: string enable: boolean configured: boolean + priority?: number } export type AiProviderLlmsItems = { @@ -48,6 +49,7 @@ export type AiProviderDefaultConfig = { export type AiProviderConfig = { id: string name: string - config: string + config?: string getApikeyUrl: string + priority?: number }